ThinkPHP数据库连接失败时不记录think\exception\PDOException,因日志系统初始化晚于连接建立;需提前初始化日志、重写connect()方法捕获并记录原始PDOException,且注意路径权限与递归调用风险。

连接失败时 think\exception\PDOException 不被记录?
默认情况下,ThinkPHP 在数据库连接失败时抛出 think\exception\PDOException,但这个异常不会自动写入日志——因为连接建立发生在日志系统初始化之后。日志驱动依赖数据库配置,而配置又依赖数据库连接,形成死循环。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 在
app/extra/database.php中禁用'deploy' => 0(避免多库切换加重初始化负担) - 将数据库配置从
database.php拆出,改用环境变量(如env('DB_HOST')),确保配置层不触发连接 - 手动在
app/common.php或app/provider.php中提前初始化日志驱动,例如调用Log::init([...]),绕过框架默认时机
如何捕获并记录 PDO::__construct() 级别的连接错误?
ThinkPHP 的 Connection 类在 connect() 方法中才真正调用 PDO::__construct(),此时已错过全局异常处理器的黄金拦截点。必须在连接发起前做兜底。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 重写
think\db\connector\Mysql(或其他对应驱动)的connect()方法,在try/catch中捕获原始PDOException - 在 catch 块里调用
Log::record()并传入完整错误信息,例如:Log::record('DB Connect failed: ' . $e->getMessage() . ' (Code:' . $e->getCode() . ')', 'error') - 注意不要直接 throw 新异常,否则会中断后续重连逻辑;可用
trigger_error()配合错误处理器二次捕获
database.php 里的 'break_reconnect' 和日志有什么关系?
这个配置控制连接失败后是否终止重试。设为 true 时,框架会在首次连接失败后直接抛异常;设为 false(默认)则尝试最多三次,每次间隔 100ms。日志只记录最后一次失败详情,前面两次失败默认静默。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 开发环境务必设
'break_reconnect' => true,避免日志被中间失败掩盖 - 生产环境若需观察重连行为,可临时开启
'debug' => true,此时每次重试都会触发Db::getConnectInfo()日志,但要注意性能损耗 - 不要依赖
'hostname'配置项的 DNS 缓存行为来“绕过”连接失败——PHP 的 PDO 不会自动重试 DNS 解析,超时由底层 socket 控制
为什么 Log::write() 写不到文件,但 echo 能看到错误?
常见于 CLI 模式或 FPM 子进程:日志路径权限不足、磁盘满、或日志驱动初始化时用了相对路径(如 runtime/log/),而当前工作目录不是项目根目录。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 检查
config/log.php中'path'是否为绝对路径,推荐用root_path('runtime/log/') - 确认
runtime/目录存在且 Web 用户(如 www-data)有写权限,ls -ld runtime查看 - 避免在
connect()失败回调里调用Log::write()——它内部可能再次触发数据库日志驱动,造成递归崩溃;改用file_put_contents()直写到明确路径


















