Skip to content

Commit c658d1e

Browse files
committed
feat(logging): introduce new frankenphp_log method
The CGO method allow to log a php message while binding an array of random type as slog.Attr.
1 parent b8ea835 commit c658d1e

File tree

6 files changed

+134
-18
lines changed

6 files changed

+134
-18
lines changed

frankenphp.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,22 @@ PHP_FUNCTION(frankenphp_log_message) {
563563
go_log_n(message, message_len, (int)level);
564564
}
565565

566+
PHP_FUNCTION(frankenphp_log) {
567+
char *message = NULL;
568+
size_t message_len = 0;
569+
zend_long level = 0;
570+
zval *context = NULL;
571+
572+
ZEND_PARSE_PARAMETERS_START(2, 3)
573+
Z_PARAM_STRING(message, message_len)
574+
Z_PARAM_LONG(level)
575+
Z_PARAM_OPTIONAL
576+
Z_PARAM_ARRAY(context)
577+
ZEND_PARSE_PARAMETERS_END();
578+
579+
go_log_attrs(message, message_len, (int)level, context);
580+
}
581+
566582
PHP_MINIT_FUNCTION(frankenphp) {
567583
zend_function *func;
568584

frankenphp.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,84 @@ func go_log_n(message *C.char, len C.int, level C.int) {
639639
phpLog(m, level)
640640
}
641641

642+
//export go_log_attrs
643+
func go_log_attrs(message *C.char, len C.int, level C.int, ctx *C.zval) {
644+
m := C.GoStringN(message, len)
645+
646+
var lvl slog.Level
647+
if level < -8 || level > 8 {
648+
lvl = slog.LevelInfo
649+
} else {
650+
lvl = slog.Level(level)
651+
}
652+
653+
attrs, err := GoMap[any](unsafe.Pointer(ctx))
654+
if err != nil {
655+
attrs = nil
656+
}
657+
658+
logger.LogAttrs(context.Background(), lvl, m, mapToAttr(attrs)...)
659+
}
660+
661+
func mapToAttr(input map[string]any) []slog.Attr {
662+
out := make([]slog.Attr, 0, len(input))
663+
664+
for key, val := range input {
665+
switch v := val.(type) {
666+
case string:
667+
out = append(out, slog.String(key, v))
668+
669+
case bool:
670+
out = append(out, slog.Bool(key, v))
671+
672+
// signed ints
673+
case int:
674+
out = append(out, slog.Int(key, v))
675+
case int8:
676+
out = append(out, slog.Int64(key, int64(v)))
677+
case int16:
678+
out = append(out, slog.Int64(key, int64(v)))
679+
case int32:
680+
out = append(out, slog.Int64(key, int64(v)))
681+
case int64:
682+
out = append(out, slog.Int64(key, v))
683+
684+
// unsigned ints
685+
case uint:
686+
out = append(out, slog.Uint64(key, uint64(v)))
687+
case uint8:
688+
out = append(out, slog.Uint64(key, uint64(v)))
689+
case uint16:
690+
out = append(out, slog.Uint64(key, uint64(v)))
691+
case uint32:
692+
out = append(out, slog.Uint64(key, uint64(v)))
693+
case uint64:
694+
out = append(out, slog.Uint64(key, v))
695+
case uintptr:
696+
out = append(out, slog.Uint64(key, uint64(v)))
697+
698+
// floats
699+
case float32:
700+
out = append(out, slog.Float64(key, float64(v)))
701+
case float64:
702+
out = append(out, slog.Float64(key, v))
703+
704+
// bonus: quelques types usuels
705+
case time.Duration:
706+
out = append(out, slog.Duration(key, v))
707+
case time.Time:
708+
out = append(out, slog.Time(key, v))
709+
case error:
710+
out = append(out, slog.String(key, v.Error()))
711+
712+
default:
713+
out = append(out, slog.Any(key, val))
714+
}
715+
}
716+
717+
return out
718+
}
719+
642720
func phpLog(m string, level C.int) {
643721
var le syslogLevel
644722
if level < C.int(syslogLevelEmerg) || level > C.int(syslogLevelDebug) {

frankenphp.stub.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,9 @@ function apache_response_headers(): array|bool {}
3838
function mercure_publish(string|array $topics, string $data = '', bool $private = false, ?string $id = null, ?string $type = null, ?int $retry = null): string {}
3939

4040
function frankenphp_log_message(string $message, int $level): void {}
41+
42+
/**
43+
* @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
44+
* 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
45+
*/
46+
function frankenphp_log(string $message, int $level = 0, array $context = []): void {}

frankenphp_arginfo.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* This is a generated file, edit the .stub.php file instead.
2-
* Stub hash: dfc970945515e93e49bf676f5a08578101e62104 */
2+
* Stub hash: 1dfd2ae90bb4e3a3c58669fa6e4f685ecf1f0133 */
33

44
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_frankenphp_handle_request, 0, 1, _IS_BOOL, 0)
55
ZEND_ARG_TYPE_INFO(0, callback, IS_CALLABLE, 0)
@@ -40,13 +40,20 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_frankenphp_log_message, 0, 2, IS
4040
ZEND_ARG_TYPE_INFO(0, level, IS_LONG, 0)
4141
ZEND_END_ARG_INFO()
4242

43+
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_frankenphp_log, 0, 1, IS_VOID, 0)
44+
ZEND_ARG_TYPE_INFO(0, message, IS_STRING, 0)
45+
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, level, IS_LONG, 0, "0")
46+
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, context, IS_ARRAY, 0, "[]")
47+
ZEND_END_ARG_INFO()
48+
4349
ZEND_FUNCTION(frankenphp_handle_request);
4450
ZEND_FUNCTION(headers_send);
4551
ZEND_FUNCTION(frankenphp_finish_request);
4652
ZEND_FUNCTION(frankenphp_request_headers);
4753
ZEND_FUNCTION(frankenphp_response_headers);
4854
ZEND_FUNCTION(mercure_publish);
4955
ZEND_FUNCTION(frankenphp_log_message);
56+
ZEND_FUNCTION(frankenphp_log);
5057

5158
static const zend_function_entry ext_functions[] = {
5259
ZEND_FE(frankenphp_handle_request, arginfo_frankenphp_handle_request)
@@ -60,5 +67,6 @@ static const zend_function_entry ext_functions[] = {
6067
ZEND_RAW_FENTRY("apache_response_headers", zif_frankenphp_response_headers, arginfo_apache_response_headers, 0, NULL, NULL)
6168
ZEND_FE(mercure_publish, arginfo_mercure_publish)
6269
ZEND_FE(frankenphp_log_message, arginfo_frankenphp_log_message)
70+
ZEND_FE(frankenphp_log, arginfo_frankenphp_log)
6371
ZEND_FE_END
6472
};

frankenphp_test.go

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1022,25 +1022,24 @@ func TestFrankenPHPLog(t *testing.T) {
10221022
handler := slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug})
10231023
logger := slog.New(handler)
10241024

1025-
runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) {
1025+
runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, _ int) {
10261026
body, _ := testGet("http://example.com/logging.php", handler, t)
10271027
assert.Empty(t, body)
10281028
}, &testOptions{
10291029
logger: logger,
10301030
nbParallelRequests: 1,
1031+
nbWorkers: 1,
10311032
})
10321033

10331034
logOutput := buf.String()
10341035

1036+
t.Logf("captured logs: %s", logOutput)
1037+
10351038
for level, needle := range map[string]string{
1036-
"emerg": "level=ERROR msg=\"testing emerg as ERROR\" syslog_level=emerg\n",
1037-
"alert": "level=ERROR msg=\"testing alert as ERROR\" syslog_level=alert\n",
1038-
"crit": "level=ERROR msg=\"testing crit as ERROR\" syslog_level=crit\n",
1039-
"error": "level=ERROR msg=\"testing error as ERROR\" syslog_level=err\n",
1040-
"warn": "level=WARN msg=\"testing warning as WARN\" syslog_level=warning\n",
1041-
"notice": "level=INFO msg=\"testing notice as INFO\" syslog_level=notice\n",
1042-
"info": "level=INFO msg=\"testing info as INFO\" syslog_level=info\n",
1043-
"debug": "level=DEBUG msg=\"testing debug as DEBUG\" syslog_level=debug\n",
1039+
"debug attrs": `level=DEBUG msg="some debug message" "key int"=1`,
1040+
"info attrs": `level=INFO msg="some info message" "key string"=string`,
1041+
"warn attrs": `level=WARN msg="some warn message"`,
1042+
"error attrs": `level=ERROR msg="some error message" err="[a v]"`,
10441043
} {
10451044
assert.Containsf(t, logOutput, needle, "should contains %q log", level)
10461045
}

testdata/logging.php

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
<?php
22

3-
frankenphp_log_message("testing emerg as ERROR", LOG_EMERG);
4-
frankenphp_log_message("testing alert as ERROR", LOG_ALERT);
5-
frankenphp_log_message("testing crit as ERROR", LOG_CRIT);
6-
frankenphp_log_message("testing error as ERROR", LOG_ERR);
7-
frankenphp_log_message("testing warning as WARN", LOG_WARNING);
8-
frankenphp_log_message("testing notice as INFO", LOG_NOTICE);
9-
frankenphp_log_message("testing info as INFO", LOG_INFO);
10-
frankenphp_log_message("testing debug as DEBUG", LOG_DEBUG);
3+
// TODO: test arbitrary levels.
4+
// TODO: test more attr type
5+
6+
7+
frankenphp_log("some debug message", -4, [
8+
"key int" => 1,
9+
]);
10+
11+
frankenphp_log("some info message", 0, [
12+
"key string" => "string",
13+
]);
14+
15+
frankenphp_log("some warn message", 4);
16+
17+
frankenphp_log("some error message", 8, [
18+
"err" => ["a", "v"],
19+
]);

0 commit comments

Comments
 (0)