Skip to content
全部文档

日志

简介

为帮助你更了解应用内部发生的情况,Laravel 提供了强大的日志服务,可将消息记录到文件、系统错误日志,甚至发送到 Slack 以通知整个团队。

Laravel 日志基于「通道」。每个通道代表一种写入日志信息的特定方式。例如,single 通道将日志写入单个文件,而 slack 通道会将日志消息发送到 Slack。日志消息可根据其严重程度写入多个通道。

底层上,Laravel 使用 Monolog 库,该库支持多种强大的日志处理器。Laravel 让配置这些处理器变得轻松,你可按需组合以自定义应用的日志处理方式。

配置

控制应用日志行为的所有配置选项都位于 config/logging.php 配置文件中。该文件允许你配置应用的日志通道,请务必查看每个可用通道及其选项。下面我们将介绍一些常见选项。

默认情况下,Laravel 记录消息时会使用 stack 通道。stack 通道用于将多个日志通道聚合为单个通道。有关构建栈的更多信息,请参阅下文文档

可用通道驱动

每个日志通道由一个「驱动」提供支持。驱动决定日志消息实际如何、以及记录到何处。以下日志通道驱动在每个 Laravel 应用中都可用。这些驱动的大多数条目已存在于应用的 config/logging.php 配置文件中,请务必查看该文件以熟悉其内容:

名称说明
custom调用指定工厂创建通道的驱动。
daily基于 RotatingFileHandler、按日轮转的 Monolog 驱动。
errorlog基于 ErrorLogHandler 的 Monolog 驱动。
monolog可使用任何受支持 Monolog handler 的 Monolog 工厂驱动。
papertrail基于 SyslogUdpHandler 的 Monolog 驱动。
single基于单个文件或路径的日志通道(StreamHandler)。
slack基于 SlackWebhookHandler 的 Monolog 驱动。
stack便于创建「多通道」通道的包装器。
syslog基于 SyslogHandler 的 Monolog 驱动。

INFO

请参阅高级通道自定义文档,了解有关 monologcustom 驱动的更多信息。

配置通道名称

默认情况下,Monolog 会以与当前环境匹配的「通道名称」实例化,例如 productionlocal。要更改此值,可在通道配置中添加 name 选项:

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

通道前提条件

配置 Single 与 Daily 通道

singledaily 通道有三个可选配置选项:bubblepermissionlocking

名称说明默认值
bubbleIndicates if messages should bubble up to other channels after being handled.true
lockingAttempt to lock the log file before writing to it.false
permissionThe log file's permissions.0644

此外,daily 通道的保留策略可通过 LOG_DAILY_DAYS 环境变量或设置 days 配置选项来配置。

名称说明默认值
days应保留每日日志文件的天数。14

配置 Papertrail 通道

papertrail 通道需要 hostport 配置选项。可通过 PAPERTRAIL_URLPAPERTRAIL_PORT 环境变量定义。可从 Papertrail 获取这些值。

配置 Slack 通道

slack 通道需要 url 配置选项。该值可通过 LOG_SLACK_WEBHOOK_URL 环境变量定义。此 URL 应与你为 Slack 团队配置的传入 webhook 的 URL 匹配。

默认情况下,Slack 只会接收 critical 级别及以上的日志;不过,你可使用 LOG_LEVEL 环境变量,或修改 Slack 日志通道配置数组中的 level 选项来调整。

记录弃用警告

PHP、Laravel 及其他库经常会通知用户某些功能已弃用并将在未来版本中移除。若希望记录这些弃用警告,可使用 LOG_DEPRECATIONS_CHANNEL 环境变量,或在应用的 config/logging.php 配置文件中指定首选的 deprecations 日志通道:

php
'deprecations' => [
    'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
    'trace' => env('LOG_DEPRECATIONS_TRACE', false),
],

'channels' => [
    // ...
]

或者,可定义一个名为 deprecations 的日志通道。若存在该名称的日志通道,将始终用于记录弃用信息:

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

构建日志栈

如前所述,stack 驱动允许你为方便起见将多个通道组合成单个日志通道。为说明如何使用日志栈,我们来看一个生产应用中可能出现的配置示例:

php
'channels' => [
    'stack' => [
        'driver' => 'stack',
        'channels' => ['syslog', 'slack'], // [tl! add]
        '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,
    ],
],

让我们剖析此配置。首先,注意我们的 stack 通道通过其 channels 选项聚合了另外两个通道:syslogslack。因此,记录消息时,这两个通道都有机会记录该消息。不过,如下文所述,这些通道是否实际记录消息,可能取决于消息的严重程度 /「级别」。

日志级别

请注意上例中 syslogslack 通道配置上的 level 配置选项。该选项决定消息被该通道记录所需的最低「级别」。支撑 Laravel 日志服务的 Monolog 提供了 RFC 5424 规范 中定义的所有日志级别。按严重程度从高到低为:emergencyalertcriticalerrorwarningnoticeinfodebug

因此,假设我们使用 debug 方法记录一条消息:

php
Log::debug('An informational message.');

根据我们的配置,syslog 通道会将该消息写入系统日志;但由于该消息不是 critical 或以上级别,不会发送到 Slack。不过,若我们记录一条 emergency 消息,它会同时发送到系统日志和 Slack,因为 emergency 级别高于两个通道的最低级别阈值:

php
Log::emergency('The system is down!');

写入日志消息

可使用 Log facade 将信息写入日志。如前所述,记录器提供了 RFC 5424 规范 中定义的八个日志级别:emergencyalertcriticalerrorwarningnoticeinfodebug

php
use Illuminate\Support\Facades\Log;

Log::emergency($message);
Log::alert($message);
Log::critical($message);
Log::error($message);
Log::warning($message);
Log::notice($message);
Log::info($message);
Log::debug($message);

可调用这些方法中的任意一个,以对应级别记录消息。默认情况下,消息会写入由 logging 配置文件配置的默认日志通道:

php
<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Support\Facades\Log;
use Illuminate\View\View;

class UserController extends Controller
{
    /**
     * Show the profile for the given user.
     */
    public function show(string $id): View
    {
        Log::info('Showing the user profile for user: {id}', ['id' => $id]);

        return view('user.profile', [
            'user' => User::findOrFail($id)
        ]);
    }
}

上下文信息

可将上下文字典数组传给日志方法。这些上下文数据会与日志消息一起格式化并显示:

php
use Illuminate\Support\Facades\Log;

Log::info('User {id} failed to login.', ['id' => $user->id]);

偶尔,你可能希望指定应包含在特定通道所有后续日志条目中的一些上下文信息。例如,你可能希望记录与应用每个传入请求关联的请求 ID。为此,可调用 Log facade 的 withContext 方法:

php
<?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
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        $requestId = (string) Str::uuid();

        Log::withContext([
            'request-id' => $requestId
        ]);

        $response = $next($request);

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

        return $response;
    }
}

若希望在所有日志通道间共享上下文信息,可调用 Log::shareContext() 方法。该方法会将上下文信息提供给所有已创建的通道以及随后创建的任何通道:

php
<?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
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        $requestId = (string) Str::uuid();

        Log::shareContext([
            'request-id' => $requestId
        ]);

        // ...
    }
}

INFO

若需要在处理队列任务时共享日志上下文,可使用任务中间件

写入特定通道

有时你可能希望将消息记录到应用默认通道以外的通道。可使用 Log facade 上的 channel 方法,检索并写入配置文件中定义的任何通道:

php
use Illuminate\Support\Facades\Log;

Log::channel('slack')->info('Something happened!');

若希望创建由多个通道组成的按需日志栈,可使用 stack 方法:

php
Log::stack(['single', 'slack'])->info('Something happened!');

按需通道

也可在运行时提供配置来创建按需通道,而无需该配置存在于应用的 logging 配置文件中。为此,可将配置数组传给 Log facade 的 build 方法:

php
use Illuminate\Support\Facades\Log;

Log::build([
  'driver' => 'single',
  'path' => storage_path('logs/custom.log'),
])->info('Something happened!');

你也可能希望在按需日志栈中包含按需通道。可通过将按需通道实例包含在传给 stack 方法的数组中来实现:

php
use Illuminate\Support\Facades\Log;

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

Log::stack(['slack', $channel])->info('Something happened!');

Monolog 通道自定义

为通道自定义 Monolog

有时你可能需要对现有通道的 Monolog 配置有完全控制。例如,你可能希望为 Laravel 内置的 single 通道配置自定义的 Monolog FormatterInterface 实现。

开始时,在通道配置上定义 tap 数组。tap 数组应包含一组类,这些类在 Monolog 实例创建后有机会对其进行自定义(或「切入」)。这些类没有约定的存放位置,你可在应用中自由创建目录来存放它们:

php
'single' => [
    'driver' => 'single',
    'tap' => [App\Logging\CustomizeFormatter::class],
    'path' => storage_path('logs/laravel.log'),
    'level' => env('LOG_LEVEL', 'debug'),
    'replace_placeholders' => true,
],

在通道上配置好 tap 选项后,即可定义将自定义 Monolog 实例的类。该类只需要一个方法:__invoke,它接收 Illuminate\Log\Logger 实例。Illuminate\Log\Logger 实例会将所有方法调用代理到底层 Monolog 实例:

php
<?php

namespace App\Logging;

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

class CustomizeFormatter
{
    /**
     * Customize the given logger instance.
     */
    public function __invoke(Logger $logger): void
    {
        foreach ($logger->getHandlers() as $handler) {
            $handler->setFormatter(new LineFormatter(
                '[%datetime%] %channel%.%level_name%: %message% %context% %extra%'
            ));
        }
    }
}

INFO

你的所有「tap」类都由服务容器解析,因此它们所需的任何构造函数依赖都会自动注入。

创建 Monolog Handler 通道

Monolog 有多种可用 handler,而 Laravel 并未为每一个都提供内置通道。在某些情况下,你可能希望创建自定义通道,它仅仅是某个没有对应 Laravel 日志驱动的特定 Monolog handler 的实例。这些通道可使用 monolog 驱动轻松创建。

使用 monolog 驱动时,handler 配置选项用于指定将实例化哪个 handler。可选地,handler 所需的任何构造函数参数都可通过 handler_with 配置选项指定:

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

Monolog 格式化器

使用 monolog 驱动时,将使用 Monolog 的 LineFormatter 作为默认格式化器。不过,你可使用 formatterformatter_with 配置选项自定义传给 handler 的格式化器类型:

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

若你使用的 Monolog handler 能够提供自己的格式化器,可将 formatter 配置选项的值设为 default

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

Monolog 处理器

Monolog 也可在记录消息之前处理它们。你可以创建自己的处理器,或使用 Monolog 提供的现有处理器

若希望为 monolog 驱动自定义处理器,请在通道配置中添加 processors 配置值:

php
'memory' => [
    'driver' => 'monolog',
    'handler' => Monolog\Handler\StreamHandler::class,
    'handler_with' => [
        'stream' => 'php://stderr',
    ],
    'processors' => [
        // Simple syntax...
        Monolog\Processor\MemoryUsageProcessor::class,

        // With options...
        [
            'processor' => Monolog\Processor\PsrLogMessageProcessor::class,
            'with' => ['removeUsedContextFields' => true],
        ],
    ],
],

通过工厂创建自定义通道

若希望定义一个对 Monolog 的实例化与配置有完全控制的完全自定义通道,可在 config/logging.php 配置文件中指定 custom 驱动类型。配置应包含 via 选项,其值为将用于创建 Monolog 实例的工厂类名称:

php
'channels' => [
    'example-custom-channel' => [
        'driver' => 'custom',
        'via' => App\Logging\CreateCustomLogger::class,
    ],
],

配置好 custom 驱动通道后,即可定义将创建 Monolog 实例的类。该类只需要一个 __invoke 方法,应返回 Monolog logger 实例。该方法将接收通道配置数组作为唯一参数:

php
<?php

namespace App\Logging;

use Monolog\Logger;

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

使用 Pail 跟踪日志消息

你经常需要实时跟踪应用的日志。例如,在调试问题或监控应用日志中特定类型的错误时。

Laravel Pail 是一个包,让你可以直接从命令行轻松深入查看 Laravel 应用的日志文件。与标准的 tail 命令不同,Pail 设计为可与任何日志驱动一起使用,包括 Sentry 或 Flare。此外,Pail 还提供一组实用过滤器,帮助你快速找到所需内容。

安装

WARNING

Laravel Pail 需要 PCNTL PHP 扩展。

开始时,使用 Composer 包管理器将 Pail 安装到项目中:

shell
composer require --dev laravel/pail

用法

要开始跟踪日志,运行 pail 命令:

shell
php artisan pail

要提高输出详细程度并避免截断(…),请使用 -v 选项:

shell
php artisan pail -v

要获得最高详细程度并显示异常堆栈跟踪,请使用 -vv 选项:

shell
php artisan pail -vv

要停止跟踪日志,可随时按 Ctrl+C

过滤日志

--filter

可使用 --filter 选项按类型、文件、消息和堆栈跟踪内容过滤日志:

shell
php artisan pail --filter="QueryException"

--message

若仅按消息过滤日志,可使用 --message 选项:

shell
php artisan pail --message="User created"

--level

--level 选项可用于按日志级别过滤日志:

shell
php artisan pail --level=error

--user

若只显示给定用户已认证期间写入的日志,可将用户 ID 提供给 --user 选项:

shell
php artisan pail --user=1