ThinkPHP6.1可通过继承think\log\driver\File并重写format()方法实现结构化日志输出,支持上下文注入、敏感信息脱敏及JSON格式化,无需修改框架源码。

ThinkPHP6.1 的日志格式化模板不能像 Laravel 那样直接在配置里写 formatter 类,但可通过自定义日志驱动或重写内置 think\log\driver\File 的行为来实现结构化、可读性强、带上下文的日志输出。关键不是“换模板”,而是控制日志内容的组装逻辑和字段呈现方式。
日志格式由日志处理器决定
TP6.1 默认使用 think\log\driver\File,其 write() 方法内部调用 $this->format($message, $type, $file, $line) 生成最终写入磁盘的字符串。这个 format() 方法是可重写的入口。
你不需要修改框架源码,只需继承该驱动并覆盖 format():
// app/log/driver/CustomFile.php
namespace app\log\driver;
use think\log\driver\File as BaseFile;
class File extends BaseFile
{
protected function format($message, $type, $file, $line)
{
$time = date('Y-m-d H:i:s', time());
$ip = request()?->ip() ?? 'CLI';
$url = request()?->url() ?? '';
$method = request()?->method() ?? '';
// 脱敏处理(如密码、token字段)
$safeMessage = is_string($message)
? $this->redactSensitive($message)
: json_encode($message, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
return sprintf(
"[%s] %s [%s] %s %s %s — %s\n",
$time,
strtoupper($type),
$ip,
$method,
$url,
$file . ':' . $line,
$safeMessage
);
}
private function redactSensitive($content)
{
if (!is_string($content)) return $content;
return preg_replace_callback(
'/("password"|\'password\'|:.*?(?:"|\')\s*:\s*["\'])([^"\']*)["\']/i',
fn($m) => $m[1] . '[REDACTED]' . $m[3],
$content
);
}
}然后在 config/log.php 中切换驱动:
立即学习“PHP免费学习笔记(深入)”;
'default' => 'custom',
'channels' => [
'custom' => [
'type' => \app\log\driver\File::class,
'path' => runtime_path('log/'),
'level' => 'debug',
],
],按场景补充结构化字段
单纯改 format() 还不够,真正提升可查性的是把请求、用户、资源等上下文注入日志消息本身。推荐在中间件中统一增强:
// app/middleware/LogContext.php
public function handle($request, \Closure $next)
{
$context = [
'request_id' => $request->header('x-request-id', uniqid('req_', true)),
'method' => $request->method(),
'url' => $request->url(),
'ip' => $request->ip(),
'user_id' => $request->session()->get('user.id', '-'),
];
// 将上下文绑定到日志通道(需配合 Log::channel()->withContext())
\think\Log::channel('custom')->withContext($context);
return $next($request);
}注意:withContext() 是 TP6.1.10+ 支持的特性,旧版需自行封装 Log::record() 并拼接数组。
输出 JSON 格式日志(适配 ELK/Sentry)
若对接日志平台,建议直接输出 JSON 行格式(每条日志一行 JSON),便于采集解析:
protected function format($message, $type, $file, $line)
{
$log = [
'timestamp' => date('c'),
'level' => strtoupper($type),
'message' => is_string($message) ? $message : (array) $message,
'file' => basename($file),
'line' => $line,
'context' => \think\Log::getContext(), // 获取中间件注入的上下文
];
return json_encode($log, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n";
}这样每行都是标准 JSON,Logstash 或 Filebeat 可直接 parse。
不复杂但容易忽略



















