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,可以手动发布:

bash 复制代码
php artisan config:publish logging

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

ini 复制代码
LOG_CHANNEL=stack
LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug

二、配置文件整体结构

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

php 复制代码
return [
    'default' => env('LOG_CHANNEL', 'stack'),   // 默认通道

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

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

三、顶层配置

default:默认通道

php 复制代码
'default' => env('LOG_CHANNEL', 'stack'),

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

deprecations:废弃警告

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

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

ini 复制代码
LOG_DEPRECATIONS_CHANNEL=daily
LOG_DEPRECATIONS_TRACE=true

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

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

四、默认通道逐个说明

1. stack:组合通道

php 复制代码
'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:

ini 复制代码
LOG_STACK=daily,slack

stack 与日志级别的配合

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

php 复制代码
'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,
    ],
],
php 复制代码
Log::debug('An informational message.');  // 只写入 syslog
Log::emergency('The system is down!');     // syslog 和 slack 都会收到

name:通道名称

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

php 复制代码
'stack' => [
    'driver' => 'stack',
    'name' => 'channel-name',
    'channels' => ['single', 'slack'],
],

2. single:单文件

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

replace_placeholders 示例:

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

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

3. daily:按天切割

php 复制代码
'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 为不删除

生成的文件名形如:

bash 复制代码
storage/logs/laravel-2026-09-27.log
storage/logs/laravel-2026-09-26.log

4. slack:推送到 Slack

php 复制代码
'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:

ini 复制代码
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:远程日志服务

php 复制代码
'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 后台获取后配置:

ini 复制代码
PAPERTRAIL_URL=logsN.papertrailapp.com
PAPERTRAIL_PORT=12345

6. stderr:标准错误输出

php 复制代码
'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 场景推荐使用,日志直接由容器收集:

ini 复制代码
LOG_CHANNEL=stderr
LOG_STDERR_FORMATTER=Monolog\Formatter\JsonFormatter

7. syslog:系统日志

php 复制代码
'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

php 复制代码
'errorlog' => [
    'driver' => 'errorlog',
    'level' => env('LOG_LEVEL', 'debug'),
    'replace_placeholders' => true,
],

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

9. null:丢弃日志

php 复制代码
'null' => [
    'driver' => 'monolog',
    'handler' => NullHandler::class,
],

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

10. emergency:兜底通道

php 复制代码
'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 并保证两者同组。

php 复制代码
'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

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

formatter / formatter_with

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

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

php 复制代码
'newrelic' => [
    'driver' => 'monolog',
    'handler' => Monolog\Handler\NewRelicHandler::class,
    'formatter' => 'default',
],

processors

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

php 复制代码
'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 及以上的日志。

生产环境建议:

ini 复制代码
LOG_LEVEL=warning

七、常用场景示例

场景 1:写入指定通道

php 复制代码
use Illuminate\Support\Facades\Log;

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

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

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

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

在 channels 中新增:

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

使用:

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

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

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

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

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

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

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

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

php 复制代码
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 中注册:

php 复制代码
->withMiddleware(function (Middleware $middleware) {
    $middleware->append(\App\Http\Middleware\AssignRequestId::class);
})

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

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

php 复制代码
// config/logging.php
'daily' => [
    'driver' => 'daily',
    'path' => storage_path('logs/laravel.log'),
    'tap' => [App\Logging\CustomizeFormatter::class],
],
php 复制代码
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 驱动)

php 复制代码
'custom' => [
    'driver' => 'custom',
    'via' => App\Logging\CreateCustomLogger::class,
],
php 复制代码
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 中包含,没有的话手动安装:

bash 复制代码
composer require --dev laravel/pail

使用

bash 复制代码
# 开始实时查看,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 里的日志配置没生效?

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

bash 复制代码
php artisan config:clear
php artisan config:cache

Q:日志文件报 Permission denied?

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

Q:如何实时查看日志?

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

bash 复制代码
tail -f storage/logs/laravel-$(date +%F).log

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

在 bootstrap/app.php 中配置:

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(),
    ]);
})
相关推荐
明月_清风1 小时前
数据处理完放在哪里?一文搞懂数据仓库与数据湖
大数据·后端·数据分析
据说幸运很容易1 小时前
TestNG 分组接入现有框架:实现、踩坑与解法
后端·架构
IT_陈寒1 小时前
我的JavaScript代码为啥在forEach里没按预期执行?
前端·人工智能·后端
136096757231 小时前
1Panel 部署 ThinkPHP8 踩坑实录
后端
用户788477316341 小时前
源码交付清单:做完一个项目,你到底该从开发商手里拿到什么
后端
mldong1 小时前
引擎里没有 setStatus:状态迁移收口,不用状态机框架
后端·架构
程序猿DD1 小时前
OctaFuse Gateway 2.12.0:供应商账号管理、路由工作区与多模态入口发现
后端
她的男孩1 小时前
开放接口限流从 20 改到 200 还是每分钟 20 次:拆完防重放+幂等+限流,我找到 5 个静默失效的坑
java·后端·架构
lizhongxuan1 小时前
Agent Sandbox 怎么选
后端