` 与 Caddy 构建结构化日志体系)
FrankenPHP 日志指南使用frankenphp_log()与 Caddy 构建结构化日志体系【免费下载链接】frankenphp The modern PHP app server项目地址: https://gitcode.com/GitHub_Trending/fr/frankenphp导读FrankenPHP 将 PHP 运行时深度嵌入 Caddy因此 PHP 应用产生的日志可以直接汇入 Caddy 的日志子系统与应用服务器、访问日志统一采集。本文以官方文档 docs/logging.md 为主体讲解两条日志通道面向现代可观测性的frankenphp_log()结构化日志函数以及兼容旧有应用的error_log()SAPI 模式非结构化日志。读完本文你将掌握两个函数的完整签名、参数语义、级别常量、JSON 输出形态并了解其底层是如何借助 Go 的log/slog实现的从而能在 Datadog、Grafana Loki、Elastic 等日志平台中按级别与字段高效检索。[!TIP] 日志只是 FrankenPHP 可观测性故事的一部分。完整的实时监控与指标视图请参阅 docs/observability.md若需要按日志、指标等维度规划生产环境可同时参考 docs/metrics.md。两条日志通道的定位FrankenPHP 与 Caddy 的日志系统无缝集成向 PHP 开发者提供两条互不冲突的通道通道函数输出形态适用场景结构化日志frankenphp_log()JSON含 level / ts / logger / msg 及自定义字段生产可观测性、日志平台检索、OpenTelemetry 等场景非结构化日志error_log($msg, 4)纯文本行兼容依赖标准 PHP 日志函数的既有应用与第三方库frankenphp_log()专为结构化日志设计将严重级别与任意上下文数据一并写入日志条目error_log()则保持 PHP 原生语义仅当$message_type为4SAPI时才会被路由到 Caddy 日志器。文档建议在生产环境优先使用frankenphp_log()因为它支持按级别Debug、Error 等过滤并可针对具体字段在日志基础设施中查询。frankenphp_log()结构化日志函数frankenphp_log()允许直接从 PHP 应用发出结构化日志使 Datadog、Grafana Loki、Elastic 等平台的采集以及 OpenTelemetry 支持都变得容易得多。底层实现上frankenphp_log()包装了 Go 的log/slog包以获得丰富的日志特性此为原文档说明的外部依赖非本仓库内文件。函数签名与参数function frankenphp_log(string $message, int $level FRANKENPHP_LOG_LEVEL_INFO, array $context []): void三个参数的含义如下message日志消息字符串。level日志严重级别。可以是任意整数官方提供四个便利常量定义见 frankenphp.stub.php常量值对应 slog 级别FRANKENPHP_LOG_LEVEL_DEBUG-4DebugFRANKENPHP_LOG_LEVEL_INFO0Info默认值FRANKENPHP_LOG_LEVEL_WARN4WarnFRANKENPHP_LOG_LEVEL_ERROR8Error从实现看级别值直接与 Go 的slog.Level对应frankenphp.go 中level : slog.Level(cLevel)级别越高代表事件越重要或越严重与log/slog的语义一致。由于可以是任意整数你完全可以自定义介于或高于这些常量之间的级别值。**context**一个关联数组其中的附加数据会写入日志条目。数组中的值会被转换为对应的 Go 类型只要 FrankenPHP 支持并通过slog.Attr加入结构化日志上下文见 [frankenphp.stub.php](https://link.gitcode.com/i/31db3f776085ea9bca7fdf5b0e7fbeac) 的文档注释以及 [frankenphp.go](https://link.gitcode.com/i/01fb855b71a80d0ee0343450d835ddb4) 中mapToAttr()将map[string]any逐项转为slog.Any 属性的逻辑。完整示例官方文档给出的示例覆盖了默认信息级日志与带上下文的警告日志?php // Log a simple informational message frankenphp_log(Hello from FrankenPHP!); // Log a warning with context data frankenphp_log( Memory usage high, FRANKENPHP_LOG_LEVEL_WARN, [ current_usage memory_get_usage(), peak_usage memory_get_peak_usage(), ], );查看日志时例如通过docker compose logs输出将呈现为结构化 JSON{level:info,ts:1704067200,logger:frankenphp,msg:Hello from FrankenPHP!} {level:warn,ts:1704067200,logger:frankenphp,msg:Memory usage high,current_usage:10485760,peak_usage:12582912}每条日志都包含统一的骨架字段level严重级别、tsUnix 时间戳、logger日志器名称frankenphp、msg消息以及context数组展开后的自定义字段如current_usage、peak_usage。这种形态可直接被日志平台按字段索引与查询。实现原理从 PHP 到 slog 的完整调用链从源码结构看frankenphp_log()的完整链路是这样的PHP 层C 扩展在 frankenphp.c 中注册PHP_FUNCTION(frankenphp_log)通过ZEND_PARSE_PARAMETERS解析message字符串、level长整型可选、context数组可选随后调用go_log_attrs()并传入当前线程索引。Go 层go_log_attrsfrankenphp.go先通过getLogger(threadIndex)取得当前 PHP 线程对应的slog.Logger与 context接着用logger.Enabled(ctx, level)判断该级别是否被启用若未启用则直接返回实现级别过滤随后把 PHP 数组转换为 Go map再经mapToAttr()展开为slog.Attr最终通过logger.LogAttrs()写出。日志器归属getLoggerfrankenphp.go优先返回当前请求线程上下文中的 logger即 Caddy 为请求配置的日志器否则回退到全局 loggerslog.Default()。这意味着frankenphp_log()的日志会流入 Caddy 为站点配置的日志输出如 stdout 或 JSON 文件与 Caddy 自身的访问日志统一出口。相应地级别过滤发生在写入之前当 Caddy 配置的日志级别低于DEBUG时Debug 级消息会被直接丢弃这也正是文档建议生产环境用frankenphp_log()按级别检索的底层支撑。仓库中的测试与示例进一步印证了上述行为集成测试 caddy/caddy_test.goTestLog用log { output stdout format json }配置站点日志并以 worker 模式加载 testdata/log-frankenphp_log.php该脚本依次调用四个级别的frankenphp_log()包括带key int、key string及嵌套数组err [a,v]的上下文数据。性能压测示例 profiles/app/frankenphp_log.php 演示了在压测脚本中同时输出 Debug带s字段与 Info 级日志并用function_exists(frankenphp_log)做降级保护——当函数不可用时回退到error_log()。error_log()兼容标准库的非结构化日志FrankenPHP 同样支持标准的error_log()函数。关键约束是只有当$message_type参数为4SAPI时消息才会被路由到 Caddy 日志器。默认情况下通过error_log()发送的消息被视为非结构化文本主要用于兼容依赖标准 PHP 库的既有应用或库。示例error_log(Database connection failed, 4);这条消息会出现在 Caddy 日志中并常带有表明其源自 PHP 的前缀。其底层路径是SAPI 层的日志消息回调frankenphp_log_messagefrankenphp.c被注册为 SAPI 模块的Log message处理器见 frankenphp.c 中frankenphp_log_message, /* Log message */消息经go_log()frankenphp.go进入 Go 层go_log会把系统日志级别syslog映射为 slog 级别如LOG_ERR→Error、LOG_WARNING→Warn、LOG_DEBUG→Debug其余默认Info并附加一个syslog_level字符串属性后写出。值得注意的是FrankenPHP 内部自身的健康检查消息如 frankenphp.c 的 “Request startup failed, thread is unhealthy” 与 frankenphp.c 的 “Failed to restart an unhealthy thread”也走这一相同的 SAPI 日志通道。仓库中的测试脚本 testdata/log-error_log.php 展示了最小用法每个请求调用error_log(request {$_GET[i]})用于验证 SAPI 模式日志在请求循环中的输出。[!TIP] 为了在生产环境获得更好的可观测性请优先使用frankenphp_log()它允许你按级别Debug、Error 等过滤日志并在日志基础设施中查询特定字段。实践建议如何组合使用两条通道结合文档指引与仓库实现可总结出如下落地建议新代码一律使用frankenphp_log()为每个关键业务事件指定明确的级别Debug/Info/Warn/Error并附上结构化上下文用户 ID、耗时、内存、错误码等让 Datadog、Loki、Elastic 可以直接按字段检索与告警。旧代码渐进迁移对依赖error_log()的既有库保持error_log($msg, 4)的调用形式即可兼容但要注意其输出是非结构化文本若需要字段化应逐步替换为frankenphp_log()。级别过滤依赖日志配置frankenphp_log()的级别过滤发生在写入前logger.Enabled因此请结合 Caddy 的log指令如output stdout、format json参见 caddy/caddy_test.go配置合适的输出与最低级别避免 Debug 噪音污染生产日志。Worker 模式下同样可用frankenphp_log()在 worker 模式php_server { worker ... }与常规模式下的行为一致测试 TestLog 即以 worker 脚本验证了四个级别的输出。与可观测性体系联动日志配合 docs/metrics.md 的指标与 docs/observability.md 的实时监控可构成完整的 FrankenPHP 生产可观测性方案。小结FrankenPHP 的日志能力本质上是一层“PHP 到 slog”的桥接frankenphp_log()提供带级别与上下文的结构化 JSON 日志适合现代日志平台error_log($msg, 4)提供兼容旧应用的非结构化文本日志。理解两者的差异与底层调用链PHP_FUNCTION(frankenphp_log)→go_log_attrs→slog.Logger.LogAttrs你就能在 Caddy 统一日志出口下构建一套可过滤、可检索、可告警的完整日志体系。【免费下载链接】frankenphp The modern PHP app server项目地址: https://gitcode.com/GitHub_Trending/fr/frankenphp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考