自定义ThinkPHP6日志驱动需实现LogHandlerInterface接口并重写save()方法,接收二维$log数组,补全time、ip、url等字段,返回bool;配置中通过type指定完整命名空间路径,避免构造函数初始化资源。

自定义 ThinkPHP6 日志驱动类,核心是实现 think\contract\LogHandlerInterface 接口,并在配置中正确注册该类。整个过程不依赖中间件或钩子,而是通过替换日志通道的 type 值来生效。
继承接口并实现 save() 方法
TP6 的日志写入由 save() 方法统一触发(注意:不是 write(),也不是 __invoke())。你的驱动类必须实现 LogHandlerInterface,且至少完成 save() 方法逻辑。
- 方法接收一个二维数组
$log,结构为['info' => ['msg1', 'msg2'], 'error' => [...]] - 需遍历每个级别下的每条日志消息,逐条处理(如格式化、转发、入库)
- 方法必须返回
bool,返回false会导致 TP 自动 fallback 到默认 file 驱动 - 避免在方法内做耗时操作(如远程请求未设超时)、长连接复用或内存缓存,每次调用都是独立实例
配置文件中指定自定义驱动
在 config/log.php 的某个 channel 配置中,把 type 改为你驱动类的完整命名空间路径:
- 不要写
'type' => 'File'或'type' => 'file'这类内置简写 - 应写成
'type' => '\app\driver\log\Tp6Log'(末尾无括号,不带.php) - 确保该类能被自动加载(放在
app/下且命名空间与目录结构一致) - 若使用多通道,可为不同 channel 指定不同驱动,例如
'sql'走数据库,'error'走 ELK
补充关键字段提升日志可用性
原生 $log 数组只含 level 和 message,缺少上下文。建议在 save() 中主动补全常用字段:
-
time:用(new DateTime())->format('c')生成 ISO8601 时间 -
ip:request()->ip()获取客户端 IP -
url:request()->url(true)记录完整请求地址 -
method:request()->method() -
context:保留原始$msg的上下文(如异常 trace、用户 ID),扁平化处理避免嵌套过深
注意构造与初始化方式
TP6 不会在日志驱动实例化时传入 App 对象或 Request 对象,所有依赖需在 save() 内按需获取:
- 用
think\facade\Request::instance()或app('request')获取当前请求 - 用
think\App::getInstance()获取 App 实例(如需读取环境配置) - 不要在
__construct()中尝试初始化 cURL 句柄或数据库连接——每次日志写入都可能新建对象,资源无法复用 - 如需远程发送(如 ELK、SLS),务必设置
CURLOPT_TIMEOUT_MS≤ 2500,失败时记录本地 fallback 文件,而非抛异常
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











