set_Error_handler仅捕获e_warning、e_notice、e_user_error等可恢复错误,不处理e_error、e_parse、e_core_error及未捕获异常;php 7+的typeerror等属于异常,由set_exception_handler处理。

set_error_handler 能捕获哪些错误
set_error_handler 只能捕获 PHP 的 E_WARNING、E_NOTICE、E_USER_ERROR 这类「可恢复」的运行时错误,**不处理 E_ERROR(如语法错误、致命错误)、E_PARSE、E_CORE_ERROR 或未捕获的异常**。你写错 function 拼写或漏掉分号,根本进不了这个函数——那是解析阶段的事,set_error_handler 压根没机会执行。
常见误判场景:
• 本地开发开了 error_reporting(E_ALL),但生产环境关了部分级别,结果线上收不到通知
• 把 file_get_contents('http://...') 失败当成普通警告来处理,其实它触发的是 E_WARNING,能被捕获,但返回值是 false,得自己判断
- 必须在错误发生前调用
set_error_handler,比如放在入口文件最顶部 - 如果启用了
display_errors = On,默认错误仍会输出到页面,需在 handler 里返回true才能屏蔽原生输出 - 想同时记录日志又不想中断流程,handler 函数末尾别
return false(这是默认行为),也别throw新异常,除非你真想把它转成异常
自定义 handler 函数的参数和返回值含义
标准签名是 function($errno, $errstr, $errfile, $errline, $errcontext)。其中 $errcontext 容易被忽略:它是错误发生时当前作用域的变量快照(类似 get_defined_vars()),但**默认不开启**——必须在 set_error_handler 第二个参数传 E_ALL 或对应掩码,且 PHP 配置中 zend.ze1_compatibility_mode 关闭(现代版本默认关闭)才有效。
返回值决定后续流程:
• 返回 true:表示你已处理,PHP 不再执行默认错误处理逻辑(比如打印到屏幕)
• 返回 false 或不返回:交还给 PHP 默认机制,可能继续报错甚至终止脚本
立即学习“PHP免费学习笔记(深入)”;
-
$errno是整数,建议用error_level_to_name($errno)转成字符串(自己写个映射数组就行) -
$errstr可能含敏感路径,上线前记得过滤$errfile中的绝对路径,避免泄露 - 不要在 handler 里调用
trigger_error或抛出异常,容易引发递归调用崩溃
与 set_exception_handler 混用时的注意事项
两者互不干扰,但实际项目里常一起用:set_exception_handler 捕获 throw 和未 catch 的异常,set_error_handler 捕获警告/通知。问题在于:**PHP 7+ 中类型错误(TypeError)、ParseError 等现在是异常而非错误,不会进 set_error_handler**。
典型踩坑:
• 写了个 function foo(int $x) { },传了字符串,PHP 7.0+ 报 Fatal error: Uncaught TypeError —— 这属于 set_exception_handler 范畴
• 用 @ 抑制错误时,set_error_handler 直接被跳过,连函数都不会调用
- 若要统一处理,可在
set_exception_handler里判断异常类型,对Error子类(如TypeError)做特殊记录 -
register_shutdown_function可补位捕获致命错误(如E_ERROR),但无法获取完整上下文,仅适合记录“脚本意外终止” - 别在 handler 或 exception handler 里做耗时操作(如写文件、发 HTTP 请求),可能拖慢整个请求
生产环境日志落地的最小可行方案
直接写文件最简单,但要注意并发和权限:fopen($log_path, 'a') 在高并发下可能丢日志,file_put_contents($log_path, $msg, FILE_APPEND | LOCK_EX) 更稳妥。关键是格式得让人能快速定位问题。
推荐一行一条结构化日志,包含时间、错误等级、消息、文件行号、请求 URI(如果有):
2024-05-20 14:22:31 [WARNING] array_merge(): Argument #2 is not an array in /var/www/app/helpers.php on line 87 [uri:/api/v1/users]
- 用
date('Y-m-d H:i:s')而不是microtime(true),排查时对齐时间更直观 - 记录
$_SERVER['REQUEST_URI']前先检查是否存在,CLI 脚本里这变量不存在 - 日志文件路径别写死,从配置读取,并确保 web 用户有写权限(比如
chown www-data:www-data /var/log/myapp/) - 单文件别超过 100MB,用
logrotate或代码里判断大小后归档
真正难的不是写 handler,而是怎么让日志里带上下文又不泄露用户数据——比如 $errcontext 里的 $_POST 或数据库密码字段,得提前过滤。