简介
Laravel 的日志系统基于 Monolog,所有配置集 中在 config/logging.php。本文以 Laravel 12 为例,逐项说明默认配置中每个字 段的含义,以及常见的使用场景。
一、配置文件位置
| 文件 | 说明 |
|---|---|
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(),
]);
})