error_log() 不支持内置日志级别,需借助 trigger_error + error_reporting 实现真·级别路由;直接写文件无锁易丢日志,推荐 file_put_contents(..., FILE_APPEND | LOCK_EX) 或专业组件。

error_log() 本身不支持日志级别控制
别被名字误导——error_log() 函数压根没有内置的 INFO、WARNING 或 ERROR 级别参数。它只管“发出去”,不管“是什么级别”。所谓“分级别记录”,靠的是组合其他机制实现的。
常见错误现象:写了一堆 error_log("xxx", 3, $path),结果所有日志混在一起,没法按严重程度过滤或告警。
- 想区分级别,必须手动拼接前缀,比如
"[" . date('Y-m-d H:i:s') . "] [ERROR] 用户支付超时\n" - 若用
message_type = 0(默认),日志走 PHP 系统日志处理器,此时实际级别由error_reporting和触发方式共同决定(比如trigger_error("msg", E_USER_ERROR)才会被识别为错误级) -
message_type = 3写文件时,完全不经过 PHP 错误级别判断,哪怕你写error_log("[INFO] 启动完成", 3, $path),它也只是一个普通字符串
用 trigger_error + error_reporting 实现真·级别路由
这是唯一能让日志自动归类进 error_log 配置路径、并受 error_reporting 控制的方式。核心是让 PHP “认为”这条日志属于某个错误类型。
使用场景:需要把调试轨迹、业务异常统一收口到主错误日志文件,且能通过配置开关某类提示(比如关掉 E_USER_NOTICE 不影响 E_USER_WARNING)。
立即学习“PHP免费学习笔记(深入)”;
- 在代码中调用
trigger_error("数据库查询慢: 1200ms", E_USER_WARNING) - 确保
php.ini中log_errors = On,error_log指向有效路径,且error_reporting包含E_USER_WARNING(例如E_ALL或E_ALL & ~E_NOTICE) - 不要用
error_log()直接写文件去模拟——它绕过了整个错误级别体系,error_reporting对它完全无效
写文件时并发丢日志是必然问题,不是概率问题
多个 PHP-FPM worker 同时执行 error_log($msg, 3, $path),大概率出现内容截断、乱序甚至覆盖。这不是环境配置问题,是底层 write() 系统调用无锁导致的。
容易踩的坑:加 sleep(0.001) 或以为“流量小就没事”,上线后 QPS 上升立刻暴露。
- 绝对路径必须提前验证:
is_writable(dirname($path))要返回true,否则静默失败 - 换行符必须手动加
\n,否则多条日志挤成一行;用\r\n可能被某些分析工具误判,推荐统一用\n - 真实可用的轻量替代:用
file_put_contents($path, $msg . "\n", FILE_APPEND | LOCK_EX),LOCK_EX保证原子写入(PHP 5.6+ 支持)
线上环境 error_log 配置不生效?先确认运行模式和配置加载路径
error_log = /var/log/php/error.log 这行配置,在 CLI 模式下基本没用——CLI 默认把日志打到终端,除非你显式开启 log_errors = On 并指定路径;Web 模式(如 FPM)才真正走文件写入。
性能影响:频繁调用 error_log(..., 3, ...) 会引发大量小 IO,尤其在高并发下拖慢响应。日志量大时建议异步或批量落盘。
- 查 CLI 加载的配置:运行
php --ini - 查 Web 加载的配置:在 PHP 页面里调用
phpinfo(),搜索 “Loaded Configuration File” - 敏感字段必须脱敏:
error_log()不做任何过滤,密码、token、手机号等要提前str_replace或substr处理 - 日志轮转不能依赖
error_log()自身——它不支持切割、压缩、过期删除,必须配logrotate或用 Monolog 等专业组件
真正稳定的线上日志方案,从来不是靠硬刚 error_log() 的缺陷,而是明确它只适合临时调试或极简脚本。一旦项目有持续维护需求,封装 file_put_contents 带锁写入,或直接上 Monolog,省下的排查时间远超初期多写的几行代码。



















