ThinkPHP 6 日志格式混乱需自定义 JsonFormatter 类替代默认 Simple 格式化器,重写 format() 方法统一处理时间、context 和异常,并通过 Log::init() 或通道配置注入,确保每行合法 JSON。

日志内容格式乱是因为默认的 ThinkLog 格式化逻辑太松散
ThinkPHP 6 默认用 think\log\driver\File 驱动,底层调用 think\log\formatter\Simple,它只做基础字段拼接,不控制字段顺序、缩进、JSON 结构或时间精度。结果就是:不同模块打的日志字段不一致,context 里嵌套数组变 Array 字符串,错误堆栈被截断,甚至中文乱码。
真正要统一格式,不能靠配置文件开关,得替换掉默认的 formatter 类。
- 别试图在
config/log.php里改format字符串模板——它只影响单行文本,对context和异常对象无效 - 别直接修改 vendor 里的
Simple.php——升级后立刻丢失 - 必须新建自定义 formatter 类,并在日志驱动初始化时显式注入
怎么写一个可复用的 JSON 格式化类
核心是继承 think\log\formatter\Formatter,重写 format() 方法,把原始 $record(数组)转成结构清晰、字段固定的 JSON 字符串。
关键点在于处理三类数据:
立即学习“PHP免费学习笔记(深入)”;
-
datetime:用$record['datetime']->format('Y-m-d H:i:s.u')保留微秒,避免时区错位 -
context:必须用json_encode($context, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),否则中文变 \uXXXX,斜杠被转义 -
exception:不能直接 toString(),要用$e->getTraceAsString()或更稳妥的think\helper\Debug::getThrowableInfo($e)(TP6.1+)
示例精简版:
namespace app\common\log;
use think\log\formatter\Formatter;
class JsonFormatter extends Formatter
{
public function format(array $record): string
{
$record['datetime'] = $record['datetime']->format('Y-m-d H:i:s.u');
if (isset($record['context']) && is_array($record['context'])) {
$record['context'] = json_encode($record['context'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
if (isset($record['exception']) && $record['exception'] instanceof \Throwable) {
$record['exception'] = $record['exception']->getMessage() . ' in ' . $record['exception']->getFile() . ':' . $record['exception']->getLine();
}
return json_encode($record, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n";
}
}
如何让 TP6 日志驱动用上你的 formatter
不是改 config,也不是 bind 到容器,而是要在创建日志实例时传进去——因为 File 驱动的构造函数支持 $formatter 参数。
有两种可靠方式:
- 在
app/common/bootstrap.php或app/provider.php中,用think\facade\Log::init()重新初始化驱动,并传入新 formatter 实例 - 如果用了多通道(如 error / sql / debug 分开),就在每个通道的配置里加
'formatter' => new \app\common\log\JsonFormatter()
注意:TP6.0 的 Log::init() 不接受 formatter 参数,必须升到 6.1+;若无法升级,就只能重写 File 驱动子类,在其 __construct() 里硬编码 formatter。
容易被忽略的兼容性坑
格式统一了,但日志文件本身可能出问题:
- Windows 下换行符是
\r\n,但json_encode输出带\n,混用会导致 tail -f 看日志时换行错乱——统一用"\n"并确保文件以 LF 保存 - 大并发下多个进程同时写一个日志文件,JSON 每行一条没问题,但千万别在 formatter 里加
file_put_contents(..., FILE_APPEND)——那是驱动该干的事 - 如果你开了日志压缩(
rotate),确认JsonFormatter输出的每行都是合法 JSON,否则解压后用 jq 解析会失败
最麻烦的是历史日志迁移:旧日志还是混乱格式,新日志是标准 JSON,查问题时得切两套解析逻辑——这事没法自动解决,得提前和运维对齐归档策略。



















