FrankenPHP 默认不记录 PHP 异常完整堆栈,需用 set_exception_handler() 捕获并调用 $e->__toString() 或 $e->getTraceAsString() 手动写入日志,且应置于 server.php 入口顶部以确保全局生效。

FrankenPHP 默认不记录 PHP 异常堆栈到日志
默认情况下,FrankenPHP 会把 error_log() 或未捕获异常的错误级别消息(如 E_ERROR、E_WARNING)输出到标准错误(stderr),但不会自动展开并写入完整堆栈跟踪。你看到的日志里只有类似 PHP Fatal error: Uncaught Exception: ... 的一行,没有后续的 Stack trace: 部分——这是因为 PHP 的默认错误处理器在 SAPI(如 FrankenPHP)中被绕过了部分行为。
用 set_exception_handler() 捕获并手动写入堆栈
最直接可控的方式是注册自定义异常处理器,把堆栈转成字符串后交由 error_log() 或写入文件。FrankenPHP 运行在 Go 层,但 PHP 运行时仍是标准 Zend 引擎,所以这个函数完全有效:
set_exception_handler(function (Throwable $e) {
$message = sprintf(
"[EXCEPTION] %s: %s in %s:%d\n%s",
get_class($e),
$e->getMessage(),
$e->getFile(),
$e->getLine(),
$e->getTraceAsString()
);
error_log($message);
});
- 必须放在所有可能抛异常的代码之前(通常在入口脚本顶部或框架 boot 阶段)
-
$e->getTraceAsString()是关键,它生成人类可读的堆栈,比debug_print_backtrace()更适合日志 - 如果用
error_log($message, 4, '/var/log/php-app.log')指定文件,需确保 FrankenPHP worker 进程有写权限 - 注意:此方式不捕获
Fatal Error(如 parse error),只捕获Throwable;要覆盖 fatal,还需配合register_shutdown_function()+error_get_last()
启用 PHP 的 display_errors = Off + log_errors = On 并配 error_log
仅靠 ini 设置无法让堆栈进日志——PHP 在 CLI/SAPI 模式下对 log_errors 的处理较保守。但可以辅助增强:
- 确保
log_errors = On(FrankenPHP 默认开启) - 设
display_errors = Off(避免堆栈泄露到 HTTP 响应) - 指定
error_log = /var/log/php-errors.log(路径需 worker 进程可写) - ⚠️ 仍不会记录完整堆栈:PHP 只记录错误摘要,不调用
Exception::getTraceAsString()
所以 ini 配置只能作为兜底,不能替代 set_exception_handler()。
立即学习“PHP免费学习笔记(深入)”;
在 FrankenPHP 的 server.php 中统一注入日志逻辑
如果你用的是 FrankenPHP 官方推荐的 server.php 入口(如 Laravel、Symfony 等项目),最佳实践是把异常处理器放在该文件顶部,而非框架内部——这样能捕获框架启动前的异常:
// server.php
<?php
use Symfony\Component\Runtime\Runner\SymfonyRuntimeRunner;
// ✅ 放在这里,早于任何框架加载
set_exception_handler(function (Throwable $e) {
error_log('[FRANKENPHP] ' . $e->__toString());
});
require __DIR__.'/vendor/autoload.php';
$runtime = new SymfonyRuntimeRunner();
exit($runtime->run());
$e->__toString() 是更简捷的选择,它已内置格式化堆栈,等价于 get_class() + getMessage() + getFile()/getLine() + getTraceAsString() 组合。
复杂点在于:不同框架可能有自己的异常处理器(如 Laravel 的 App\Exceptions\Handler),若同时启用,容易重复记录或冲突。建议只保留一层——要么用框架的,要么用这里的全局层,别混用。



















