error_log() 默认写入 php 配置的 error_log 路径而非自定义文件,仅当显式传入文件路径或使用模式 3/4 时才改变行为;自定义日志需处理并发、换行、权限、轮转及监控对接。

error_log() 写日志时为什么没出现在预期文件里
默认情况下 error_log() 不写入自定义文件,而是走 PHP 的错误日志配置路径(error_log ini 指令值),常被误以为“不生效”。它只在参数明确指定文件路径时才绕过全局配置。
- 不带参数或传
false:写入error_logini 设置的路径(如 Apache 的error.log) - 传字符串路径(如
'/var/log/myapp.log'):直接写入该文件,但需确保 PHP 进程有写权限且目录存在 - 传
3(整数):等价于error_log($msg, 3, $file),是写文件的显式模式 - 常见坑:路径用相对路径(如
'logs/app.log'),实际写到 webserver 工作目录(未必是项目根目录)
用 file_put_contents() 自建日志函数要注意什么
比 error_log() 更可控,但得自己处理并发、格式、换行和权限。PHP 默认不保证文件写入原子性,高并发下易丢日志或内容错乱。
- 必须加
FILE_APPEND | LOCK_EX标志:file_put_contents($file, $line, FILE_APPEND | LOCK_EX) - 每条日志末尾手动加
\n,否则所有内容挤在一行 - 避免频繁打开/关闭文件:不要在循环里反复调用;单次请求多次写入可接受,但高频服务建议用缓冲+定时刷盘或切到 syslog
- 注意时区:
date('Y-m-d H:i:s')依赖date_default_timezone_set(),否则可能用 UTC 时间,和系统日志对不上
自定义日志类里要不要支持 rotate(日志轮转)
线上环境必须支持,否则单个日志文件会无限增长,直到磁盘写满或触发运维告警。PHP 本身不提供自动 rotate,得自己判断并归档。
- 简单方案:每天检查
filemtime($log_file),若跨天则重命名成app.log.2024-05-20,再新建空文件 - 更稳做法:用
is_file($log_file) && filesize($log_file) > 10 * 1024 * 1024判断是否超 10MB,再 rotate - 别用
rename()直接覆盖旧归档名——如果同名已存在,会被覆盖丢失;应先if (is_file($backup)) { unlink($backup); } - rotate 操作本身要尽量轻量,避免在每次写日志时都检查,可设为“首次写入当天第一条日志时检查”
监控场景下 error_log() 和自定义类哪个更适合
监控需要稳定、可采集、低侵入——error_log() 写 syslog(error_log($msg, 4))最省事;自定义类适合结构化日志(如 JSON),但得额外对接日志采集器。
立即学习“PHP免费学习笔记(深入)”;
-
error_log($msg, 4)把日志发给系统 syslog,Linux 下自动进/var/log/messages或/var/log/syslog,Logstash/Filebeat 天然能抓 - 自定义类输出 JSON 格式(如
['level'=>'warn','msg'=>'timeout','time'=>...]),方便 ELK 做字段提取,但需确保无非法字符(如未转义的双引号) - 避免在日志里拼接敏感数据(如
$_SERVER['HTTP_AUTHORIZATION']),监控系统可能长期留存,合规风险高 - 如果用 Monolog 等第三方库,注意其默认 handler 在 CLI 和 FPM 下行为不同(比如
StreamHandler可能复用 fd,FPM worker 复用时出问题)
日志路径权限、并发写入、rotate 时机、输出格式与监控链路的衔接——这几个点不提前想清楚,上线后查问题反而更费劲。











