コンテキスト
はじめに
Laravelの「コンテキスト」機能を使用すると、アプリケーション内で実行されているリクエスト、ジョブ、およびコマンド全体で情報をキャプチャ、取得、および共有できます。このキャプチャされた情報は、アプリケーションによって書き込まれたログにも含まれ、ログエントリが書き込まれる前に行われた周囲のコード実行履歴をより深く理解し、分散システム全体の eseguzione フローを追跡できます。
仕組み
Laravelのコンテキスト機能を理解する最良の方法は、組み込みのログ機能を使用して実際に確認することです。開始するには、Context
ファサードを使用してコンテキストに情報を追加できます。この例では、ミドルウェアを使用して、受信するすべてのリクエストのコンテキストにリクエストURLと一意のトレースIDを追加します
<?php namespace App\Http\Middleware; use Closure;use Illuminate\Http\Request;use Illuminate\Support\Facades\Context;use Illuminate\Support\Str;use Symfony\Component\HttpFoundation\Response; class AddContext{ /** * Handle an incoming request. */ public function handle(Request $request, Closure $next): Response { Context::add('url', $request->url()); Context::add('trace_id', Str::uuid()->toString()); return $next($request); }}
コンテキストに追加された情報は、リクエスト全体で書き込まれるログエントリにメタデータとして自動的に追加されます。コンテキストをメタデータとして追加することで、個々のログエントリに渡される情報を、Context
を介して共有される情報と区別できます。たとえば、次のログエントリを書き込むとします
Log::info('User authenticated.', ['auth_id' => Auth::id()]);
書き込まれたログには、ログエントリに渡された auth_id
が含まれますが、コンテキストの url
と trace_id
もメタデータとして含まれます
User authenticated. {"auth_id":27} {"url":"https://example.com/login","trace_id":"e04e1a11-e75c-4db3-b5b5-cfef4ef56697"}
コンテキストに追加された情報は、キューにディスパッチされたジョブでも使用できます。たとえば、コンテキストに情報を追加した後、ProcessPodcast
ジョブをキューにディスパッチするとします
// In our middleware...Context::add('url', $request->url());Context::add('trace_id', Str::uuid()->toString()); // In our controller...ProcessPodcast::dispatch($podcast);
ジョブがディスパッチされると、コンテキストに現在保存されている情報がキャプチャされ、ジョブと共有されます。キャプチャされた情報は、ジョブの実行中に現在のコンテキストにハイドレートされます。そのため、ジョブのハンドルメソッドがログに書き込む場合
class ProcessPodcast implements ShouldQueue{ use Queueable; // ... /** * Execute the job. */ public function handle(): void { Log::info('Processing podcast.', [ 'podcast_id' => $this->podcast->id, ]); // ... }}
結果のログエントリには、ジョブを最初にディスパッチしたリクエスト中にコンテキストに追加された情報が含まれます
Processing podcast. {"podcast_id":95} {"url":"https://example.com/login","trace_id":"e04e1a11-e75c-4db3-b5b5-cfef4ef56697"}
Laravelのコンテキストの組み込みログ関連機能に焦点を当ててきましたが、以下のドキュメントでは、コンテキストを使用してHTTPリクエスト/キューに入れられたジョブの境界を越えて情報を共有する方法、およびログエントリに書き込まれない非表示のコンテキストデータを追加する方法について説明します。
コンテキストのキャプチャ
Context
ファサードの add
メソッドを使用して、現在のコンテキストに情報を保存できます
use Illuminate\Support\Facades\Context; Context::add('key', 'value');
複数の項目を一度に追加するには、連想配列を add
メソッドに渡します
Context::add([ 'first_key' => 'value', 'second_key' => 'value',]);
add
メソッドは、同じキーを共有する既存の値を上書きします。キーがまだ存在しない場合にのみコンテキストに情報を追加する場合は、addIf
メソッドを使用できます
Context::add('key', 'first'); Context::get('key');// "first" Context::addIf('key', 'second'); Context::get('key');// "first"
条件付きコンテキスト
when
メソッドを使用して、指定された条件に基づいてコンテキストにデータを追加できます。指定された条件が true
と評価された場合、when
メソッドに提供された最初のクロージャが呼び出され、条件が false
と評価された場合は2番目のクロージャが呼び出されます
use Illuminate\Support\Facades\Auth;use Illuminate\Support\Facades\Context; Context::when( Auth::user()->isAdmin(), fn ($context) => $context->add('permissions', Auth::user()->permissions), fn ($context) => $context->add('permissions', []),);
スタック
コンテキストでは、「スタック」を作成できます。スタックは、追加された順序で保存されるデータのリストです。push
メソッドを呼び出すことによって、スタックに情報を追加できます
use Illuminate\Support\Facades\Context; Context::push('breadcrumbs', 'first_value'); Context::push('breadcrumbs', 'second_value', 'third_value'); Context::get('breadcrumbs');// [// 'first_value',// 'second_value',// 'third_value',// ]
スタックは、アプリケーション全体で発生しているイベントなど、リクエストに関する履歴情報をキャプチャするのに役立ちます。たとえば、クエリが実行されるたびにスタックにプッシュするイベントリスナーを作成し、クエリSQLと期間をタプルとしてキャプチャできます
use Illuminate\Support\Facades\Context;use Illuminate\Support\Facades\DB; DB::listen(function ($event) { Context::push('queries', [$event->time, $event->sql]);});
stackContains
メソッドと hiddenStackContains
メソッドを使用して、値がスタックにあるかどうかを判断できます
if (Context::stackContains('breadcrumbs', 'first_value')) { //} if (Context::hiddenStackContains('secrets', 'first_value')) { //}
stackContains
メソッドと hiddenStackContains
メソッドは、2番目の引数としてクロージャを受け入れるため、値の比較操作をより詳細に制御できます
use Illuminate\Support\Facades\Context;use Illuminate\Support\Str; return Context::stackContains('breadcrumbs', function ($value) { return Str::startsWith($value, 'query_');});
コンテキストの取得
Context
ファサードの get
メソッドを使用して、コンテキストから情報を取得できます
use Illuminate\Support\Facades\Context; $value = Context::get('key');
only
メソッドを使用して、コンテキスト内の情報のサブセットを取得できます
$data = Context::only(['first_key', 'second_key']);
pull
メソッドを使用して、コンテキストから情報を取得し、コンテキストからすぐに削除できます
$value = Context::pull('key');
コンテキストデータがスタックに保存されている場合は、pop
メソッドを使用してスタックから項目をポップできます
Context::push('breadcrumbs', 'first_value', 'second_value'); Context::pop('breadcrumbs')// second_value Context::get('breadcrumbs');// ['first_value']
コンテキストに保存されているすべての情報を取得する場合は、all
メソッドを呼び出すことができます
$data = Context::all();
項目の存在確認
has
メソッドを使用して、コンテキストに指定されたキーの値が保存されているかどうかを判断できます
use Illuminate\Support\Facades\Context; if (Context::has('key')) { // ...}
has
メソッドは、保存されている値に関係なく true
を返します。そのため、たとえば、null
値を持つキーは存在すると見なされます
Context::add('key', null); Context::has('key');// true
コンテキストの削除
forget
メソッドを使用して、現在のコンテキストからキーとその値を削除できます
use Illuminate\Support\Facades\Context; Context::add(['first_key' => 1, 'second_key' => 2]); Context::forget('first_key'); Context::all(); // ['second_key' => 2]
forget
メソッドに配列を提供することにより、一度に複数のキーを忘れることができます
Context::forget(['first_key', 'second_key']);
非表示コンテキスト
コンテキストでは、「非表示」データを保存できます。この非表示情報はログに追加されず、上記で説明したデータ取得メソッドではアクセスできません。コンテキストは、非表示のコンテキスト情報を操作するための別のメソッドセットを提供します
use Illuminate\Support\Facades\Context; Context::addHidden('key', 'value'); Context::getHidden('key');// 'value' Context::get('key');// null
「非表示」メソッドは、上記で説明した非表示ではないメソッドの機能を反映しています
Context::addHidden(/* ... */);Context::addHiddenIf(/* ... */);Context::pushHidden(/* ... */);Context::getHidden(/* ... */);Context::pullHidden(/* ... */);Context::popHidden(/* ... */);Context::onlyHidden(/* ... */);Context::allHidden(/* ... */);Context::hasHidden(/* ... */);Context::forgetHidden(/* ... */);
イベント
コンテキストは、コンテキストのハイドレーションとデハイドレーションプロセスにフックできる2つのイベントをディスパッチします。
これらのイベントの使用方法を説明するために、アプリケーションのミドルウェアで、受信HTTPリクエストの Accept-Language
ヘッダーに基づいて app.locale
構成値を設定するとします。コンテキストのイベントを使用すると、リクエスト中にこの値をキャプチャし、キューで復元して、キューで送信された通知に正しい app.locale
値があることを確認できます。コンテキストのイベントと非表示データを使用してこれを実現できます。以下のドキュメントで説明します。
デハイドレーション
ジョブがキューにディスパッチされるたびに、コンテキスト内のデータは「デハイドレート」され、ジョブのペイロードとともにキャプチャされます。Context::dehydrating
メソッドを使用すると、デハイドレーションプロセス中に呼び出されるクロージャを登録できます。このクロージャ内では、キューに入れられたジョブと共有されるデータに変更を加えることができます。
通常、アプリケーションの AppServiceProvider
クラスの boot
メソッド内に dehydrating
コールバックを登録する必要があります
use Illuminate\Log\Context\Repository;use Illuminate\Support\Facades\Config;use Illuminate\Support\Facades\Context; /** * Bootstrap any application services. */public function boot(): void{ Context::dehydrating(function (Repository $context) { $context->addHidden('locale', Config::get('app.locale')); });}
dehydrating
コールバック内で Context
ファサードを使用しないでください。現在のプロセスのコンテキストが変更されるためです。コールバックに渡されたリポジトリのみを変更するようにしてください。
ハイドレーション
キューに登録されたジョブがキューで実行を開始するたびに、ジョブと共有されていたコンテキストは現在のコンテキストに「ハイドレート」されます。 Context::hydrated
メソッドを使用すると、ハイドレーションプロセス中に呼び出されるクロージャを登録できます。
通常、hydrated
コールバックは、アプリケーションの AppServiceProvider
クラスの boot
メソッド内に登録する必要があります。
use Illuminate\Log\Context\Repository;use Illuminate\Support\Facades\Config;use Illuminate\Support\Facades\Context; /** * Bootstrap any application services. */public function boot(): void{ Context::hydrated(function (Repository $context) { if ($context->hasHidden('locale')) { Config::set('app.locale', $context->getHidden('locale')); } });}
hydrated
コールバック内で Context
ファサードを使用しないでください。代わりに、コールバックに渡されたリポジトリのみを変更するようにしてください。