日志


简介

为了帮助您更好地了解应用程序中发生的情况,Laravel 提供了强大的日志记录服务,允许您将消息记录到文件、系统错误日志,甚至发送到 Slack,通知整个团队。

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

在底层,Laravel 使用 Monolog 库,该库提供了对各种强大的日志处理程序的支持。Laravel 使配置这些处理程序变得轻而易举,允许您混合和匹配它们,以定制应用程序的日志处理。

配置

应用日志系统的所有配置都存放在配置文件 config/logging.php 中,该文件允许你配置应用的日志通道,因此请务必查看每个可用通道及其配置项。下面我们就来看看其中某些配置项。

默认情况下,Laravel 使用 stack 通道来记录日志信息,stack 通道被用于聚合多个日志通道到单个通道,更多关于构建 stack 的信息,请查看下面的文档

配置通道名称

默认情况下,Monolog 通过与当前环境匹配的「通道名称」实例化,例如 productionlocal,要改变这个值,添加 name 项到通道配置:

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

有效通道驱动列表

每个日志通道都由一个“驱动程序”(driver)提供支持。驱动程序确定实际记录日志消息的方式和位置。以下是每个 Laravel 应用程序中可用的日志通道驱动程序。在您的应用程序的 config/logging.php 配置文件中已经存在大多数这些驱动程序的条目,因此请确保查看此文件以熟悉其内容:

名称 描述
stack 用于创建「多通道」通道的聚合器
single 基于单文件/路径的日志通道(StreamHandler
daily 基于 RotatingFileHandler 的 Monolog 驱动,以天为维度对日志进行分隔
slack 基于 SlackWebhookHandler 的 Monolog 驱动
papertrail 基于 SyslogUdpHandler 的 Monolog 驱动
syslog 基于 SyslogHandler 的 Monolog 驱动
errorlog 基于 ErrorLogHandler 的 Monolog 驱动
monolog Monolog 改成驱动,可以使用所有支持的 Monolog 处理器
custom 调用指定改成创建通道的驱动
null 一个丢弃所有日志消息的驱动

注:查看高级通道自定义文档学习 monologcustom 驱动。

通道预备知识

配置 Single 和 Daily 通道

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

名称 描述 默认值
bubble 表示消息在被处理后是否冒泡到其它通道 true
permission 日志文件权限 0644
locking 在日志文件写入前尝试锁定它 false

另外,daily 通道的保留策略可以通过 days 选项进行配置:

名称 描述 默认值
days daily通道日志文件保留的天数 7

配置 Papertrail 通道

papertrail 通道要求 urlport 配置选项,你可以从 Papertrail 中获取这些值。

配置 Slack 通道

slack 通道需要一个 url 配置项,这个 URL 需要和你配置的 Slack 团队请求 URL 相匹配。默认情况下,Slack 只会接收 critical 级别(及以上)的日志,不过,你可以在配置文件的 logging 选项中进行调整。

日志记录警告

PHP、Laravel 和其他库通常会通知其用户某些功能已过时,并将在将来的版本中删除。如果您希望记录这些过时的警告,可以在应用程序的 config/logging.php 配置文件中指定您首选的过时日志通道:

'deprecations' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
 
'channels' => [
    ...
]

或者,您可以定义一个名为 deprecations 的日志通道。如果存在一个名为 deprecations 的日志通道,它将始终用于记录过时的内容:

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

构建日志堆栈

如上所述,stack 驱动允许你将多个通道合并到单个日志通道,为了说明如何实现,让我们看一个你可能在生产环境中看到的示例配置:

'channels' => [
    'stack' => [
        'driver' => 'stack',
        'channels' => ['syslog', 'slack'],
    ],
    
    'syslog' => [
        'driver' => 'syslog',
        'level' => 'debug',
    ],
    
    'slack' => [
        'driver' => 'slack',
        'url' => env('LOG_SLACK_WEBHOOK_URL'),
        'username' => 'Laravel Log',
        'emoji' => ':boom:',
        'level' => 'critical',
    ],
],

我们来剖析这个配置。首先,注意 stack 通道通过 channels 项将聚合了其他两个通道:syslogslack。因此,记录日志信息时,这两个通道都有机会记录信息。

日志级别

注意上述示例中 syslogslack 通道配置中出现的 level 配置项,这个配置项决定了日志信息被通道记录所必须达到的最低「级别」。为 Laravel 提供日志服务的 Monolog,支持定义在 RFC 5424 规范中的所有日志级别:emergencyalertcriticalerrorwarningnoticeinfodebug

因此,假设我们使用 debug 方法来记录日志信息:

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

鉴于我们的配置,syslog 通道将会将信息记录到系统日志;不过,由于错误消息不是 critical 或更高级别,将不会发送到 Slack。但是,如果我们记录的是 emergency 级别的信息,就会被发送到系统日志和 Slack,因为 emergency 级别高于两个通道的最低级别门槛:

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

写入日志信息

你可以使用 Log 门面记录日志信息,如上所述,日志系统提供了定义在 RFC 5424 规范中的八种日志级别:emergencyalertcriticalerrorwarningnoticeinfodebug

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);

因此,你可以调用其中的任意一个方法来记录相应级别的日志信息,默认情况下,信息会被写入到通过配置文件 config/logging.php 所配置的默认通道:

<?php
 
namespace App\Http\Controllers;
 
use App\Http\Controllers\Controller;
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);
 
        return view('user.profile', [
            'user' => User::findOrFail($id)
        ]);
    }
}

上下文信息

上下文数据也会以数组形式传递给日志方法,然后和日志信息一起被格式化和显示:

use Illuminate\Support\Facades\Log;
 
Log::info('User failed to login.', ['id' => $user->id]);

有时,您可能希望指定某些上下文信息,以便在特定通道中的所有后续日志条目中包含该信息。例如,您可能希望记录与应用程序中的每个传入请求相关联的请求 ID。为了实现这一点,您可以调用 Log 门面的 withContext 方法:

<?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
        ]);
 
        return $next($request)->header('Request-Id', $requestId);
    }
}

如果您想在所有日志记录通道之间共享上下文信息,可以调用 Log::shareContext() 方法。这个方法将为所有创建的通道和随后创建的任何通道提供上下文信息。通常,shareContext 方法应该从应用程序服务提供者的 boot 方法中调用:

use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
 
class AppServiceProvider
{
    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Log::shareContext([
            'invocation-id' => (string) Str::uuid(),
        ]);
    }
}

写入指定通道

有时候你可能希望将日志信息记录到某个通道而不是应用的默认通道。要实现这个目的,你可以使用 Log 门面上的 channel 方法来获取配置文件定义的通道并将日志写入进去:

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

如果你想要创建一个由多个通道组成的按需日志堆栈,可以使用 stack 方法:

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

按需通道

还可以通过在运行时提供配置而不将该配置包含在应用程序的日志记录配置文件中来创建按需通道。为了实现这一点,您可以将配置数组传递给 Log 门面的 build 方法:

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

您还可以将按需通道包含在按需日志堆栈中。可以通过在传递给 stack 方法的数组中包含按需通道实例来实现这一点:

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 的配置,例如,你可能想要为给定通道的处理器配置一个自定义的 Monolog FormatterInterface 实现。

作为开始,我们在通道的配置上定义一个 tap 数组,这个 tap 数组需要包含可以自定义创建后的 Monolog 实例的类列表:

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

在通道上配置好 tap 项后,就可以定义自定义 Monolog 实例的类了。这个类只需要一个获取 Illuminate\Log\Logger 实例的方法:__invokeIlluminate\Log\Logger 实例会代理所有调用底层 Monolog 实例的方法:

<?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%'
            ));
        }
    }
}

注:所有「tap」类都通过服务容器解析,所以他们需要的所有构造函数依赖都会被自动注入。

创建 Monolog 处理器通道

Monolog 有多个可用的处理器,在某些情况下,你所想创建的日志记录器类型只不过是带有指定处理器实例的 Monolog 驱动,这些通道可以通过 monolog 驱动创建。

使用 monolog 驱动时,handler 配置项用于指定实例化哪个处理器,作为可选的处理器构造函数参数,可以使用 handler_with 配置项来设置:

'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 配置项自定义传入处理器的格式化工具的类型:

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

如果你使用的是能够自己提供格式化工具的 Monolog 处理器,可以将 formatter 配置项的值设置为 default

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

通过工厂创建通道

如果你想要定义一个完整的自定义通道从而可以完全控制 Monolog 的实例化和配置,可以在配置文件 config/logging.php 中指定一个 custom 驱动类型。此外,你的配置中还要包含一个 via 项来指定被调用来创建 Monolog 实例的工厂类:

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

配置好 custom 通道后,就可以定义创建 Monolog 实例的类了,这个类只需要一个返回 Monolog 实例的方法:__invoke

<?php
    
namespace App\Logging;
    
use Monolog\Logger;
    
class CreateCustomLogger
{
    /**
     * Create a custom Monolog instance.
     */
    public function __invoke(array $config): Logger
    {
        return new Logger(/* ... */);
    }
}

点赞 取消点赞 收藏 取消收藏

<< 上一篇: 错误处理

>> 下一篇: Artisan 控制台