-
Notifications
You must be signed in to change notification settings - Fork 407
feat: add frankenphp_log_message() as a PHP function
#1979
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
ee77c62 to
6cec2d9
Compare
ff20363 to
8e172c5
Compare
|
You should probably also add an integration test to 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, i int) {
body, _ := testGet(fmt.Sprintf("http://example.com/log.php", i), handler, t)
assert.Equal(t, body, fmt.Sprintf("whatever the script output looks like", i))
}, &testOptions{logger: logger})
// time.Sleep(time.Millisecond * 10) // not sure if a sleep is needed to avoid race conditions
logOutput := buf.String()
assert.Contains(t, logOutput, "whatever was logged")
} |
|
I'm working on this too. I wonder if we shouldn't expose slog more directly: give the ability to add structured logs, custom OpenTelemetry-compatible levels etc? What I have in mind was to expose a class implementing both PSR-3 and a special WDYT? |
That sounds great, indeed! That would maybe help us filter the logging for special categories. In the application we're developing, the PHP code currently produces an "audit log" (ex: "user A added a new user") by writing directly to a file. This file currently needs to be watched to retrieve the audit events and forward them to the monitoring server. It would be great if we could just pipe these events through the same logging pipeline inside the Go code. @dunglas let me know if you need any help with this more capable implementation. |
c0ddb8c to
9cc7b0d
Compare
|
Actually, I wonder if we couldn't just update your PR to provide a single function having this API: /**
* @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
*/
frankenphp_log(string $message, int $level = 0, array $context = []);This will provide the low-level brick to have PSR-3 and similar implementations user-land. This will also keep full compatibility with Go and Caddy logging systems, as well as with OpenTelemetry, because of the more open log level. WDYT? |
|
So that means keeping the old SAPI function |
|
You can probably just use this function to convert an arbitrary array to a map. Currently it will take a zval, after #1894 you'll be able to pass the array directly. |
24339e5 to
7f3e43e
Compare
frankenphp.stub.php
Outdated
| */ | ||
| function mercure_publish(string|array $topics, string $data = '', bool $private = false, ?string $id = null, ?string $type = null, ?int $retry = null): string {} | ||
|
|
||
| function frankenphp_log_message(string $message, int $level): void {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the benefit of keeping this function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
none, let me remove it
frankenphp.go
Outdated
| var lvl slog.Level | ||
| if level < -8 || level > 8 { | ||
| lvl = slog.LevelInfo | ||
| } else { | ||
| lvl = slog.Level(level) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| var lvl slog.Level | |
| if level < -8 || level > 8 { | |
| lvl = slog.LevelInfo | |
| } else { | |
| lvl = slog.Level(level) | |
| } | |
| lvl = slog.Level(level) |
According to the docs, any int can be used as level: https://pkg.go.dev/log/slog#hdr-Levels
frankenphp.go
Outdated
|
|
||
| ctx := phpThreads[threadIndex].context() | ||
|
|
||
| // NOTE: no need to check for logger enabled levels. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should do it anyway as early as possible to prevent useless work and allocations.
d0ca622 to
5083c4f
Compare
The CGO method allow to log a php message while binding an array of random type as slog.Attr.
|
Otherwise starting to look good👍, CI failures are unrelated to this PR I think (apart from the clang-format one) |
frankenphp.c
Outdated
| 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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| 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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
frankenphp.go
Outdated
| } | ||
|
|
||
| //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 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| 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 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
frankenphp.go
Outdated
| 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 | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| 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 | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently, franken.gp:705 won't compile
As discussed in #1961, there is no real way to pass a severity/level to any log handler offered by PHP that would make it to the FrankenPHP layer. This new function allows applications embedding FrankenPHP to integrate PHP logging into the application itself, thus offering a more streamlined experience.