コンテンツにスキップ
Laravel Nightwatchのウェイティングリストにご登録ください。

コンテキスト

はじめに

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 が含まれますが、コンテキストの urltrace_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'));
});
}
lightbulb

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

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