ThinkPHP需配置log.php的level包含error/notice/alert、file路径为绝对路径、single设为false,并在入口注册register_shutdown_function捕获Fatal Error;同时将flush_interval设为1确保日志实时写入。

如何让ThinkPHP自动记录完整错误日志到指定文件
默认情况下,ThinkPHP在APP_DEBUG = false时只写runtime/log/下的日志,且不包含堆栈、请求参数等关键信息——这会导致线上问题排查困难。
必须手动配置日志驱动和级别:
- 在
config/log.php中设置'default' => 'file',并确保'level' => ['error', 'notice', 'alert'](不能只留'error',否则trigger_error()类警告不会被捕获) -
'file' => ['path' => '/var/log/thinkphp/']要指向有写权限的绝对路径,相对路径在CLI模式下容易错位 - 务必关闭
'single' => true(默认为false),否则每天日志会覆盖,丢掉异常时间线
捕获未被try-catch兜住的致命错误(Fatal Error)
ThinkPHP内置的think\exception\Handle对Fatal error无能为力,因为PHP在解析或执行阶段崩溃时根本进不了框架生命周期。
需在入口文件public/index.php最顶部注册底层处理器:
立即学习“PHP免费学习笔记(深入)”;
register_shutdown_function(function () {
if ($error = error_get_last()) {
if (in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
$log = date('Y-m-d H:i:s') . " FATAL: {$error['message']} in {$error['file']}:{$error['line']}\n";
file_put_contents('/var/log/thinkphp/fatal.log', $log, FILE_APPEND | LOCK_EX);
}
}
});
注意:error_get_last()只能拿到最后一次错误,所以这个钩子必须放在所有其他register_shutdown_function之前;另外E_ERROR不包含E_WARNING,别误以为它能捕获所有报错。
异常触发企业微信/钉钉告警的最小可行配置
不要直接在AppExceptionHandle里发HTTP请求——框架尚未完成初始化,Config、Log可能不可用,且阻塞响应。
推荐用异步+队列方式落地:
- 在
app/exception/Handle.php的report()方法里,只做轻量动作:queue_push('alert', ['msg' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine()]) - 单独写一个
command/AlertSend命令,用curl或file_get_contents调用钉钉机器人Webhook,超时设为3s,失败自动重试最多2次 - 避免在
render()里发告警——那是渲染错误页面用的,用户还没看到页面就收到告警,体验奇怪
为什么Log::error()有时没写进文件,但error_log()却有
这是ThinkPHP日志缓冲机制导致的:默认启用'flush_interval' => 10,即每10条日志才刷一次磁盘。如果进程意外退出(如超时kill),最后几条就丢了。
线上环境建议:
- 把
'flush_interval' => 1,牺牲一点性能换日志完整性 - 禁用
'json' => true(默认false),JSON格式在日志切割或grep时反而难处理 - 确认
opcache.revalidate_freq不为0,否则修改了log.php配置也不会生效
监控不是配完就完事,真正难的是让每一条Fatal error都留下痕迹,而不是靠重启后看有没有新日志来反推问题。


















