ThinkPHP 6 新增自定义日志通道需调用 Log::channel() 动态注册,如 Log::channel('admin', [...]),不可仅修改配置文件;使用时必须链式调用如 Log::channel('api')->error(),避免分两行导致写入 default 通道。

ThinkPHP 6 如何新增自定义日志通道
ThinkPHP 6 默认只配置了 default 和 error 两个日志通道,要按模块(如 admin、api、pay)分离日志,必须手动注册新通道。不改配置文件、不重写日志驱动,直接在 app/common.php 或服务提供者中调用 Log::channel() 注册即可生效。
常见错误是试图在 config/log.php 里直接加数组键但没在 channels 中声明,结果日志仍写入 default;或误以为调用 Log::init() 能动态加载通道——它只初始化已注册的通道,不支持运行时追加。
- 在
app/common.php开头添加:use think\facade\Log; Log::channel('admin', [ 'type' => 'File', 'path' => runtime_path() . 'log/admin/', 'level' => ['info', 'error', 'notice'], 'file_size' => 1024 * 1024 * 5, ]); - 通道名(如
'admin')必须是合法 PHP 标识符,不能含斜杠或点;路径需确保目录可写,否则静默失败 - 同一通道多次调用
Log::channel()不会覆盖,仅首次注册生效
在控制器/模型中正确使用模块日志通道
调用 Log::channel('admin')->info() 是最直接方式,但容易漏写通道名或拼错,尤其在多模块混用时。更稳妥的做法是封装一个模块专属日志门面,比如 AdminLog,避免每次重复写字符串。
注意:ThinkPHP 的 Log 门面是静态代理,Log::channel('xxx') 返回的是独立实例,和 Log::info() 走的不是同一个通道,后者永远走默认通道。
立即学习“PHP免费学习笔记(深入)”;
- 推荐写法:
Log::channel('api')->error('支付回调异常:' . $e->getMessage()); - 错误写法:
Log::channel('api'); Log::error(...);—— 第二行仍写入 default - 若频繁使用,可在模块基类中定义方法:
protected function apiLog() { return Log::channel('api'); },后续调用$this->apiLog()->info(...)
日志文件按日期+模块自动归档的配置要点
ThinkPHP 默认的 File 驱动支持 realtime_write 和 max_files,但模块日志要真正“隔离”,关键在 filename 和 path 的动态构造。硬编码路径会导致所有日志挤在一个文件里,失去模块区分意义。
不要依赖 dateFormat 自动切分——它只控制单个日志文件名里的日期,不解决多模块共用目录的问题。
- 正确设置
path:用runtime_path() . 'log/' . $module . '/',其中$module来自路由或请求上下文 - 启用
apart_level参数(值为['error', 'sql']等)可将特定级别日志单独成文件,但不替代模块隔离 - 若需按天滚动且保留模块前缀,可自定义
filename:'filename' => date('Y-m-d') . '_admin.log',配合max_files控制保留天数
调试阶段如何快速验证日志是否写入目标通道
最常被忽略的是权限和路径问题:开发环境能写,部署到 Linux 后因 runtime/log/xxx 目录不存在或 www 用户无写权限,日志静默丢弃。TP 不报错,只返回 false,导致排查困难。
不要只看日志文件是否存在,要确认内容是否实时更新;也不要只测试 info 级别——如果通道配置了 level 限制,debug 日志会被过滤掉。
- 临时加一行强制写入:
Log::channel('pay')->error('[DEBUG] test write at ' . date('Y-m-d H:i:s'));,然后立刻tail -f runtime/log/pay/*.log - 检查返回值:
if (false === Log::channel('admin')->info('test')) { throw new Exception('admin log write failed'); } - 确认
runtime目录下对应子目录存在且权限为755(Linux),Windows 下注意路径斜杠方向是否一致
模块日志真正的难点不在注册通道,而在于统一入口拦截和上下文注入——比如自动从路由解析出模块名并绑定到当前日志通道。这个逻辑一旦散落在各处,就很难维护。建议在中间件里做一次集中处理,而不是每个控制器都手动选通道。



















