set_error_handler无法捕获致命错误,应使用register_shutdown_function配合error_get_last();mail()阻塞且无上下文,error_log(...,1,$to)更轻量但依赖sendmail_path配置;高并发下推荐phpmailer异步发送并限流。

set_error_handler 里直接调 mail() 会丢致命错误
PHP 的 set_error_handler 只能捕获非致命错误(E_WARNING、E_NOTICE 等),E_ERROR、E_PARSE 这类会中断脚本执行的错误根本进不了你的函数。所以别指望靠它“兜住所有错误发邮件”。
- 真正能覆盖致命错误的,只有
register_shutdown_function配合error_get_last() -
mail()是阻塞同步调用,若 MTA 响应慢或超时,整个请求会被拖住,可能触发 Nginx/Apache 超时 - 错误上下文(如堆栈、变量 dump)必须手动收集,
mail()默认不带这些,容易漏关键诊断信息
error_log(..., 1, $to) 比 mail() 更轻量但限制多
用 error_log($message, 1, $to) 发邮件,本质是让 PHP 调系统 sendmail,不走 PHP 的 mail() 函数逻辑,启动快、无扩展依赖。
- 前提是 php.ini 中
sendmail_path必须指向真实可用的 MTA,例如sendmail_path = "/usr/sbin/sendmail -t -i" - 第四个参数
$headers必须包含合法From:,否则多数 MTA 拒绝投递;\r\n换行符不能写成\n - 它只发纯文本,无法带附件、HTML 或 UTF-8 主题,且收不到发送失败反馈(成功与否全看系统日志)
- 不推荐在高并发场景下高频调用,因为每次都会 fork 进程跑 sendmail,压测时容易打满系统 fork 数
用 PHPMailer 异步发邮件要绕开主线程阻塞
想发带堆栈、HTML 格式、支持 SMTP 认证的告警邮件,PHPMailer 是更可靠的选择,但它不能直接塞进 set_error_handler 里同步调用。
- 必须把错误数据序列化后写入临时文件或 Redis 队列,再由独立的守护进程(如 cron 或 supervisor 管理的 worker)消费并发送
- 若硬要在错误处理中发,至少要用
proc_open()启动后台进程,避免阻塞当前请求;但要注意子进程权限、超时和孤儿进程清理 - SMTP 密码等敏感信息绝不能硬编码在错误处理器里,应从环境变量或配置中心读取,并确保 error handler 文件本身不被 Web 直接访问
- 务必限制单次错误最多发几封(比如 5 分钟内同错误类型只发 1 封),否则数据库崩了可能瞬间触发几百封告警邮件
最容易被忽略的三个生产陷阱
很多线上告警邮件没收到,不是代码写错,而是卡在基础设施层。
- MTA 服务没跑起来,或者
/usr/sbin/sendmail是个空链接(常见于 Alpine 容器镜像) - Web 用户(如
www-data)没有执行 sendmail 的权限,SELinux 或 AppArmor 策略拦截了 fork - 邮件头里
From域名没配 SPF/DKIM,被 Gmail 或 QQ 邮箱直接归为垃圾邮件,连日志都看不到退信记录
真要验证是否通路,别只测 mail() 函数返回 true,得去收件箱查,再查 MTA 的 maillog(如 /var/log/mail.log)确认有无 “sent” 或 “deferred” 记录。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











