在使用 laravel 的日志组件(Facade门面模式)时,我们可以通过 withContext 方法为请求周期注册全局的上下文信息,用来做 RequestID/TraceId 类的请求链路追踪,非常的方便。但在 10- 以下的版本中,withContext 只能为默认日志通道注入全局上下文,在非默认通道的场景,比如 cli 下, 就无法优雅的实现了。

但如果你了解 ServiceServiceProviderFacade 三者之间的关系,那可以参考如下两类方法来优雅的实现非默认日志通道下注册全局 context 的方法:

门面 \Illuminate\Support\Facades\Log 代理的日志服务
实为 \Illuminate\Log\LogServiceProviderapp 容器中注册的
标识名为 log\Illuminate\Log\LogManager 日志服务的实例。

只要理解这三者的关系,我们就可以为Log门面重新注册。

方法一、非默认通道的日志Facade

\App\Supports\Facades\LogCli 代理一个非默认通道日志服务的实例即可,比如 log-cli

<?php

namespace App\Supports\Facades;

use Illuminate\Support\Facades\Facade;

/**
 * @method static \Psr\Log\LoggerInterface channel(string $channel = null)
 * @method static \Psr\Log\LoggerInterface stack(array $channels, string $channel = null)
 * @method static \Psr\Log\LoggerInterface build(array $config)
 * @method static \Illuminate\Log\Logger withContext(array $context = [])
 * @method static \Illuminate\Log\Logger withoutContext()
 * @method static void alert(string $message, array $context = [])
 * @method static void critical(string $message, array $context = [])
 * @method static void debug(string $message, array $context = [])
 * @method static void emergency(string $message, array $context = [])
 * @method static void error(string $message, array $context = [])
 * @method static void info(string $message, array $context = [])
 * @method static void log($level, string $message, array $context = [])
 * @method static void notice(string $message, array $context = [])
 * @method static void warning(string $message, array $context = [])
 * @method static void write(string $level, string $message, array $context = [])
 * @method static void listen(\Closure $callback)
 *
 * @see \Illuminate\Log\Logger
 */
class LogCli extends Facade
{
    public static function getFacadeAccessor(): string
    {
        return 'log-cli';
    }
}

\App\Providers\AppServiceProvider 中注册 log-cli 的实例到 app 容器中

<?php

namespace App\Providers;

use Illuminate\Log\LogManager;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        /**
         * @see \App\Supports\Facades\LogCli
         */
        $this->app->singleton('log-cli', function () {
            return (new LogManager($this->app))->channel('cli');
        });
    }
}

如此后,我们就可以在使用 LogCli::withContext 注册运行周期内的全局上下文参数了。

LogCli::withContext(['traceId' => "traceId will record in all log"]);
// 以下日志都会携带上 traceId 参数
LogCli::info("this is info");
LogCli::warning("this is warning");
LogCli::error("this is error");

方法二、自适应日志Facade

听起来是不是很高大上很优雅?其实就是仍使用 laravelLog FacadeFacade 只是个代理,将 实例 的诸多 方法静态 的风格 暴露 出来 方便调用。何谓自适应呢?让 Log Facadehttp 下使用 default 通道,在 cli 下使用 cli 通道,使用者无需刻意的去指定。
实现起来也很简单,根据运行时环境来判断,决定 log 代理哪个通道的日志服务。

<?php

namespace App\Providers;

use Illuminate\Log\LogManager;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        /**
         * @see \Illuminate\Log\LogManager
         * @see \Illuminate\Log\LogServiceProvider
         * @see \Illuminate\Support\Facades\Log
         * @see \Illuminate\Support\Facades\Log::withContext()
         * cli 环境下,将 log 服务重新注册为 cli 通道的日志对象
         * 主要为了解决 10- 版本下无法为非默认通道注入全局context的问题
         */
        if ('cli' == \PHP_SAPI) {
            $this->app->singleton('log', function () {
                return (new LogManager($this->app))->channel('cli');
            });
        }
    }
}

这样在 http 模式下自动使用 default 通道,console 模式下自动使用 cli 通道。

Log::withContext(['traceId' => "traceId will record in all log"]);
// 以下日志都会携带上 traceId 参数
Log::info("this is info");
Log::warning("this is warning");
Log::error("this is error");

big_cat
1.7k 声望130 粉丝

规范至上