Monolog 默认不支持直接写数据库,因其设计遵循「日志处理与存储解耦」原则,仅负责格式化、过滤和分发,不内置数据库写入逻辑;Laravel 也未实现 DatabaseHandler 驱动,强行配置会报错。

为什么 Monolog 默认不支持直接写数据库
因为 Monolog 的设计原则是「日志处理与存储解耦」——它只负责格式化、过滤、分发日志,不内置数据库写入逻辑。Laravel 虽然封装了 Monolog,但默认驱动(SingleStreamHandler、RotatingFileHandler)全走文件,没提供 DatabaseHandler。
强行在配置里填 database 会报错:Driver [database] not supported,这不是 Laravel 配置漏了,是它压根没实现这个驱动。
- 别试图改
config/logging.php里的driver为database—— 无效 - 也别找第三方包自动注册
DatabaseHandler—— 多数已过时或与 Laravel 9+ 不兼容 - 真正可行的路只有一条:自己写一个
Handler,并把它挂进 Monolog 的 pipeline
怎么写一个安全可用的 DatabaseHandler
核心不是“把日志塞进数据库”,而是“不拖慢主流程、不因 DB 故障丢日志、能被 Laravel 容器管理”。直接用 Eloquent 写会卡住请求,必须异步或降级兜底。
- 继承
Monolog\Handler\AbstractProcessingHandler,重写write()方法 - 用
DB::table('logs')->insertOrIgnore(...)替代 Eloquent 模型 —— 避免模型事件、观察者、访问器干扰 - 包裹
try/catch,捕获QueryException和连接超时,失败时 fallback 到文件(比如写到storage/logs/fallback.log) - 字段建议至少包含:
level(int)、message(text)、context(json)、channel(varchar)、created_at(datetime)
示例关键片段:
use Monolog\Handler\AbstractProcessingHandler;
use Illuminate\Support\Facades\DB;
class DatabaseHandler extends AbstractProcessingHandler
{
public function write(array $record): void
{
try {
DB::table('logs')->insertOrIgnore([
'level' => $record['level'],
'message' => $record['message'],
'context' => json_encode($record['context']),
'channel' => $record['channel'],
'created_at' => now(),
]);
} catch (\Exception $e) {
// 降级写文件,避免日志全丢
file_put_contents(
storage_path('logs/fallback.log'),
"[" . date('Y-m-d H:i:s') . "] " . $record['message'] . "\n",
FILE_APPEND
);
}
}
}
如何在 logging.php 中正确注册自定义 Handler
Laravel 不允许直接在配置里 new 实例,必须通过服务容器绑定 + 驱动扩展。硬编码 new DatabaseHandler 会导致无法注入依赖(比如 DB),且每次日志都新建对象,浪费资源。
Laravel Cloud(cloud.laravel.com)。此技能用于处理所有 Laravel Cloud 请求,包括搜索和读取数据。只要任务涉及 Laravel Cloud,即使用此技能。
- 在
App\Providers\AppServiceProvider::boot()里调用Log::extend('database', ...) - 闭包里返回新实例,但要把
DatabaseHandler的构造参数(如 $level、$bubble)从配置中读出来 - 确保
config/logging.php新增 channel 时,driver设为database,其他字段(如level)会被传进闭包 - 不要在闭包里做耗时操作(如查配置表),所有参数必须是配置文件里静态定义的
配置示例(config/logging.php):
'channels' => [
'db' => [
'driver' => 'database',
'level' => 'debug',
'bubble' => true,
],
],
哪些场景下不该用数据库存日志
不是所有日志都适合进数据库。高频、低价值、调试类日志(比如循环里打的 Log::debug("i={$i}"))写库等于自建 DDOS。
- HTTP 请求量 > 100 QPS 的项目,慎用 —— 即使加了队列,DB 插入延迟也会累积
- 日志量大(单日 > 1GB)时,MySQL 的
INSERT性能会断崖下跌,不如用file+logrotate+ELK - 开发环境开启数据库日志没问题;生产环境建议只对
error和critical级别启用,其他级别走文件 - 如果用了读写分离,千万别让日志写从库 —— 主从延迟会导致日志时间戳错乱,排查时会怀疑人生
最常被忽略的一点:数据库日志表没加索引。查某个用户报错时,光靠 where context like '%user_id:123%' 扫全表,500 万行就卡死。至少给 level、created_at、channel 加联合索引。










