thinkphp日志分片不能只靠log_file配置,因其不支持变量插值;需自定义file驱动重写getlogname()方法,结合日期与模块动态生成路径,并确保目录可写及定时清理旧日志。

ThinkPHP 日志分片为什么不能只靠 log_file 配置
因为 log_file 是静态路径,不支持变量插值或运行时拼接。直接写成 runtime/log/{date}/{module}.log 会被当作文本字面量处理,不会自动替换 {date} 或 {module} —— 这是新手最常卡住的第一步。
ThinkPHP 默认日志驱动(File)本身不解析路径中的业务维度,它只认一个固定文件或按大小轮转。想实现「按日期 + 模块」双维度分目录,必须接管日志写入前的路径生成逻辑。
- 核心要改的是
think\log\driver\File类中getLogName()方法的行为 - 不能直接修改 vendor 里的源码,得通过自定义日志驱动或中间件拦截
- 模块名需从当前请求上下文提取(如控制器类名、路由参数、
Request::module()),不是所有场景都能稳定获取
如何让日志自动落到 runtime/log/20240615/user/ 这样的路径
最稳妥的方式是继承 think\log\driver\File,重写 getLogName(),在其中注入日期和模块逻辑:
class ModuleDateFile extends \think\log\driver\File
{
protected function getLogName()
{
$date = date('Ymd');
$module = app('request')->module() ?: 'common';
$filename = $date . DS . $module . DS . 'debug.log';
return $this->path . $filename;
}
}
然后在 config/log.php 中指定驱动:
'default' => 'moduledate',
'drivers' => [
'moduledate' => [
'type' => ModuleDateFile::class,
'level' => ['error', 'info', 'debug'],
'file' => 'runtime/log/',
],
]
- 注意
$this->path是配置里传入的file值,末尾不带斜杠,所以拼接时要用DS - 务必确保
runtime/log/20240615/user/目录可写,TP 不会自动创建多级子目录 —— 缺少mkdir($dir, 0755, true)就会静默失败 - 如果用 CLI 命令行调用,
app('request')可能为 null,得 fallback 到App::module()或硬编码console
模块名取不准?别依赖 Route::rule() 或控制器注解
模块名不是你想取就能取准的。TP6 的模块概念弱化后,Request::module() 在 API 路由、多应用模式、或者用了 think\swoole 时可能返回空或异常值。
更可靠的方案是:在关键入口(如中间件、BaseController 构造函数)把当前业务域写入全局上下文,再由日志驱动读取:
// 在中间件中
Context::set('log_module', 'order');
// 在自定义 File 驱动里
$module = Context::get('log_module') ?: 'unknown';
- 避免在日志驱动里调用
Loader::parseName()或反射控制器类 —— 性能敏感,且容易触发 autoload 异常导致日志写入中断 - 不要用
__FILE__或debug_backtrace()动态推导模块,既慢又不可控 - 如果项目已用多应用(
app/multi/),优先取app('http')->getName(),它比Request::module()更贴近实际应用边界
按日期分目录后,旧日志自动清理怎么做
TP 自带的 max_files 只管文件数量,不管目录层级。放任不管,几个月后会出现几百个日期子目录,每个里面一堆 debug_*.log,磁盘迟早爆。
推荐用命令行定时清理,而不是在日志写入时判断:
php think clear:log --days=30
这个命令需要自己实现,核心逻辑是遍历 runtime/log/* 目录,检查文件夹名是否为有效日期格式(/^\d{8}$/),再比对是否早于 30 天:
- 别用
scandir()直接列所有子目录 —— 如果有人手建了runtime/log/temp/这种非日期目录,会误删 - 删除前先
is_dir()+preg_match('/^\d{8}$/', $basename)双重校验 - 清理脚本加
ob_end_flush()或日志记录,否则 cron 执行失败你根本不知道
路径生成、模块识别、目录清理,三件事看着独立,其实环环相扣。最容易漏的是目录自动创建和清理脚本的健壮性 —— 它们不出错时没人关注,一出就是磁盘满或日志全丢。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











