未捕获异常在FrankenPHP worker模式下会静默失败,因进程常驻且set_exception_handler仅限当前请求有效;必须用frankenphp_handle_request()包裹请求逻辑、外层try/catch兜底、禁用exit/throw,并配合error_log记录与FRANKENPHP_DEBUG=1调试。

未捕获异常在 worker 模式下直接静默失败
FrankenPHP 的 worker 模式中,PHP 进程长期存活,set_exception_handler() 注册的全局处理器只在**当前请求生命周期内有效**;一旦异常未被捕获且 handler 里调用了 exit() 或抛出新异常,整个 worker 进程可能崩溃退出,而 FrankenPHP 默认不会自动重启它(除非显式配置 restart: always)。你看到的往往不是堆栈,而是连接中断、500 空响应或日志里一条“worker died”。
必须用 frankenphp_handle_request() 包裹 + 显式兜底
不能依赖框架自带的异常处理链——它可能在 worker 复用上下文时失效。入口脚本(如 public/index.php)必须按以下结构组织:
- 循环外:初始化框架、数据库连接池、Redis 客户端等一次性资源
- 循环内:用
frankenphp_handle_request()包裹完整请求处理逻辑 - 在回调函数末尾或
catch块中,确保执行:gc_collect_cycles()、DB::disconnect()(Laravel)、think\Container::clear()(TP6)等清理动作 - 在
frankenphp_handle_request()回调外层加try/catch,捕获所有逃逸异常并记录到error_log(),**禁止 throw 或 exit**
set_exception_handler() 仍需注册,但作用域受限
该函数在 worker 模式下依然有效,但它只对「当前请求中未被 try/catch 捕获的异常」起作用。常见陷阱包括:
- 中间件提前
return或throw,绕过后续 handler 注册点 - 异步任务(如
pcntl_fork子进程、协程任务)中抛出的异常无法被主进程 handler 捕获 - handler 内部调用
http_response_code(500)后直接echo,若 output buffering 未开启,响应可能被截断 - 必须在每次
frankenphp_handle_request()调用前重新注册 handler,因为某些框架会覆盖它
生产环境别信 display_errors=On
开发时靠 display_errors=On 看堆栈是权宜之计;worker 模式下它反而容易导致响应头混乱或缓冲区溢出。真实线上方案是:
立即学习“PHP免费学习笔记(深入)”;
- 所有异常统一走
error_log($e->__toString(), 4)写入 syslog 或文件,路径需可写且轮转配置到位 - 配合
FRANKENPHP_DEBUG=1启动,让响应头带X-PHP-Exception,前端或监控系统可据此告警 - 若用 Sentry 或 OpenTelemetry,确保 SDK 初始化在循环外,并调用
flush()在每次请求结束前提交事件 - 检查
log_errors_max_len是否过小(默认 1024),大堆栈会被截断
gc_collect_cycles() 或一次 DB::disconnect(),下一个请求就可能复用上一个请求的异常状态。



















