ThinkPHP 8.0 日志默认不写入文件,需同时满足开启记录、配置有效通道、请求正常结束(或手动调用Log::save());仅调用Log::info()或Log::record()不会落盘。

ThinkPHP 8.0 的日志默认不写入文件,除非你显式开启记录、配置了有效通道,且请求正常结束(或手动触发 Log::save())。光靠 Log::info() 或 Log::record() 调用,日志只是暂存在内存里,不会落盘。
日志不写入文件的常见原因
最常踩的坑是:以为调用了就写了,结果 runtime/log/ 下空空如也。核心问题通常出在三处:
-
APP_DEBUG为false时,config/log.php中的'level'若为空数组([]),则所有级别日志都会被过滤掉 —— 这比LOG_RECORD=>false更隐蔽 - 未配置
'path',或路径不可写(如runtime/log/目录不存在、权限不足、父目录无执行权限) - 使用了
Log::record()但没调用Log::save(),或在 CLI 命令中未手动保存(Web 请求结束会自动 save,CLI 不会)
配置 log.php 时 level 和 channels 的关键写法
TP8 的日志配置以 config/log.php 为准,不再读取旧版 LOG_LEVEL 常量。正确写法必须明确指定级别数组和通道驱动:
-
'level'必须是字符串数组,例如['info', 'error', 'sql'];写成'INFO,ERROR'字符串会被忽略 -
'channels'中的'file'通道必须显式启用,且'type'值为'File'(首字母大写,小写'file'会静默失败) - 若想把 SQL 单独记到
sql.log,需额外定义一个'sql'通道,并在模型或 Db 类中用Log::channel('sql')->info(...)
示例最小可用配置:
立即学习“PHP免费学习笔记(深入)”;
return [
'default' => 'file',
'level' => ['info', 'error', 'sql'],
'channels' => [
'file' => [
'type' => 'File',
'path' => runtime_path('log/'),
'single' => false,
'max_files' => 30,
],
],
];
CLI 命令中写日志必须手动 save
在定时任务或自定义命令(如 php think demo:task)中,Log::info() 不会自动落盘。因为 CLI 没有“请求结束”生命周期钩子。
- 必须在命令逻辑末尾加
Log::save();,否则日志永远留在内存 - 若命令中途异常退出(未捕获的 Exception),
Log::save()不会执行,已 record 的内容丢失 - 建议包裹在
try...finally中:finally { Log::save(); },确保无论成功失败都写一次
调试时快速验证日志是否生效
别等部署后翻日志文件。本地调试可直接用以下两行代码验证通路:
- 先确认路径可写:
is_writable(runtime_path('log/'))返回true - 再强制写一条并立即保存:
Log::channel('file')->info('test at '.date('Y-m-d H:i:s')); Log::save(); - 立刻检查
runtime/log/2026-05-08.log是否新增一行 —— 注意日期文件名按天滚动,不是固定文件名
真正容易被忽略的是:日志通道名('file')、驱动类名('File')、级别写法(数组非字符串)、CLI 下的 save 时机 —— 这四点错一个,日志就静默消失。



















