Laravel 12 日志配置详解:config/logging.php 逐项说明

简介

Laravel 的日志系统基于 Monolog,所有配置集
中在 config/logging.php。本文以 Laravel 12 为例,逐项说明默认配置中每个字
段的含义,以及常见的使用场景。

参考
:Laravel 12.x Logging 官方文档


一、配置文件位置

文件 说明
config/logging.php 日志主配置文件
.env 通过环境变量覆盖配置
storage/logs/ 默认日志输出目录
bootstrap/app.php 异常上报相关配置(withExceptions)

如果项目中没有 config/logging.php,可以手动发布:

php artisan config:publish logging

新项目 .env 中默认的日志相关变量:

LOG_CHANNEL=stack
LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug

二、配置文件整体结构

Laravel 12 默认的 config/logging.php 由三部分组成:

return [
'default' => env('LOG_CHANNEL', 'stack'), // 默认通道

'deprecations' => [ ... ], // 废弃警告日志

'channels' => [ ... ], // 所有通道定义
];

三、顶层配置

default:默认通道

'default' => env('LOG_CHANNEL', 'stack'),

调用 Log::info()、logger() 等方法时,未指定通道就写入这里配置的通道。值必须
是 channels 中定义过的某个 key。

deprecations:废弃警告

'deprecations' => [
'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
'trace' => env('LOG_DEPRECATIONS_TRACE', false),
],
字段 说明
channel PHP / Laravel 废弃功能警告写入哪个通道,默认 null 即丢弃
trace 是否记录调用堆栈,方便定位是哪行代码触发了废弃警告

升级 PHP 或 Laravel 版本前,建议临时打开:

LOG_DEPRECATIONS_CHANNEL=daily
LOG_DEPRECATIONS_TRACE=true

另一种方式是直接在 channels 中定义一个名为 deprecations 的通道,只要存在同
名通道,废弃警告就一定会写入它
,优先于上面的配置:

'channels' => [
'deprecations' => [
'driver' => 'single',
'path' => storage_path('logs/php-deprecation-warnings.log'),
],
],

四、默认通道逐个说明

1. stack:组合通道

'stack' => [
'driver' => 'stack',
'channels' => explode(',', (string) env('LOG_STACK', 'single')),
'ignore_exceptions' => false,
],
字段 说明
driver stack 表示把多个通道组合成一个
channels 要组合的通道列表,Laravel 12 通过 LOG_STACK 逗号分隔配置
ignore_exceptions 某个子通道写入失败时是否忽略异常,false 表示抛出

关于 ignore_exceptions:

  • false(默认):任一子通道抛出异常(如 Slack 网络超时、日志文件无写权限),异
    常会向上抛出,可能导致当前请求报错
  • true:吞掉子通道的异常,继续写其他通道,适合包含 Slack 等外部服务的组合,避
    免 “记录日志失败”反过来影响业务

例如同时写文件并推送 Slack:

LOG_STACK=daily,slack

stack 与日志级别的配合

stack 中的每个子通道都会收到消息,但是否真正写入由各自的 level 决定。官方文档
示例:

'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['syslog', 'slack'],
'ignore_exceptions' => false,
],

'syslog' => [
'driver' => 'syslog',
'level' => env('LOG_LEVEL', 'debug'),
'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER),
'replace_placeholders' => true,
],

'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'),
'emoji' => env('LOG_SLACK_EMOJI', ':boom:'),
'level' => env('LOG_LEVEL', 'critical'),
'replace_placeholders' => true,
],
],
Log::debug('An informational message.');  // 只写入 syslog
Log::emergency('The system is down!'); // syslog 和 slack 都会收到

name:通道名称

Monolog 实例默认使用当前环境名(如 production、local)作为通道名,也就是日志
里 production.ERROR: 中的 production。可以通过 name 修改:

'stack' => [
'driver' => 'stack',
'name' => 'channel-name',
'channels' => ['single', 'slack'],
],

2. single:单文件

'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'replace_placeholders' => true,
],
字段 说明
path 日志文件路径
level 最低记录级别,低于该级别的日志会被忽略
replace_placeholders 是否把消息中的 {key} 替换为上下文中的值

replace_placeholders 示例:

Log::info('用户 {id} 登录成功', ['id' => 42]);
// 输出:用户 42 登录成功

single 会一直写同一个文件,文件会无限增长,生产环境更推荐 daily。

3. daily:按天切割

'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'days' => env('LOG_DAILY_DAYS', 14),
'replace_placeholders' => true,
],
字段 说明
days 保留最近多少天的日志文件,超过的自动删除,0 为不删除

生成的文件名形如:

storage/logs/laravel-2026-09-27.log
storage/logs/laravel-2026-09-26.log

4. slack:推送到 Slack

'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'),
'emoji' => env('LOG_SLACK_EMOJI', ':boom:'),
'level' => env('LOG_LEVEL', 'critical'),
'replace_placeholders' => true,
],
字段 说明
url Slack Incoming Webhook 地址
username 消息发送者显示名称
emoji 发送者头像 emoji
level 一般只推送 critical 及以上的严重错误

url 是必填项,需要先在 Slack 中创建
Incoming Webhook,再配置到
.env:

LOG_SLACK_WEBHOOK_URL=https://hooks.slack.com/services/xxx/xxx/xxx

注意 level 读取的也是 LOG_LEVEL,只有 .env 未设置时才默认 critical。新
项目 .env 里默认就有 LOG_LEVEL=debug,此时 Slack 会收到所有 debug 日志。建
议直接写死 'level' => 'critical'。

5. papertrail:远程日志服务

'papertrail' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'debug'),
'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class),
'handler_with' => [
'host' => env('PAPERTRAIL_URL'),
'port' => env('PAPERTRAIL_PORT'),
'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'),
],
'processors' => [PsrLogMessageProcessor::class],
],
字段 说明
driver monolog 表示直接使用 Monolog 的 Handler
handler Monolog Handler 类名
handler_with 传给 Handler 构造函数的参数
processors 日志处理器,PsrLogMessageProcessor 用于替换占位符

host 和 port 必填,从 Papertrail 后台获取后配置:

PAPERTRAIL_URL=logsN.papertrailapp.com
PAPERTRAIL_PORT=12345

6. stderr:标准错误输出

'stderr' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'debug'),
'handler' => StreamHandler::class,
'handler_with' => [
'stream' => 'php://stderr',
],
'formatter' => env('LOG_STDERR_FORMATTER'),
'processors' => [PsrLogMessageProcessor::class],
],
字段 说明
stream 输出流,php://stderr 即标准错误
formatter 格式化类,如 Monolog\Formatter\JsonFormatter 输出 JSON

Docker / K8s 场景推荐使用,日志直接由容器收集:

LOG_CHANNEL=stderr
LOG_STDERR_FORMATTER=Monolog\Formatter\JsonFormatter

7. syslog:系统日志

'syslog' => [
'driver' => 'syslog',
'level' => env('LOG_LEVEL', 'debug'),
'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER),
'replace_placeholders' => true,
],
字段 说明
facility syslog 设施类型,默认 LOG_USER

8. errorlog:PHP error_log

'errorlog' => [
'driver' => 'errorlog',
'level' => env('LOG_LEVEL', 'debug'),
'replace_placeholders' => true,
],

写入 PHP 的 error_log(),最终位置由 php.ini 中的 error_log 决定,通常会进
入 PHP-FPM 或 Web 服务器的错误日志。

9. null:丢弃日志

'null' => [
'driver' => 'monolog',
'handler' => NullHandler::class,
],

所有写入的日志都被丢弃,常用于测试环境或关闭废弃警告。

10. emergency:兜底通道

'emergency' => [
'path' => storage_path('logs/laravel.log'),
],

当配置的通道本身出错(比如通道名写错、Slack 地址无法访问)时,Laravel 会把错误写
到这里,保证日志不会完全丢失。


五、通用可选参数

1. 所有通道通用

参数 说明
driver 驱动类型,见文末驱动汇总
level 最低记录级别
name Monolog 通道名,默认是当前环境名
tap 通道创建后执行的自定义类,用于修改 Monolog 实例
replace_placeholders 是否替换消息中的 {key} 占位符

2. single / daily 专用

参数 默认值 说明
bubble true 处理后是否继续冒泡给其他通道
locking false 写文件前是否尝试加文件锁
permission 0644 日志文件权限
days 14 仅 daily,保留天数,也可用 LOG_DAILY_DAYS 设置

permission 很实用:php-fpm 用户和命令行用户不同时,常出现 Permission denied
无法写日志,可以设置为 0664 并保证两者同组。

'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'days' => 14,
'permission' => 0664,
],

3. monolog 驱动专用

monolog 驱动可以直接使用 Monolog 的任意
Handler,适
合 Laravel 没有内置驱动的场景。

参数 说明
handler 要实例化的 Monolog Handler 类
handler_with 传给 Handler 构造函数的参数(按参数名匹配)
formatter 格式化类,默认 LineFormatter;设为 default 使用 Handler 自带的格式化
formatter_with 传给格式化类构造函数的参数
processors 写入前对日志进行加工的处理器列表

handler / handler_with

'logentries' => [
'driver' => 'monolog',
'handler' => Monolog\Handler\SyslogUdpHandler::class,
'handler_with' => [
'host' => 'my.logentries.internal.datahubhost.company.com',
'port' => '10000',
],
],

formatter / formatter_with

'browser' => [
'driver' => 'monolog',
'handler' => Monolog\Handler\BrowserConsoleHandler::class,
'formatter' => Monolog\Formatter\HtmlFormatter::class,
'formatter_with' => [
'dateFormat' => 'Y-m-d',
],
],

Handler 自带格式化时,设为 default:

'newrelic' => [
'driver' => 'monolog',
'handler' => Monolog\Handler\NewRelicHandler::class,
'formatter' => 'default',
],

processors

支持两种写法:直接写类名,或者带构造参数的数组形式。可用处理器见
Monolog Processor。

'memory' => [
'driver' => 'monolog',
'handler' => Monolog\Handler\StreamHandler::class,
'handler_with' => [
'stream' => 'php://stderr',
],
'processors' => [
// 简单写法:每条日志附带内存占用
Monolog\Processor\MemoryUsageProcessor::class,

// 带参数写法:替换占位符后移除已使用的 context 字段
[
'processor' => Monolog\Processor\PsrLogMessageProcessor::class,
'with' => ['removeUsedContextFields' => true],
],
],
],

常用处理器:

处理器 说明
PsrLogMessageProcessor 替换 {key} 占位符
MemoryUsageProcessor 附带当前内存占用
MemoryPeakUsageProcessor 附带内存峰值
IntrospectionProcessor 附带调用的文件、行号、类、方法
WebProcessor 附带 URL、IP、请求方法等
UidProcessor 附带唯一 ID,便于串联同一请求

4. custom 驱动专用

参数 说明
via 工厂类,__invoke 返回 Monolog 实例

六、日志级别说明

Laravel 遵循 RFC 5424 定义的 8 个级别,从低到高:

级别 方法 说明
debug Log::debug() 调试信息
info Log::info() 普通信息,如用户登录
notice Log::notice() 正常但值得注意的事件
warning Log::warning() 警告,如使用了废弃接口
error Log::error() 运行时错误
critical Log::critical() 严重错误,如组件不可用
alert Log::alert() 需要立即处理
emergency Log::emergency() 系统不可用

通道的 level 设为 warning 时,只会记录 warning 及以上的日志。

生产环境建议:

LOG_LEVEL=warning

七、常用场景示例

场景 1:写入指定通道

use Illuminate\Support\Facades\Log;

Log::channel('slack')->critical('支付服务异常');

场景 2:临时组合多个通道

Log::stack(['daily', 'slack'])->error('订单同步失败', ['order_id' => 1001]);

场景 3:自定义业务日志通道

在 channels 中新增:

'order' => [
'driver' => 'daily',
'path' => storage_path('logs/order/order.log'),
'level' => 'info',
'days' => 30,
'replace_placeholders' => true,
],

使用:

Log::channel('order')->info('订单 {id} 已创建', ['id' => $order->id]);

场景 4:不改配置文件,按需创建通道

Log::build([
'driver' => 'single',
'path' => storage_path('logs/import.log'),
])->info('导入完成');

按需通道也可以放进 Log::stack 中和已有通道组合:

$channel = Log::build([
'driver' => 'single',
'path' => storage_path('logs/import.log'),
]);

Log::stack(['slack', $channel])->info('导入完成');

场景 5:给日志统一添加上下文

withContext 只作用于当前默认通道,shareContext 作用于所有已创建和之后
创建的通道
。常见做法是在中间件中为每个请求生成 request_id:

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Response;

class AssignRequestId
{
public function handle(Request $request, Closure $next): Response
{
$requestId = (string) Str::uuid();

// 当前通道后续所有日志都带上 request-id
Log::withContext([
'request-id' => $requestId,
]);

// 如需所有通道都带上,改用 shareContext
// Log::shareContext(['request-id' => $requestId]);

$response = $next($request);

$response->headers->set('Request-Id', $requestId);

return $response;
}
}

在 bootstrap/app.php 中注册:

->withMiddleware(function (Middleware $middleware) {
$middleware->append(\App\Http\Middleware\AssignRequestId::class);
})

队列任务中需要共享上下文时,可以借助
任务中间件 调
用 shareContext。

场景 6:使用 tap 自定义格式

// config/logging.php
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'tap' => [App\Logging\CustomizeFormatter::class],
],
namespace App\Logging;

use Illuminate\Log\Logger;
use Monolog\Formatter\LineFormatter;

class CustomizeFormatter
{
public function __invoke(Logger $logger): void
{
foreach ($logger->getHandlers() as $handler) {
$handler->setFormatter(new LineFormatter(
"[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n",
'Y-m-d H:i:s'
));
}
}
}

tap 类由服务容器解析,构造函数中的依赖会自动注入。Illuminate\Log\Logger 会把
方法调用代理到底层 Monolog 实例,所以可以直接调用
getHandlers()、pushProcessor() 等。

场景 7:完全自定义通道(custom 驱动)

'custom' => [
'driver' => 'custom',
'via' => App\Logging\CreateCustomLogger::class,
],
namespace App\Logging;

use Monolog\Logger;

class CreateCustomLogger
{
public function __invoke(array $config): Logger
{
return new Logger(/* ... */);
}
}

八、驱动汇总

驱动 底层 Monolog Handler 说明
single StreamHandler 单文件
daily RotatingFileHandler 按天切割文件
stack - 组合多个通道
slack SlackWebhookHandler 推送到 Slack Webhook
papertrail SyslogUdpHandler 推送到 Papertrail
syslog SyslogHandler 写入系统 syslog
errorlog ErrorLogHandler 写入 PHP error_log
monolog 任意 直接使用任意 Monolog Handler
custom 任意 通过工厂类完全自定义

九、使用 Pail 实时查看日志

Pail 是官方的日志实时查看工具,和 tail 不同的是它支持任意日志驱动(包括
Sentry、 Flare),并提供过滤功能。

安装

需要 PHP 的 PCNTL 扩展。Laravel
12 新项目默认已在 require-dev 中包含,没有的话手动安装:

composer require --dev laravel/pail

使用

# 开始实时查看,Ctrl+C 退出
php artisan pail

# 显示更多内容,避免截断
php artisan pail -v

# 最详细输出,包含异常堆栈
php artisan pail -vv

过滤参数

参数 说明 示例
--filter 按类型、文件、消息、堆栈内容过滤 php artisan pail --filter="QueryException"
--message 只按消息内容过滤 php artisan pail --message="User created"
--level 按日志级别过滤 php artisan pail --level=error
--user 只显示指定用户 ID 登录期间产生的日志 php artisan pail --user=1

十、常见问题

Q:修改了 .env 里的日志配置没生效?

多半是配置被缓存了,清理后重新缓存:

php artisan config:clear
php artisan config:cache

Q:日志文件报 Permission denied?

php-fpm(如 www-data)和执行 artisan 的用户不同,一方创建的文件另一方无法写入
。给通道加上 'permission' => 0664,并将两个用户加入同一用户组。

Q:如何实时查看日志?

推荐使用上文的 php artisan pail,或者直接:

tail -f storage/logs/laravel-$(date +%F).log

Q:如何控制异常的日志级别或不记录某些异常?

在 bootstrap/app.php 中配置:

use Psr\Log\LogLevel;

->withExceptions(function (Exceptions $exceptions) {
// 指定异常的日志级别
$exceptions->level(PDOException::class, LogLevel::CRITICAL);

// 不记录某些异常
$exceptions->dontReport([
App\Exceptions\BusinessException::class,
]);

// 为所有异常日志添加上下文
$exceptions->context(fn () => [
'user_id' => auth()->id(),
]);
})