ThinkPHP日志未写入、堆栈不全、SQL错误难定位,根本原因是APP_DEBUG、LOG_RECORD和runtime/log写权限三者缺一不可;LOG_RECORD默认关闭,需显式启用;异常堆栈丢失多因ExceptionHandle未调用parent::render;SQL错误应通过Db::listen或catch块中Db::getLastSql()捕获,而非依赖失败前的getLastSql()。

日志没写进去、异常堆栈看不全、SQL错在哪——不是配置漏了,就是执行时机不对。ThinkPHP的日志和异常追踪必须同时满足三个条件:APP_DEBUG开启、log channel允许该级别、runtime/log有写权限。
APP_DEBUG 和 LOG_RECORD 必须同时为 true
只开 APP_DEBUG 不等于日志能写进去。LOG_RECORD 是独立开关,控制日志是否真正落盘。ThinkPHP 6+ 中它默认关闭,哪怕 APP_DEBUG = true,Log::debug() 也静默丢弃。
- 确认
public/index.php开头第一行是define('APP_DEBUG', true);,且前面无空格/BOM/输出 - 在
config/app.php中显式加:'LOG_RECORD' => true - 检查
config/log.php的默认 channel(如'file')中'level'是否包含'debug'或'error' -
runtime/log/目录需存在且 PHP 进程有写权限(Linux 下常见权限不足导致日志文件为空)
错误堆栈不完整?检查 ExceptionHandle 的 render 方法
框架抛出的异常被 app/ExceptionHandle.php 拦截后,如果 render() 方法里没调用父类逻辑或手动记录,原始堆栈就丢了。尤其自定义了 JSON 错误响应时,容易漏掉 trace 信息。
- 确保
render()方法中至少有一句:return parent::render($e); - 若需额外记录,应在
render()开头加:Log::error('Uncaught exception: ' . $e->getMessage(), ['trace' => $e->getTraceAsString()]); - 不要在
render()里直接echo或exit,会中断框架异常流程,导致 Trace 面板不显示
SQL 错误定位:别只信 getLastSql()
Db::getLastSql() 返回的是上一条「成功执行」的 SQL;遇到语法错误、字段不存在(如 MySQL 1054)时,它可能还是旧语句,甚至为空。真要抓错的那条 SQL,得用监听。
立即学习“PHP免费学习笔记(深入)”;
- 临时调试:在出问题的方法开头加
Db::listen(function ($sql, $time, $explain) { dump($sql); }); - 永久生效:在
config/database.php的连接配置里设'trace' => true,再配合Db::getLastSql()放在catch块里 - 警惕
->fetchSql(true):它只生成 SQL 字符串,不执行,参数绑定是否生效无法验证,容易误判 - MySQL 1054 错误在 ThinkPHP 中常因字段名写成驼峰(如
userName)但数据库是下划线(user_name)且未配auto_write_timestamp或type_cast
Trace 面板不显示?优先查模板和缓存
页面底部没 Trace 浮层,90% 不是配置问题,而是输出被截断或模板没加载。Trace 是通过在视图末尾注入 HTML 实现的,任何提前 exit、ob_end_flush() 或模板中 {__NOCACHE__} 都会让它消失。
- 确认
config/app.php中'trace' => ['type' => 'html'](非console) - 清空
runtime/view/和runtime/cache/,避免旧编译模板干扰 - 检查控制器是否用了
return json(...)或response()->code(200)->send()—— 这些跳过视图渲染,Trace 自然不出现 - 若用 Nginx,确认没开启
fastcgi_buffering off;类似设置,否则 HTML 尾部可能被缓冲丢弃
最易忽略的一点:日志文件按日期切分,但 runtime/log/ 下看不到当天的 error.log,往往是因为系统时间比服务器时间快,导致日志写进了“未来”的文件名里——检查服务器 date 命令输出是否与当前真实时间一致。



















