log_errors = on 仅开启错误记录开关,不自动写入堆栈;需配合 set_exception_handler() 和 $e->gettraceasstring() 才能将完整异常堆栈写入日志,且必须置于入口脚本顶部。

log_errors = On 只是开关,不等于堆栈自动进文件
开了 log_errors = On 之后,PHP 仍只记录错误摘要(比如 PHP Warning: Invalid argument supplied to foreach()),不会附带堆栈跟踪。这是因为 PHP 在 SAPI 模式(包括 FrankenPHP)下默认跳过 Exception::getTraceAsString() 调用——它只把错误“抛给”日志系统,不展开上下文。
常见现象:日志里看到 Uncaught Error: Call to undefined function foo(),但没 Stack trace: 后续行,定位不到哪段代码触发的。
-
log_errors = On必须和error_log同在一份生效的 php.ini 中(用frankenphp php-cli -i | grep "Loaded Configuration File"确认路径) - 设
error_log = /var/log/php-warnings.log时,确保目录存在且 FrankenPHP worker 进程有写权限(Docker 中常需chown www-data:www-data /var/log) - 不要设
error_log = php-errors.log(相对路径),工作目录不确定,日志大概率写失败
set_error_handler() 捕获 E_WARNING/E_NOTICE,但不能捕获 E_ERROR
set_error_handler() 能接管 E_WARNING、E_NOTICE、E_USER_* 等非致命错误,但它对 E_ERROR、E_PARSE、E_COMPILE_ERROR 无效——这些会直接终止脚本,跳过自定义处理器。
典型场景:你在 public/index.php 开头注册了 handler,但入口文件本身有语法错误(比如漏了分号),这个错误根本不会进 handler,只能靠 stderr 或 Caddy 启动日志看到。
- handler 函数里必须返回
true才能屏蔽默认行为(否则错误仍会打到 error_log) - 用
$e->getTraceAsString()获取堆栈,比debug_print_backtrace()更适合写入日志(后者输出格式不可控) - 别忘了
error_reporting()要包含你想捕获的级别,例如error_reporting(E_ALL & ~E_DEPRECATED)
register_shutdown_function() 补漏 E_ERROR 和 Parse Error
要捕获 E_ERROR 和解析期错误(如 Parse error: syntax error),必须搭配 register_shutdown_function() + error_get_last()。FrankenPHP 启动后,PHP 引擎会在脚本终止前调用 shutdown 函数,这时才能拿到最后的致命错误信息。
注意:这个函数无法区分是正常退出还是崩溃,所以一定要先判断 error_get_last() 是否非空,再决定是否记录。
- 必须放在入口脚本最顶部(早于任何可能出错的代码)
- 检查
error_get_last()['type']是否属于E_ERROR、E_PARSE、E_CORE_ERROR等致命类型 - 用
error_log($msg, 3, $file)写入文件时,$file必须是绝对路径,且与log_errors配置的路径一致,避免日志分散 - 如果用了框架(如 Laravel),它的异常处理中间件可能提前 exit,导致 shutdown 不触发——临时注释掉中间件再验证
PHP 错误日志和 Caddy 日志是两套系统,别混用
Caddy 的 log 指令只记录 HTTP 层事件(请求方法、状态码、耗时),完全不感知 PHP 的 E_WARNING 或 Fatal error。你不能指望在 /var/log/caddy/access.log 里看到 Undefined variable $foo。
反过来说,PHP 的 error_log() 输出也不会自动带上请求 ID 或客户端 IP——除非你手动拼接 frankenphp_request_id() 和 $_SERVER['REMOTE_ADDR']。
- 想关联 PHP 错误和某次 HTTP 请求?在每个入口脚本开头加:
error_log('[' . frankenphp_request_id() . '] ' . $_SERVER['REMOTE_ADDR'] . ' - '); - 多站点部署时,Caddy 的
log { output file ... }可按域名分离访问日志,但 PHP 错误日志仍需靠每个站点入口的ini_set('error_log', '/path/to/site-php.log')分离 - 容器环境推荐
error_log = stderr,这样docker logs -f能同时看到 Caddy 访问日志和 PHP 错误,靠请求 ID 字符串做关联
实际调试时最容易忽略的是:error_reporting 值被框架或 Composer 自动加载器覆盖,或者 set_error_handler() 注册太晚(比如在路由 dispatch 之后),导致部分警告根本没被捕获。建议在 public/index.php 第一行就做三件事:确认 error_reporting、注册 set_error_handler、注册 register_shutdown_function。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











