php写json日志应每条独立成行、用flock保证原子性、避免error_log前缀污染、预检数据可序列化并捕获jsonexception,否则易致日志错位、撕裂、截断或崩溃。

PHP写JSON日志时,别直接file_put_contents拼字符串
直接用file_put_contents把json_encode()结果连同时间、上下文硬拼成字符串,看着快,但很快会出问题:JSON字段含换行或双引号时日志错位,多进程写入时内容撕裂,查问题时根本分不清哪段是哪个请求的。
真正可靠的做法是每条日志独立成行、结构清晰、可被后续工具(比如jq或ELK)解析:
- 每次只写一条完整JSON对象,末尾加换行(
\n),不加逗号,不包数组 - 用
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES避免日志里全是\uXXXX和多余反斜杠 - 写入前加
flock($fp, LOCK_EX),尤其在CLI多进程或FPM高并发场景下
$log_entry = [
'time' => date('c'),
'level' => 'info',
'message' => $msg,
'context' => $context
];
$fp = fopen('/var/log/app.json.log', 'a');
flock($fp, LOCK_EX);
fwrite($fp, json_encode($log_entry, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n");
flock($fp, LOCK_UN);
fclose($fp);
用error_log写JSON日志?小心格式污染和截断
error_log默认会在内容前后自动加时间戳、脚本路径、冒号空格,比如:[21-May-2024 14:23:05 UTC] {"msg":"ok"}。这会让整行不再是合法JSON,jq -r '.msg'直接报错。
更麻烦的是,PHP对error_log单次写入长度有限制(通常约1024字节),超长JSON会被静默截断,且无提示。
立即学习“PHP免费学习笔记(深入)”;
- 若坚持用
error_log,必须配合ini_set('log_errors_max_len', 0)(0表示不限长) - 但依然无法去掉自动添加的前缀——得改用自定义
error_log目标为文件句柄,并关闭log_errors,否则双写 - 推荐绕过
error_log,直接走fopen+fwrite,控制权完全在自己手里
日志文件爆炸?注意json_encode的循环引用和资源泄漏
把$GLOBALS、$this、PDO连接对象甚至cURL句柄直接塞进日志上下文,json_encode会卡住、返回false,或者无限递归导致内存溢出——而你只看到空日志或PHP崩溃。
这不是配置问题,是数据本身不可序列化。
- 写日志前先用
is_array()或is_object()判断,再用json_last_error() === JSON_ERROR_NONE校验编码结果 - 对对象,优先调用其
__toString()或显式提取->id、->name等字段,别var_export整个实例 - 敏感字段如
password、token必须在写入前unset,别指望“日志脱敏系统”后期处理
PHP 8.1+ 的JsonException不是摆设,得真捕获
旧代码习惯用if (json_encode($data) === false)判断失败,但在PHP 8.1+,如果传入了不可序列化的资源(如mysqli连接),json_encode会抛出JsonException而非返回false——没加try/catch就直接崩掉。
- 必须用
try { json_encode($data); } catch (JsonException $e) { /* 记原始类型+报错信息 */ } - 别只捕获
Exception,JsonException是独立子类,漏掉就等于没写 - 开发期开
display_errors=On看不出问题,生产环境log_errors=On才暴露,所以测试时要主动传资源变量触发异常
JSON日志真正的难点不在语法,而在数据来源是否干净、写入过程是否原子、错误分支是否覆盖全——少一个环节,查问题时就得多花三小时翻原始文件找断行点。










