FrankenPHP日志需强制写入php://stdout并配置JSON格式,避免被中间层截断或缓冲延迟;多通道日志应共用同一stdout handler,通过JSON字段区分来源,确保采集器可结构化解析。

FrankenPHP里日志不进stdout,得手动桥接
FrankenPHP 默认把 PHP-FPM 的 stderr 和 stdout 直接透传给宿主(比如 Docker 或 systemd),但 Symfony 的 Monolog 默认不写 stdout —— 它在 dev 环境走 console handler(输出到终端),在 prod 环境可能压根没配文件 handler,导致日志“消失”在 FrankenPHP 进程内部,采集器(如 Fluent Bit、Loki、Datadog Agent)根本收不到。
强制所有日志走 stdout 的最简配置
别依赖 console handler(它只在 CLI 模式下有效,Web 请求中行为不可靠),改用 stream handler 直接写 php://stdout:
monolog:
handlers:
stdout:
type: stream
path: 'php://stdout'
level: info
channels: ['!event', '!doctrine']
formatter: monolog.formatter.json
关键点:
-
path: 'php://stdout'是唯一能被 FrankenPHP 捕获并转发的输出目标 - 必须显式加
formatter: monolog.formatter.json,否则采集器无法结构化解析(默认是文本格式) -
channels: ['!event', '!doctrine']防止高频日志刷爆 stdout 流量 - 不要设
level: debug上生产 —— FrankenPHP 的 stdout 是单管道,debug 日志会挤占 error/info 的可见性
多通道日志怎么统一进同一管道
自定义通道(如 payment、api)不能各自开 php://stdout,否则日志行会乱序、无归属。正确做法是:所有通道共用一个 stdout handler,靠 JSON 字段区分来源:
立即学习“PHP免费学习笔记(深入)”;
monolog:
channels: ['payment', 'api', 'security']
handlers:
stdout:
type: stream
path: 'php://stdout'
level: info
channels: ['payment', 'api', 'security', 'main']
formatter: monolog.formatter.json
然后在代码里记录时带上上下文:
$logger->info('Payment succeeded', [
'channel' => 'payment',
'order_id' => 'ord_123',
'amount' => 99.99
]);
这样采集器收到的每条 JSON 都含 "channel":"payment" 字段,可按字段路由或过滤。
FrankenPHP + systemd 场景下的权限与缓冲陷阱
如果跑在 systemd 下,常见两个坑:
-
StandardOutput=journal或StandardOutput=append:/var/log/myapp.log会截断php://stdout输出 —— 必须设StandardOutput=inherit,让 FrankenPHP 自己接管 stdout - PHP 的
output_buffering默认开启,导致日志延迟几秒才刷出;在php.ini或 FrankenPHP 的php_value中加output_buffering=Off和implicit_flush=On - JSON 格式日志若含未转义换行符(比如异常堆栈),会被 systemd/journald 当成多条日志切分 —— 用
monolog.formatter.json已自动处理,但自定义 formatter 时务必调用json_encode($context, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
真正卡住的地方往往不是配置 YAML,而是 stdout 是否被中间层劫持、缓冲是否关闭、JSON 是否合法 —— 这三处任一出问题,日志就静默了。



















