thinkphp日志级别分流不能只靠level配置,因其file驱动仅过滤级别而不分流文件;必须用多通道+白名单+自定义驱动或闭包处理器,为error/info/sql设独立channel并禁用默认通道。

ThinkPHP 日志级别分流为什么不能只靠 level 配置
因为 ThinkPHP 默认的 File 日志驱动不支持“同一通道内按级别写入不同文件”,level 只控制“哪些级别允许写入该通道”,不是路由规则。你设了 'level' => ['error', 'notice'],它只是过滤掉 warning 以下日志,但所有符合条件的日志仍会进同一个文件。
- 真正分流必须靠「多通道 + 级别白名单 + 自定义驱动或闭包处理器」
- 官方
Channel机制是入口,但默认File驱动不支持动态文件名,得自己扩展或换用Rotate类驱动 - 如果直接在
log.php里给多个通道配相同path,会引发并发写冲突,尤其高流量下日志错乱
如何配置 error / info / sql 三通道并确保互不干扰
核心是为每个级别建独立 channel,显式指定 path 和 level,并禁用全局通道的冗余写入。
- 在
config/log.php中定义三个通道:'error'、'info'、'sql',每个都设'type' => 'file'和唯一'path'(如runtime/log/error/) - 关闭默认通道:把
'default'改成一个不存在的名称(如'none'),或设'level' => []彻底禁用 - SQL 日志需额外钩子:在数据库配置中启用
'trigger_sql' => true,再通过Db::listen()把 SQL 记录到Log::channel('sql')->info() - 注意
path目录需手动创建并赋予写权限,TP 不自动递归建多级目录
自定义日志驱动时绕不开的 write 方法陷阱
如果你继承 think\log\driver\File 写自定义驱动,write 方法里直接拼接文件名很容易出问题——TP 会在每次调用前做一次 getLogName(),但默认实现不感知日志级别。
- 必须重写
getLogName(),根据传入的$level返回不同文件名,比如"{$level}_".date('Y-m-d').".log" - 不要在
write()里用file_put_contents追加,要用fopen(..., 'a')+flock,否则并发时会丢日志 - TP 6.1+ 对
write的第三个参数$save(布尔值)有强依赖,它决定是否真正落盘;自定义驱动里漏判这个值,会导致日志静默丢失 - 测试时用
Log::channel('xxx')->error('test')单独触发,别依赖中间件或控制器生命周期,避免被缓存或未初始化拦截
路由日志通道的实际生效点不在配置文件里
所谓“路由日志通道”,本质是运行时根据请求路径或路由标识动态切换 Log::channel(),和配置文件里的静态通道定义无关。它不靠配置项驱动,靠代码分支。
- 在中间件(如
AppInit或自定义日志中间件)里判断Request::routeInfo(),匹配'admin/'前缀就切到Log::channel('admin') - 不能在
config/log.php里写类似'route_map'这种伪配置,TP 没这个机制,写了也无效 - 若想按模块分流,更稳妥的方式是各模块控制器构造函数里主动指定通道:
$this->log = Log::channel('api');,避免全局中间件误判 - 注意
Log::channel()是单例模式,一旦调用过某个 channel,后续同名调用返回的是同一个实例,不会重复初始化
最易被忽略的一点:日志通道的 level 是写入时校验的,不是通道初始化时校验的。也就是说,即使你给 sql 通道配了 ['sql'],但用 Log::channel('sql')->info() 依然能写进去——因为 info 不在白名单里,TP 直接跳过写入,不报错也不提示。得靠日志内容搜不到来反推配置失效。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!










