Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions frankenphp.c
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,28 @@ PHP_FUNCTION(mercure_publish) {
RETURN_THROWS();
}

PHP_FUNCTION(frankenphp_log) {
char *message = NULL;
size_t message_len = 0;
zend_long level = 0;
zval *context = NULL;

ZEND_PARSE_PARAMETERS_START(2, 3)
Z_PARAM_STRING(message, message_len)
Z_PARAM_LONG(level)
Z_PARAM_OPTIONAL
Z_PARAM_ARRAY(context)
ZEND_PARSE_PARAMETERS_END();

char *ret = NULL;
ret = go_log_attrs(thread_index, message, message_len, (int)level, context);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
PHP_FUNCTION(frankenphp_log) {
char *message = NULL;
size_t message_len = 0;
zend_long level = 0;
zval *context = NULL;
ZEND_PARSE_PARAMETERS_START(2, 3)
Z_PARAM_STRING(message, message_len)
Z_PARAM_LONG(level)
Z_PARAM_OPTIONAL
Z_PARAM_ARRAY(context)
ZEND_PARSE_PARAMETERS_END();
char *ret = NULL;
ret = go_log_attrs(thread_index, message, message_len, (int)level, context);
PHP_FUNCTION(frankenphp_log) {
zend_string *message = NULL;
zend_long level = 0;
zval *context = NULL;
ZEND_PARSE_PARAMETERS_START(2, 3)
Z_PARAM_STR(message)
Z_PARAM_LONG(level)
Z_PARAM_OPTIONAL
Z_PARAM_ARRAY(context)
ZEND_PARSE_PARAMETERS_END();
char *ret = NULL;
ret = go_log_attrs(thread_index, message, level, context);

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (ret != NULL) {
zend_throw_exception(spl_ce_RuntimeException, ret, 0);
free(ret);
RETURN_THROWS();
}
}

PHP_MINIT_FUNCTION(frankenphp) {
zend_function *func;

Expand Down
36 changes: 36 additions & 0 deletions frankenphp.go
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,42 @@ func go_log(threadIndex C.uintptr_t, message *C.char, level C.int) {
}
}

//export go_log_attrs
func go_log_attrs(threadIndex C.uintptr_t, message *C.char, len C.int, level C.int, cattrs *C.zval) *C.char {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
func go_log_attrs(threadIndex C.uintptr_t, message *C.char, len C.int, level C.int, cattrs *C.zval) *C.char {
func go_log_attrs(threadIndex C.uintptr_t, message *C.zend_string, level C.zend_long, cattrs *C.zval) *C.char {

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

var attrs map[string]any

if cattrs == nil {
attrs = nil
} else {
var err error
if attrs, err = GoMap[any](unsafe.Pointer(cattrs)); err != nil {
// NOTE: return value is already formatted for a PHP exception message.
return C.CString("Failed to log message: converting attrs: " + err.Error())
}
}

m := C.GoStringN(message, len)
lvl := slog.Level(level)

ctx := phpThreads[threadIndex].context()

if globalLogger.Enabled(ctx, lvl) {
globalLogger.LogAttrs(ctx, lvl, m, mapToAttr(attrs)...)
}

return nil
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
m := C.GoStringN(message, len)
lvl := slog.Level(level)
ctx := phpThreads[threadIndex].context()
if globalLogger.Enabled(ctx, lvl) {
globalLogger.LogAttrs(ctx, lvl, m, mapToAttr(attrs)...)
}
return nil
}
ctx := phpThreads[threadIndex].context()
if globalLogger.Enabled(ctx, lvl) {
globalLogger.LogAttrs(ctx, slog.Level(level), GoString(unsafe.Pointer(m)), mapToAttr(attrs)...)
}
return nil
}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


func mapToAttr(input map[string]any) []slog.Attr {
out := make([]slog.Attr, 0, len(input))

for key, val := range input {
out = append(out, slog.Any(key, val))
}

return out
}

//export go_is_context_done
func go_is_context_done(threadIndex C.uintptr_t) C.bool {
return C.bool(phpThreads[threadIndex].frankenPHPContext().isDone)
Expand Down
6 changes: 6 additions & 0 deletions frankenphp.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,9 @@ function apache_response_headers(): array|bool {}
* @param string|string[] $topics
*/
function mercure_publish(string|array $topics, string $data = '', bool $private = false, ?string $id = null, ?string $type = null, ?int $retry = null): string {}

/**
* @param int $level The importance or severity of a log event. The higher the level, the more important or severe the event. Common levels are -4 for debug, 0 for info, 4 for warn, and 8 for error. For more details, see: https://pkg.go.dev/log/slog#Level
* array<string, any> $context Values of the array will be converted to the corresponding Go type (if supported by FrankenPHP) and added to the context of the structured logs using https://pkg.go.dev/log/slog#Attr
*/
function frankenphp_log(string $message, int $level = 0, array $context = []): void {}
10 changes: 8 additions & 2 deletions frankenphp_arginfo.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: cd534a8394f535a600bf45a333955d23b5154756 */
* Stub hash: 28aa97e2c6102b3e51059dbd001ac65679f0bfda */

ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_frankenphp_handle_request, 0, 1, _IS_BOOL, 0)
ZEND_ARG_TYPE_INFO(0, callback, IS_CALLABLE, 0)
Expand Down Expand Up @@ -35,14 +35,19 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_mercure_publish, 0, 1, IS_STRING
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, retry, IS_LONG, 1, "null")
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_frankenphp_log, 0, 1, IS_VOID, 0)
ZEND_ARG_TYPE_INFO(0, message, IS_STRING, 0)
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, level, IS_LONG, 0, "0")
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, context, IS_ARRAY, 0, "[]")
ZEND_END_ARG_INFO()

ZEND_FUNCTION(frankenphp_handle_request);
ZEND_FUNCTION(headers_send);
ZEND_FUNCTION(frankenphp_finish_request);
ZEND_FUNCTION(frankenphp_request_headers);
ZEND_FUNCTION(frankenphp_response_headers);
ZEND_FUNCTION(mercure_publish);

ZEND_FUNCTION(frankenphp_log);

static const zend_function_entry ext_functions[] = {
ZEND_FE(frankenphp_handle_request, arginfo_frankenphp_handle_request)
Expand All @@ -55,5 +60,6 @@ static const zend_function_entry ext_functions[] = {
ZEND_FE(frankenphp_response_headers, arginfo_frankenphp_response_headers)
ZEND_FALIAS(apache_response_headers, frankenphp_response_headers, arginfo_apache_response_headers)
ZEND_FE(mercure_publish, arginfo_mercure_publish)
ZEND_FE(frankenphp_log, arginfo_frankenphp_log)
ZEND_FE_END
};
29 changes: 28 additions & 1 deletion frankenphp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1013,7 +1013,34 @@ func FuzzRequest(f *testing.F) {
// Headers should always be present even if empty
assert.Contains(t, body, fmt.Sprintf("[CONTENT_TYPE] => %s", fuzzedString))
assert.Contains(t, body, fmt.Sprintf("[HTTP_FUZZED] => %s", fuzzedString))

}, &testOptions{workerScript: "request-headers.php"})
})
}

func TestFrankenPHPLog(t *testing.T) {
var buf bytes.Buffer
handler := slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug})
logger := slog.New(handler)

runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, _ int) {
body, _ := testGet("http://example.com/log_to_slog.php", handler, t)
assert.Empty(t, body)
}, &testOptions{
logger: logger,
nbParallelRequests: 1,
nbWorkers: 1,
})

logOutput := buf.String()

t.Logf("captured log output: %s", logOutput)

for level, needle := range map[string]string{
"debug attrs": `level=DEBUG msg="some debug message" "key int"=1`,
"info attrs": `level=INFO msg="some info message" "key string"=string`,
"warn attrs": `level=WARN msg="some warn message"`,
"error attrs": `level=ERROR msg="some error message" err="[a v]"`,
} {
assert.Containsf(t, logOutput, needle, "should contains %q log", level)
}
}
23 changes: 23 additions & 0 deletions testdata/log_to_slog.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

// NOTE: use CGO frankenphp_log method.
// The message and it's optional arguments are expected to be logged by go' std slog system.
// The log level should be respected out of the box by the std' slog.
//
// ac[0] expect the log message as string
// ac[1] expect the slog.Level, from -8 to +8
// ac[2] is an optional php map, which will be converted to a []slog.Attr

frankenphp_log("some debug message", -4, [
"key int" => 1,
]);

frankenphp_log("some info message", 0, [
"key string" => "string",
]);

frankenphp_log("some warn message", 4);

frankenphp_log("some error message", 8, [
"err" => ["a", "v"],
]);