はじめに
Laravelの「コンテキスト」機能は、アプリケーション内で実行されるリクエスト、ジョブ、コマンド全体で情報をキャプチャ、取得、共有することを可能にします。このキャプチャされた情報は、アプリケーションによって書き込まれたログにも含まれ、ログエントリが書き込まれる前に発生した周囲のコード実行履歴に対する深い洞察を提供し、分散システム全体での実行フローを追跡できるようにします。
動作の仕組み
Laravelのコンテキスト機能を理解する最良の方法は、組み込みのロギング機能を使用して実際に見ることです。始めるには、Context
ファサードを使用してコンテキストに情報を追加できます。この例では、すべての受信リクエストに対してリクエストURLと一意のトレースIDをコンテキストに追加するためにmiddlewareを使用します:
<?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',
]);
``````php
Context::add('key', 'first');
Context::get('key');
// "first"
Context::addIf('key', 'second');
Context::get('key');
// "first"
`
条件付きコンテキスト
``````php
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]);
});
``````php
if (Context::stackContains('breadcrumbs', 'first_value')) {
//
}
if (Context::hiddenStackContains('secrets', 'first_value')) {
//
}
`
``````php
use Illuminate\Support\Facades\Context;
use Illuminate\Support\Str;
return Context::stackContains('breadcrumbs', function ($value) {
return Str::startsWith($value, 'query_');
});
`
コンテキストの取得
``````php
use Illuminate\Support\Facades\Context;
$value = Context::get('key');
`
``````php
$data = Context::only(['first_key', 'second_key']);
`
``````php
$value = Context::pull('key');
`
コンテキストに保存されているすべての情報を取得したい場合は、all
メソッドを呼び出すことができます:
$data = Context::all();
アイテムの存在確認
``````php
use Illuminate\Support\Facades\Context;
if (Context::has('key')) {
// ...
}
`
``````php
Context::add('key', null);
Context::has('key');
// true
`
コンテキストの削除
``````php
use Illuminate\Support\Facades\Context;
Context::add(['first_key' => 1, 'second_key' => 2]);
Context::forget('first_key');
Context::all();
// ['second_key' => 2]
`
``````php
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::onlyHidden(/* ... */);
Context::allHidden(/* ... */);
Context::hasHidden(/* ... */);
Context::forgetHidden(/* ... */);
イベント
コンテキストは、コンテキストの水和および脱水プロセスにフックするための2つのイベントをディスパッチします。
これらのイベントがどのように使用されるかを示すために、アプリケーションのミドルウェアで、受信したHTTPリクエストのAccept-Language
ヘッダーに基づいてapp.locale
設定値を設定したと想像してください。コンテキストのイベントは、リクエスト中にこの値をキャプチャし、キューで復元することを可能にし、キューで送信される通知が正しいapp.locale
値を持つことを保証します。コンテキストのイベントと隠れたデータを使用してこれを達成できます。以下のドキュメントで説明します。
脱水
ジョブがキューにディスパッチされるたびに、コンテキスト内のデータは「脱水」され、ジョブのペイロードと一緒にキャプチャされます。Context::dehydrating
メソッドを使用すると、脱水プロセス中に呼び出されるクロージャを登録できます。このクロージャ内で、キューに送信されるジョブと共有されるデータを変更できます。
通常、dehydrating
コールバックをアプリケーションの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::dehydrating(function (Repository $context) {
$context->addHidden('locale', Config::get('app.locale'));
});
}
<a name="hydrated"></a>
### 水和
キューされたジョブがキューで実行を開始すると、ジョブと共有されたコンテキストは現在のコンテキストに「水和」されます。`````Context::hydrated`````メソッドを使用すると、水和プロセス中に呼び出されるクロージャを登録できます。
通常、`````hydrated`````コールバックをアプリケーションの`````AppServiceProvider`````クラスの`````boot`````メソッド内で登録する必要があります:
``````php
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
ファサードを使用しないでください。代わりに、コールバックに渡されたリポジトリにのみ変更を加えるようにしてください。