在使用 laravel 的日誌組件(Facade門面模式)時,我們可以通過 withContext 方法為請求週期註冊全局的上下文信息,用來做 RequestID/TraceId 類的請求鏈路追蹤,非常的方便。但在 10- 以下的版本中,withContext 只能為默認日誌通道注入全局上下文,在非默認通道的場景,比如 cli 下, 就無法優雅的實現了。
但如果你瞭解 Service,ServiceProvider,Facade 三者之間的關係,那可以參考如下兩類方法來優雅的實現非默認日誌通道下注冊全局 context 的方法:
門面 \Illuminate\Support\Facades\Log 代理的日誌服務
實為 \Illuminate\Log\LogServiceProvider 向 app 容器中註冊的
標識名為 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
聽起來是不是很高大上很優雅?其實就是仍使用 laravel 的 Log Facade,Facade 只是個代理,將 實例 的諸多 方法 以 靜態 的風格 暴露 出來 方便調用。何謂自適應呢?讓 Log Facade 在 http 下使用 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');
});
// 替換 log 門面
Log::swap(app("log"));
// 全局 with 一個 trace_id
Log::withContext(['trace_id' => Str::uuid()->toString()]);
}
}
}
這樣在 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");