はじめに

Laravelの「コンテキスト」機能は、アプリケーション内で実行されるリクエスト、ジョブ、コマンド全体で情報をキャプチャ、取得、共有することを可能にします。このキャプチャされた情報は、アプリケーションによって書き込まれたログにも含まれ、ログエントリが書き込まれる前に発生した周囲のコード実行履歴に対する深い洞察を提供し、分散システム全体での実行フローを追跡できるようにします。

動作の仕組み

Laravelのコンテキスト機能を理解する最良の方法は、組み込みのロギング機能を使用して実際に見ることです。始めるには、Contextファサードを使用してコンテキストに情報を追加できます。この例では、すべての受信リクエストに対してリクエストURLと一意のトレースIDをコンテキストに追加するためにmiddlewareを使用します:

  1. <?php
  2. namespace App\Http\Middleware;
  3. use Closure;
  4. use Illuminate\Http\Request;
  5. use Illuminate\Support\Facades\Context;
  6. use Illuminate\Support\Str;
  7. use Symfony\Component\HttpFoundation\Response;
  8. class AddContext
  9. {
  10. /**
  11. * Handle an incoming request.
  12. */
  13. public function handle(Request $request, Closure $next): Response
  14. {
  15. Context::add('url', $request->url());
  16. Context::add('trace_id', Str::uuid()->toString());
  17. return $next($request);
  18. }
  19. }

コンテキストに追加された情報は、リクエスト全体で書き込まれる任意のログエントリにメタデータとして自動的に追加されます。コンテキストをメタデータとして追加することで、個々のログエントリに渡された情報とContextを介して共有される情報を区別できます。たとえば、次のログエントリを書いたと想像してください:

  1. Log::info('User authenticated.', ['auth_id' => Auth::id()]);

書き込まれたログには、ログエントリに渡されたauth_idが含まれますが、コンテキストのurltrace_idもメタデータとして含まれます:

  1. User authenticated. {"auth_id":27} {"url":"https://example.com/login","trace_id":"e04e1a11-e75c-4db3-b5b5-cfef4ef56697"}

コンテキストに追加された情報は、キューにディスパッチされたジョブにも利用可能です。たとえば、コンテキストに情報を追加した後、ProcessPodcastジョブをキューにディスパッチすると想像してください:

  1. // In our middleware...
  2. Context::add('url', $request->url());
  3. Context::add('trace_id', Str::uuid()->toString());
  4. // In our controller...
  5. ProcessPodcast::dispatch($podcast);

ジョブがディスパッチされると、現在コンテキストに保存されている情報がキャプチャされ、ジョブと共有されます。キャプチャされた情報は、ジョブが実行されている間に現在のコンテキストに再水和されます。したがって、ジョブのハンドルメソッドがログに書き込む場合:

  1. class ProcessPodcast implements ShouldQueue
  2. {
  3. use Queueable;
  4. // ...
  5. /**
  6. * Execute the job.
  7. */
  8. public function handle(): void
  9. {
  10. Log::info('Processing podcast.', [
  11. 'podcast_id' => $this->podcast->id,
  12. ]);
  13. // ...
  14. }
  15. }

結果として得られるログエントリには、ジョブを元々ディスパッチしたリクエスト中にコンテキストに追加された情報が含まれます:

  1. Processing podcast. {"podcast_id":95} {"url":"https://example.com/login","trace_id":"e04e1a11-e75c-4db3-b5b5-cfef4ef56697"}

Laravelのコンテキストに関連する組み込みのロギング機能に焦点を当てましたが、以下のドキュメントでは、コンテキストがHTTPリクエスト/キューされたジョブの境界を越えて情報を共有する方法や、ログエントリと共に書き込まれない隠れたコンテキストデータを追加する方法を示します。

コンテキストのキャプチャ

現在のコンテキストに情報を保存するには、Contextファサードのaddメソッドを使用します:

  1. use Illuminate\Support\Facades\Context;
  2. Context::add('key', 'value');

複数のアイテムを一度に追加するには、addメソッドに連想配列を渡すことができます:

  1. Context::add([
  2. 'first_key' => 'value',
  3. 'second_key' => 'value',
  4. ]);
  1. ``````php
  2. Context::add('key', 'first');
  3. Context::get('key');
  4. // "first"
  5. Context::addIf('key', 'second');
  6. Context::get('key');
  7. // "first"
  8. `

条件付きコンテキスト

  1. ``````php
  2. use Illuminate\Support\Facades\Auth;
  3. use Illuminate\Support\Facades\Context;
  4. Context::when(
  5. Auth::user()->isAdmin(),
  6. fn ($context) => $context->add('permissions', Auth::user()->permissions),
  7. fn ($context) => $context->add('permissions', []),
  8. );
  9. `

スタック

コンテキストは、「スタック」を作成する機能を提供します。これは、追加された順序でデータを保存するリストです。pushメソッドを呼び出すことでスタックに情報を追加できます:

  1. use Illuminate\Support\Facades\Context;
  2. Context::push('breadcrumbs', 'first_value');
  3. Context::push('breadcrumbs', 'second_value', 'third_value');
  4. Context::get('breadcrumbs');
  5. // [
  6. // 'first_value',
  7. // 'second_value',
  8. // 'third_value',
  9. // ]

スタックは、アプリケーション全体で発生しているイベントなど、リクエストに関する履歴情報をキャプチャするのに役立ちます。たとえば、クエリが実行されるたびにスタックにプッシュするイベントリスナーを作成し、クエリのSQLと期間をタプルとしてキャプチャできます:

  1. use Illuminate\Support\Facades\Context;
  2. use Illuminate\Support\Facades\DB;
  3. DB::listen(function ($event) {
  4. Context::push('queries', [$event->time, $event->sql]);
  5. });
  1. ``````php
  2. if (Context::stackContains('breadcrumbs', 'first_value')) {
  3. //
  4. }
  5. if (Context::hiddenStackContains('secrets', 'first_value')) {
  6. //
  7. }
  8. `
  1. ``````php
  2. use Illuminate\Support\Facades\Context;
  3. use Illuminate\Support\Str;
  4. return Context::stackContains('breadcrumbs', function ($value) {
  5. return Str::startsWith($value, 'query_');
  6. });
  7. `

コンテキストの取得

  1. ``````php
  2. use Illuminate\Support\Facades\Context;
  3. $value = Context::get('key');
  4. `
  1. ``````php
  2. $data = Context::only(['first_key', 'second_key']);
  3. `
  1. ``````php
  2. $value = Context::pull('key');
  3. `

コンテキストに保存されているすべての情報を取得したい場合は、allメソッドを呼び出すことができます:

  1. $data = Context::all();

アイテムの存在確認

  1. ``````php
  2. use Illuminate\Support\Facades\Context;
  3. if (Context::has('key')) {
  4. // ...
  5. }
  6. `
  1. ``````php
  2. Context::add('key', null);
  3. Context::has('key');
  4. // true
  5. `

コンテキストの削除

  1. ``````php
  2. use Illuminate\Support\Facades\Context;
  3. Context::add(['first_key' => 1, 'second_key' => 2]);
  4. Context::forget('first_key');
  5. Context::all();
  6. // ['second_key' => 2]
  7. `
  1. ``````php
  2. Context::forget(['first_key', 'second_key']);
  3. `

隠れたコンテキスト

コンテキストは、「隠れた」データを保存する機能を提供します。この隠れた情報はログに追加されず、上記のデータ取得メソッドを介してアクセスできません。コンテキストは、隠れたコンテキスト情報と対話するための異なるメソッドセットを提供します:

  1. use Illuminate\Support\Facades\Context;
  2. Context::addHidden('key', 'value');
  3. Context::getHidden('key');
  4. // 'value'
  5. Context::get('key');
  6. // null

「隠れた」メソッドは、上記の非隠れたメソッドの機能を反映しています:

  1. Context::addHidden(/* ... */);
  2. Context::addHiddenIf(/* ... */);
  3. Context::pushHidden(/* ... */);
  4. Context::getHidden(/* ... */);
  5. Context::pullHidden(/* ... */);
  6. Context::onlyHidden(/* ... */);
  7. Context::allHidden(/* ... */);
  8. Context::hasHidden(/* ... */);
  9. Context::forgetHidden(/* ... */);

イベント

コンテキストは、コンテキストの水和および脱水プロセスにフックするための2つのイベントをディスパッチします。

これらのイベントがどのように使用されるかを示すために、アプリケーションのミドルウェアで、受信したHTTPリクエストのAccept-Languageヘッダーに基づいてapp.locale設定値を設定したと想像してください。コンテキストのイベントは、リクエスト中にこの値をキャプチャし、キューで復元することを可能にし、キューで送信される通知が正しいapp.locale値を持つことを保証します。コンテキストのイベントと隠れたデータを使用してこれを達成できます。以下のドキュメントで説明します。

脱水

ジョブがキューにディスパッチされるたびに、コンテキスト内のデータは「脱水」され、ジョブのペイロードと一緒にキャプチャされます。Context::dehydratingメソッドを使用すると、脱水プロセス中に呼び出されるクロージャを登録できます。このクロージャ内で、キューに送信されるジョブと共有されるデータを変更できます。

通常、dehydratingコールバックをアプリケーションのAppServiceProviderクラスのbootメソッド内で登録する必要があります:

  1. use Illuminate\Log\Context\Repository;
  2. use Illuminate\Support\Facades\Config;
  3. use Illuminate\Support\Facades\Context;
  4. /**
  5. * Bootstrap any application services.
  6. */
  7. public function boot(): void
  8. {
  9. Context::dehydrating(function (Repository $context) {
  10. $context->addHidden('locale', Config::get('app.locale'));
  11. });
  12. }
  1. <a name="hydrated"></a>
  2. ### 水和
  3. キューされたジョブがキューで実行を開始すると、ジョブと共有されたコンテキストは現在のコンテキストに「水和」されます。`````Context::hydrated`````メソッドを使用すると、水和プロセス中に呼び出されるクロージャを登録できます。
  4. 通常、`````hydrated`````コールバックをアプリケーションの`````AppServiceProvider`````クラスの`````boot`````メソッド内で登録する必要があります:
  5. ``````php
  6. use Illuminate\Log\Context\Repository;
  7. use Illuminate\Support\Facades\Config;
  8. use Illuminate\Support\Facades\Context;
  9. /**
  10. * Bootstrap any application services.
  11. */
  12. public function boot(): void
  13. {
  14. Context::hydrated(function (Repository $context) {
  15. if ($context->hasHidden('locale')) {
  16. Config::set('app.locale', $context->getHidden('locale'));
  17. }
  18. });
  19. }
  20. `

hydratedコールバック内でContextファサードを使用しないでください。代わりに、コールバックに渡されたリポジトリにのみ変更を加えるようにしてください。