php错误日志最佳架构需同时覆盖e_error、e_warning和未捕获异常三类源头,仅用set_exception_handler不牢靠,因其无法捕获致命错误;必须配合register_shutdown_function+error_get_last()和set_error_handler协同工作。

PHP 自定义异常处理体系不能只靠 set_exception_handler,错误日志的最佳架构必须同时覆盖 E_ERROR、E_WARNING 和未捕获异常三类源头,缺一不可。
为什么 set_exception_handler 单独用不牢靠
它只捕获 throw 出来的 Exception 或 Throwable,对 include 不存在的文件、函数名拼错、语法解析失败等 E_ERROR / E_PARSE 类致命错误完全无感。这类错误会直接终止脚本,连 set_exception_handler 的回调都进不去。
常见现象:线上突然 500,Nginx error log 里只有 PHP message: PHP Fatal error: Uncaught Error: Call to undefined function foo()...,但你的异常处理器日志里空空如也。
- 致命错误必须靠
register_shutdown_function+error_get_last()捞底 -
set_error_handler负责E_WARNING、E_NOTICE等非致命错误 - 三者注册顺序无关紧要,但必须在脚本最开始(如入口
index.php头部)就完成
set_error_handler 怎么转错误为异常才不翻车
不是所有错误都该 throw。比如 E_USER_DEPRECATED 或某些被 @ 抑制的 E_WARNING,强行 throw 会导致流程中断,反而掩盖真实问题。
实操建议:
- 在自定义错误处理器中,先判断
error_reporting()返回值是否为 0 —— 是则说明被@抑制,直接return false让 PHP 默认机制处理(或忽略) - 只对
$errno & (E_ERROR | E_WARNING | E_USER_ERROR | E_USER_WARNING)这类明确需干预的级别实例化ErrorException - 构造
ErrorException时务必传入第 4、5 参数:new ErrorException($errstr, 0, $errno, $errfile, $errline),否则堆栈位置会丢失 - 不要在错误处理器里做耗时操作(如写文件、发 HTTP 请求),容易拖慢整个请求
日志写入为什么不能只用 error_log()
error_log() 是同步阻塞调用,在高并发下极易成为瓶颈;且无法自动添加上下文(如 $_SERVER['REQUEST_URI']、用户 ID、Trace ID),日志条目之间缺乏关联性。
更务实的做法:
- 用
Monolog\Logger配合StreamHandler(文件) +RotatingFileHandler(按天/大小轮转) - 生产环境禁用
display_errors,但确保log_errors = On且error_log指向独立路径(避免和应用日志混在一起) - 每条日志至少携带:
datetime、level、message、file:line、context(含请求方法、URI、IP) - 敏感字段如密码、token 必须在写入前从
$context中 unset,不能依赖日志库自动过滤
register_shutdown_function 捞致命错误的坑点
这个函数是兜底关键,但容易漏掉两个边界情况:
-
error_get_last()在exit()或fastcgi_finish_request()后可能返回null,需加判空 - 内存溢出(
E_ERRORwith “Allowed memory size exhausted”)发生时,error_get_last()可能不可靠,建议额外检查memory_get_usage() > memory_get_peak_usage()是否异常接近上限 - 多进程环境下用
file_put_contents(..., FILE_APPEND | LOCK_EX)写日志,否则日志行会错乱或覆盖 - 别在 shutdown 函数里再触发新错误(比如尝试读一个不存在的配置文件),否则会陷入无限递归崩溃
真正难的不是把三套机制写出来,而是让它们不互相干扰、不重复记录、不掩盖原始错误位置——尤其是当 set_error_handler 把某个 E_WARNING 转成异常,又被 set_exception_handler 捕获,最后 register_shutdown_function 又扫到同一个错误时,得靠错误码和时间戳去去重。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











