必须通过Log::extend()在AppServiceProvider的boot()中注册自定义日志驱动,闭包接收$app、$config、$name参数并返回PSR-3兼容实例;Handler需重写write()而非handle(),且须从$config读取配置项。

怎么注册自定义日志通道驱动
Laravel 不允许直接在 config/logging.php 里写类名或闭包来定义新驱动,必须通过服务容器绑定 + extend() 注册。漏掉这一步,配置里的 'driver' => 'my_custom' 会直接报错 Driver [my_custom] not supported。
实操建议:
- 在
App\Providers\AppServiceProvider::boot()中调用Log::extend('my_custom', ...) - 闭包参数必须接收三个:$app(容器)、$config(该通道的 config 数组)、$name(通道名)
- 返回值必须是
Psr\Log\LoggerInterface实例,不能是Illuminate\Log\Logger原始对象(它不实现 PSR 接口) - 若依赖自定义 Handler,需确保它继承
Monolog\Handler\AbstractProcessingHandler或实现Monolog\Handler\HandlerInterface
为什么 Monolog Handler 要重写 write() 而不是 handle()
Monolog v2+ 的 handle() 是入口,但 Laravel 日志系统在底层做了封装,实际调用的是 write() —— 它负责格式化、级别过滤、上下文合并等前置逻辑。如果只重写 handle(),你的 Handler 可能收不到任何日志,或者收到未处理的原始数组。
常见错误现象:
- 日志完全不写入,控制台也无报错
-
context字段丢失,extra混乱 - 级别被忽略(比如
debug()写进了 error 文件)
正确做法:
- 继承
Monolog\Handler\StreamHandler或AbstractProcessingHandler - 重写
write(),在里面调用$this->isHandling($record)做级别判断 - 用
$this->getFormatter()->format($record)获取标准字符串,别手动拼接
自定义驱动如何读取配置项中的参数
你不能在 extend() 闭包里硬编码路径或 API 地址,所有动态参数必须从 $config 数组里取。这个数组就是你在 config/logging.php 对应通道下写的配置,比如 'api_url' => env('LOG_API_URL')。
使用场景:
- 写入远程 HTTP 接口时需要
$config['api_url']和$config['timeout'] - 写入数据库时需要
$config['connection']或$config['table'] - 加密日志时需要
$config['cipher']和$config['key']
注意点:
-
$config默认不含driver、name、channels这些元信息,只含你显式声明的键 - 环境变量务必用
env()包裹并设默认值,否则上线后$config['api_url']为null会导致连接失败 - 不要在
extend()里做耗时操作(如初始化 Redis 连接),应延迟到write()首次调用时
Laravel 9+ 使用自定义驱动时的兼容性坑
Laravel 9 升级了 Monolog 到 v2.8+,而 v1 和 v2 的 Handler 构造函数签名不同:v1 接收 $stream,v2 接收 $file 或 $handler。如果你沿用旧教程代码,大概率会遇到 Too few arguments to function Monolog\Handler\StreamHandler::__construct()。
性能与兼容性影响:
- Monolog v2 强制要求
Logger必须传 Formatter 实例,Laravel 默认用LineFormatter,但你的自定义 Handler 若没设 formatter,会 fallback 到空字符串导致日志不可读 - PHP 8.1+ 下,未声明属性类型会导致
Creation of dynamic property警告(尤其在 Handler 里动态挂属性时) - 队列任务中使用自定义日志通道,需确保 Handler 类可序列化;含资源句柄(如
fopen返回的 resource)会直接失败
最常被忽略的一点:自定义驱动注册必须在 Log::extend(),而不是 Log::build() —— 后者只返回临时实例,无法被 Log::channel('xxx') 复用。



















