FrankenPHP 默认不关联访问日志与业务日志,需通过 X-Request-ID 实现关联:启动时用 --log-format 注入 header,PHP 入口生成并透传该 ID,所有日志显式携带,Monolog 用 Processor 统一注入,避免依赖 $_SERVER['REQUEST_ID']。

FrankenPHP 的访问日志和业务日志默认不关联
FrankenPHP 本身不自动注入请求 ID 或 trace ID 到 PHP 进程上下文,所以 error_log()、Monolog 或 Laravel 的日志写入,和 Nginx/Apache 风格的访问日志(如 access.log)天然脱节。你看到的每条访问日志行和每条 PHP 错误/调试日志之间,没有可直接匹配的字段。
用 X-Request-ID 头 + 自定义日志格式打通两端
最轻量且兼容性最好的方式是:由 FrankenPHP 在入口层生成唯一 X-Request-ID,透传给 PHP,并让 PHP 日志器在每条日志中显式输出该 ID;同时修改 FrankenPHP 的日志格式,把该 header 写入 access.log。
- FrankenPHP 启动时加
--log-format参数,例如:frankenphp --log-format='{"time":"%t","status":%s,"method":"%m","path":"%U","request_id":"%{X-Request-ID}i"}' - 在 PHP 入口(如
public/index.php)开头加:$requestId = $_SERVER['HTTP_X_REQUEST_ID'] ?? bin2hex(random_bytes(8));<br>putenv("REQUEST_ID=$requestId"); - 所有后续日志(包括
error_log()、Monologhandler)都应手动拼接该 ID,例如:error_log("[{$requestId}] User login failed: invalid token"); - 注意:FrankenPHP 不会自动设置
X-Request-ID,需前端反代(如 Nginx)或客户端携带;若无外部网关,可在frankenphp.yaml的server下用headers注入:headers:<br> X-Request-ID: "{{.RequestID}}"
PHP 中避免依赖 $_SERVER['REQUEST_ID'] 直接读取
FrankenPHP 不会把自定义环境变量(如 REQUEST_ID)自动注入到 $_SERVER 数组里,只对 putenv() 设置的变量生效于当前进程——但很多日志库(如 Laravel 的 Log::info())默认不读取环境变量,也不会自动带入上下文。
- 不要写
$_SERVER['REQUEST_ID']—— 它不存在,会返回undefined index - 改用
getenv('REQUEST_ID')或$_ENV['REQUEST_ID'](需确保variables_order包含E) - Monolog 可用
Processors统一注入:$logger->pushProcessor(new \Monolog\Processor\PsrLogMessageProcessor());<br>$logger->pushProcessor(function ($record) {<br> $record['extra']['request_id'] = getenv('REQUEST_ID') ?: 'unknown';<br> return $record;<br>}); - 若用 Swoole 或常驻进程模型,注意
putenv()是进程级的,每次请求必须重设,否则会复用上一次的值
别指望 access.log 时间戳能精确对齐 PHP 日志
FrankenPHP 的访问日志写入发生在响应头发送前,而 PHP 业务日志可能在响应体生成中途甚至结束后才写入(尤其用了缓冲或异步 handler)。即使时间戳都用 %t 和 date('c'),毫秒级误差仍常见。
立即学习“PHP免费学习笔记(深入)”;
- 排查时优先匹配
X-Request-ID,而非时间 - 如果发现某次请求在
access.log里有记录,但 PHP 日志完全缺失,大概率是 PHP 进程崩溃或被强制终止(如超时),没走到日志写入逻辑 - FrankenPHP 的
--log-level debug会输出内部调度信息,可用于确认请求是否真正进入 PHP 执行阶段



















