
Laravel 11で導入されたContextファサードは、この散らかったログに共通のメタデータを付与し、リクエストからキューイングされたジョブまでデータを引き継ぐための仕組みです。laravel 11 context ログ 連携で調べている人が期待するような、何でも自動的に追跡してくれる魔法の機能ではありません。ですが、ログへ手作業で同じ情報を付け続けるという、地味で壊れやすい仕事をかなり減らせます。
ここでは、Laravel 11のContextが何を保持し、ログにどう反映され、キューへどのように引き継がれるのかを、従来のLog::withContext()やLog::shareContext()との違いも含めて整理します。
Contextファサードが解決するログ出力の課題
LaravelでログへユーザーIDやリクエストIDを付けたい場合、これまではログを書き込む箇所ごとに追加情報を渡す方法が一般的でした。
たとえば、次のような処理です。
Log::info('注文を作成しました', [
'request_id' => $requestId,
'user_id' => $userId,
]);ただし、この書き方は最初のうちは問題ありません。問題になるのは、アプリケーションが育ったあとです。
コントローラーではrequest_idを付けているのに、サービスクラスでは付け忘れる。例外処理では別のロガーを使ってしまう。イベントリスナーや通知処理では、そもそもリクエストIDを引数として受け取っていない。そうして同じリクエストに関係するログが、少しずつ別人のような顔をし始めます。
「共通のログ情報」を各メソッドへ引数で渡せば解決する、という設計もあります。理屈は正しい。しかし、現場ではその引数がサービス、リポジトリ、イベント、ジョブへと延々と渡されます。途中の一箇所で落とすと終わりです。型で守れる部分もありますが、ログ用のメタデータをアプリケーションの主要なデータフローへ混ぜる設計は、結局のところ保守コストになります。
Laravel 11のContextは、現在の実行サイクルにメタデータを保存し、アプリケーションが書き込むログへ自動的に添付します。基本的な追加は、Context::add('key', 'value')です。ファサードの完全修飾名はIlluminate\Support\Facades\Contextになります。
たとえば、リクエストの入口付近で次のように情報を追加します。
Context::add('request_id', $requestId);
以降、そのリクエストサイクル中にLaravelが出力するログへ、Contextのデータがメタデータとして付与されます。個々のLog::info()へ毎回request_idを書かなくても、ログの検索に使える共通情報を持たせられるわけです。
ここで少し注意が必要です。Contextがログへ追加するのは、ログ本文を書き換えることではありません。ログメッセージそのものにIDを埋め込むのではなく、ログレコードへメタデータを添付する仕組みです。ログフォーマットや利用しているログドライバーによって見え方は変わります。
Contextはログを賢くする機能ではない。散らかったログに、あとから辿れる手掛かりを最初から付けておくための仕組みだ。
リクエストIDをどこで追加するか
Contextの使い方で最初に迷うのは、どの層でデータを追加するかです。コントローラーの冒頭でも動きますが、実際にはミドルウェアのようなリクエスト全体を見渡せる場所が扱いやすいでしょう。
リクエストIDを追加する場所には、次のような候補があります。
- リクエストを受け取った直後に、既存のヘッダー値またはアプリケーション側で生成したIDを登録する
- 認証済みユーザーが確定した段階で、ユーザーIDやテナントIDを追加する
- コントローラーではなく、共通ミドルウェアで全ルートへ適用する
- 外部APIを呼び出す前に、外部サービス名や処理区分を追加する
どれが正解かは、アプリケーションの境界によって変わります。すべてを最初から追加する必要はありません。ログを検索する際に実際に使う情報だけを選びます。
リクエストID、ユーザーID、テナントID、処理対象の注文IDあたりは候補になります。一方で、リクエストパラメータ全体や認証トークンのような情報をContextへ入れるのは避けるべきです。Contextは便利なので、放っておくと何でも入れたくなります。そこには、便利な箱がいつの間にか個人情報と巨大な配列を抱え込むという罠がある。
Contextへ追加する情報は、ログへ出ても問題がないものに絞る。結局のところ、これはセキュリティ設計の一部です。
Laravel 11のContextは何を保持するのか
Laravel 11のContextは、リクエスト、キューイングされたジョブ、コマンド実行といった処理の流れにまたがってデータを保持・共有します。
従来のLog::withContext()やLog::shareContext()も、ログに共通データを付ける目的では使えました。Laravel 11のContextが違うのは、ログ出力だけに閉じず、処理の実行コンテキストとしてデータを扱い、キューへ投入されるジョブへの引き継ぎも標準の仕組みに含まれている点です。
整理すると、次のようになります。
| 観点 | Log::withContext() | Log::shareContext() | Contextファサード |
|---|---|---|---|
| 主な目的 | 特定のログ出力へ共通情報を追加 | ロガーへ共通情報を共有 | 実行コンテキストとログ情報を共有 |
| ログへの付与 | 対象のロガーに依存 | 共有対象のロガーに付与 | アプリケーションのログへ自動付与 |
| ジョブへの引き継ぎ | 自動連携を前提にしない | 自動連携を前提にしない | リクエスト中に投入したジョブへ引き継ぎ |
| 利用範囲 | ログ中心 | ログ中心 | リクエスト、ジョブ、コマンドを横断 |
| Laravel 11での位置付け | 既存の手段 | 既存の手段 | 標準提供される統合的な仕組み |
ここでいう「横断」は、処理が別プロセスになっても、すべての状態が無条件で共有されるという意味ではありません。HTTPリクエストの中でジョブをディスパッチした場合、その時点のContext情報がジョブへ引き継がれる、という設計です。
キューのワーカーが別のプロセスで動いていても、ジョブの実行時にContextが復元されることで、元のリクエストとジョブのログを関連付けられます。これは、ジョブへrequest_idを明示的に渡す設計とは別のアプローチです。
Context::add()でメタデータを登録する
最も基本的な使い方は、Contextへキーと値を追加することです。
Context::add('request_id', $requestId);
複数の値を登録する場合も、同じように必要なキーを追加します。
Context::add('user_id', $userId);
Context::add('tenant_id', $tenantId);
この情報は、以降のログに追加されるコンテキストデータとして扱われます。たとえば、注文処理の深い場所にあるサービスクラスでLog::warning('在庫確認に失敗しました')とだけ書いていても、Contextに登録されたリクエストIDやユーザーIDがログ側へ付加されます。
この設計の良いところは、ドメインロジックへログ追跡用の引数を過剰に渡さなくて済むことです。注文作成という業務処理の引数に、ログのためだけのrequestIdを混ぜる必要がありません。
もちろん、すべてをContextに押し込むのも違います。Contextは業務データの受け渡しを代替するものではありません。注文金額や権限判定に必要な値をContextから読むようにすると、依存関係が見えなくなります。
使い分けは比較的単純です。
1. 業務処理の結果や判断に必要な値は、通常の引数やDTOで渡す
2. ログ追跡や処理経路の識別に使う値は、Contextへ登録する
3. 外部システムへ渡す必要がある値は、Context任せにせず明示的に変換する
4. セキュリティや個人情報に関係する値は、ログ出力の可否を確認してから登録する
Contextは便利な共有領域ですが、暗黙の依存を増やす道具でもあります。便利だからといって、サービスクラスがContextの中身に依存し始めると、テストの準備が急に面倒になります。現場では、ログの相関に限定するくらいがちょうどいいでしょう。
ログへのメタデータ自動付与をどう検証するか
Contextの導入で期待されるのは、ログに共通データが付くことです。しかし、「付くはず」で終わらせると、ログフォーマットを変更したときや別の出力先を追加したときに簡単に壊れます。
まず確認したいのは、Contextへ追加したデータが、通常のアプリケーションログへどのように出力されるかです。Context::add()の直後にログを一行出し、その内容を確認します。次に、サービスクラスやイベントリスナーなど、Contextを直接参照しない場所からログを出します。
この二段階を分けるのがポイントです。
- Contextを追加した場所から出したログ
- Contextを知らない別クラスから出したログ
- 例外処理で出したログ
- データベース処理や外部API呼び出し周辺のログ
- キュー投入前に出したログ
個々のログ呼び出しにメタデータを渡していないのに、共通の情報が付いていることを確認できれば、Contextの基本的な連携は機能しています。
ログ本文とメタデータを混同しない
ログを見るとき、開発者はつい一行の文字列だけに注目します。しかし、Contextの情報はログのメタデータとして扱われるため、ログ出力先やフォーマットによって確認方法が変わります。
JSON形式のログであれば、request_idやuser_idがフィールドとして見えるでしょう。テキスト形式では、プロセッサーやハンドラーの設定によって表示位置が変わることがあります。開発環境では見えているのに、本番のログ集約基盤では検索対象になっていない、という事態も起こります。
これはContextの問題というより、ログ基盤側の問題です。アプリケーションがメタデータを出していても、収集側が本文しか取り込まなければ検索できません。
特に確認したいのは次の点です。
- ログが構造化形式で保存されているか
- Contextのキーがログのフィールドとして保持されているか
- ログ集約基盤でキー検索できるか
- 例外ログにも同じメタデータが付くか
- ローカル、ステージング、本番でフォーマットが一致しているか
「ログに出た」だけでは、運用上の価値はまだ半分です。障害時にrequest_idで関連ログを絞り込めるところまで確認して、初めて連携が完成します。
Contextへ入れる値の粒度
Contextへ登録する値は、検索しやすく、漏れても被害を広げにくい粒度にします。
たとえば、次のような値は比較的扱いやすいでしょう。
- リクエストを一意に識別するID
- 認証済みユーザーの内部ID
- テナントを識別するID
- 処理の種類を表す固定的な区分
- 呼び出し元となる内部サービス名
- 外部API連携の処理識別子
逆に、次のような値はContextへ直接保存しない方が安全です。
- パスワードやアクセストークン
- Cookieや認証ヘッダーの全体
- リクエストボディの全量
- 大きなモデルオブジェクト
- 画像やファイルなどのバイナリ
- ログへ出す必要のない個人情報
大量のオブジェクトや巨大なデータを入れても性能影響がない、と言い切れる仕組みではありません。ログは処理のたびに書かれる可能性があります。何気なく追加した配列が、ログファイルのサイズを膨らませ、保管コストと検索性能を同時に悪化させることがあります。
力技だが、最初はrequest_idだけで始めるのが堅実です。必要になったらtenant_idやjob_typeを追加する。観測したい情報を一度に全部詰め込まない。ログは多ければ多いほど役に立つわけではありません。
ログの相関に必要なのは、情報量ではなく、同じ処理を確実に見分けられる少数のキーだ。
キューイングジョブへデータを引き継ぐ仕組み
Contextの価値が最も分かりやすく現れるのが、キューイングジョブとの連携です。
HTTPリクエストの中で、注文処理やメール送信、外部サービスとの同期ジョブをキューへ投入するケースを考えます。リクエストのログにはリクエストIDが付いている。ジョブのログにはジョブIDしかない。これまでは、両者を関連付けるために、リクエストIDをジョブのコンストラクターへ渡す設計が必要でした。
もちろん、明示的に渡す方法は今でも有効です。業務上必要なIDであれば、むしろ明示的に渡すべきです。ただ、ログ追跡専用の情報まで毎回引数に含めると、ジョブの定義が監視要件に引っ張られます。
Laravel 11のContextでは、リクエスト中にContextへ追加した情報が、そのリクエストサイクル内でディスパッチされたキューイングジョブへ自動的に引き継がれます。これにより、元のリクエストとジョブのログを同じリクエストIDで追跡できます。
処理の流れは、概念的には次のようになります。
1. リクエストの開始時にrequest_idをContextへ追加する
2. リクエスト内で注文処理やイベント発火を実行する
3. イベントやサービスからキューイングジョブをディスパッチする
4. ジョブが実行されると、Contextの情報が利用可能になる
5. ジョブが出力するログにも、元の処理に関係するメタデータが付く
ここで「キューへ積んだすべてのジョブが、永遠に同じContextを持つ」と考えると事故になります。引き継ぎの起点は、リクエストサイクル内でディスパッチされたジョブです。ジョブが別のジョブを後から投入する場合や、コマンドから直接投入する場合などは、実際の実行経路を確認する必要があります。
ジョブの業務データとContextのデータは分ける
たとえば注文メール送信ジョブが、注文IDとリクエストIDを持っているとします。
注文IDは、ジョブが実行されるために必要な業務データです。ジョブのプロパティやコンストラクターで明示的に渡すべきです。リクエストIDは、ログを追跡するためのメタデータです。こちらはContextによる引き継ぎの対象にできます。
この区別を曖昧にすると、ジョブの再実行で困ります。
キューのジョブは、失敗後に再試行されたり、管理画面やコマンドから手動で実行されたりします。常に元のHTTPリクエストが存在するとは限りません。注文IDがなければ処理できないジョブは、Contextではなく明示的な引数として持つ必要があります。
一方、リクエストIDが存在しない場合は、ジョブ単体の実行IDや処理区分を使ってログを追跡する設計にします。Contextが空だからといって、業務処理そのものを止めるべきではありません。
この境界を表にすると、次のようになります。
| 情報 | 役割 | 渡し方 |
|---|---|---|
| 注文ID | ジョブが処理する対象 | ジョブの引数として明示的に渡す |
| ユーザーID | 業務上の権限や通知先の判定 | 必要な処理へ明示的に渡す |
| リクエストID | ログを関連付ける識別子 | Contextへ追加する |
| テナントID | 業務処理の対象範囲 | 業務要件に応じて明示的に渡す |
| 処理区分 | ログ検索や運用上の分類 | Contextへ追加する |
| 認証トークン | 外部連携の認証情報 | Contextへ保存しない |
Contextを業務データの代わりに使わない。この線引きを守るだけで、後からコードを読む人の負担はかなり減ります。
リトライ時に何を追跡するか
ジョブが失敗して再試行された場合、元のリクエストIDを残したいことがあります。たとえば、ユーザー操作を起点にした外部API連携が失敗し、ワーカーが再試行しているケースです。
この場合、元のリクエストとの関係を追跡できることには意味があります。ただし、リトライ一回目と二回目を区別できなければ、ログがまた混ざります。
そのため、運用上は次のような情報を分けて考えます。
- リクエストを識別するID
- ジョブそのものを識別するID
- リトライ回数
- 処理対象の業務ID
- 外部サービス側のリクエスト識別子
Contextは、このうちログの共通相関に向いた情報を持たせる場所です。ジョブ固有の状態や再試行回数までContextだけで表現しようとすると、ジョブの実行モデルとログの追跡モデルが混ざります。
Laravel 11のContextは、トレースIDやリクエストURLなどを共有する用途にも使えます。ただし、外部のAPMサービスやエラー監視サービスがContextの値をどこまで自動同期するかは、利用するツールと設定に依存します。Contextへ登録したからといって、DatadogやSentryなどの外部サービスへすべてが自動連携されると決めつけるのは危険です。
必要なら、ログ出力とは別に、APM側のコンテキスト登録処理を用意します。標準機能の範囲と、外部ツールの連携範囲は分けて確認する。ここを混同すると、本番障害のときだけIDが消えるという、ありがたくない謎のバグになります。
Log::withContext()からの移行をどう考えるか
Laravel 11以前のアプリケーションでは、Log::withContext()やLog::shareContext()を使って共通のログ情報を付けていることがあります。すでに安定して動作しているなら、Contextへ急いで移行しなければならないわけではありません。
Laravel 8.49以降などでも、これらの仕組みによってログのメタデータを共有することは可能でした。つまり、Contextの導入は「これまでログへ共通情報を付けられなかった」という話ではありません。
違いは、ログに情報を付ける仕組みを、リクエストやジョブの実行コンテキストへ広げやすくなったことです。
従来の方法が向いているケース
Log::withContext()は、ある処理の中でロガーへ追加情報を渡したい場合に分かりやすい方法です。対象範囲が狭く、ログ出力だけに関心があるなら、あえて置き換える理由はありません。
たとえば、一つのサービスメソッド内で外部APIの応答を記録したい場合、必要な情報をそのロガーへ渡す方が意図が明確なこともあります。Contextを使うと、メソッドの外側にまで情報が残るため、適用範囲を広げたくない処理では従来の方法が扱いやすいでしょう。
Log::shareContext()も、アプリケーションのロガーへ共通情報を共有する目的では有効です。古いLaravelバージョンを保守している場合、Contextファサードをそのまま利用できると考えない方が安全です。Laravel 10以下を含む既存案件へ、Laravel 11の実装をそのまま持ち込むことはできません。
Contextへ移行する価値があるケース
次のような要件があるなら、Contextへの移行を検討する価値があります。
- HTTPリクエストとキューのログを同じIDで追跡したい
- ログ用の共通情報を各クラスへ引数で渡したくない
- ログ出力箇所の増加に伴う付与漏れを減らしたい
- リクエスト、ジョブ、コマンドで共通の観測情報を扱いたい
- Laravel 11以降へのアップデートを済ませ、標準機能へ寄せたい
ただし、既存コードを一気に置き換える必要はありません。まず新しく追加する処理だけContextを使い、古いログには従来の方法を残す段階的な移行でも構いません。
移行でありがちな失敗は、全ログの書き方を一度に変えようとすることです。ログはアプリケーションの至るところにあります。テスト、例外処理、ジョブ、通知、イベント、バッチ。横断的な変更を一括で行うと、修正そのものより、どこでログの形が変わったのかを確認する作業が重くなります。
現実的には、次の順番が無難です。
1. 現在ログ検索に使っている共通キーを洗い出す
2. リクエストの入口でContextへ追加する
3. 代表的なサービスと例外処理のログを確認する
4. キューへ投入される処理で引き継ぎを確認する
5. ログ集約基盤で検索できることを確認する
6. 新規コードからContextを標準にする
7. 効果が確認できた範囲だけ既存コードを置き換える
古い書き方が残っていること自体は、直ちに問題ではありません。ContextとLog::withContext()を同じアプリケーション内で使う場合は、キーの命名と上書きルールを決めておけばよいでしょう。
実装前に決めておきたい運用ルール
Contextの実装は、コード数だけを見れば大きなものではありません。難しいのは、何を入れるか、いつ入れるか、どのログを同じ処理として扱うかです。
特に個人開発や少人数チームのアプリケーションでは、ログ設計が後回しになりがちです。障害が起きたらサーバーへ入り、時刻でログを眺める。最初はそれでも何とかなります。しかし、外部API、キュー、定期実行、管理画面が増えると、時刻だけでは追い切れません。
キー名を場当たり的に増やさない
request_idとrequestId、trace_idとtraceIdが混在すると、検索側で余計な変換が必要になります。Contextへ登録するキーは、プロジェクト内で命名規則を固定します。
よく使うキーを決める場合でも、すべてを標準化する必要はありません。まずは次のような最小構成から始めるとよいでしょう。
request_id:HTTPリクエストの相関に使うIDuser_id:認証済みユーザーの内部識別子tenant_id:マルチテナント構成での対象識別子operation:処理の種類job_id:ジョブを個別に追跡するための識別子
ただし、job_idのようにLaravelやキュー基盤が別の識別子を持つ場合は、同じ意味の値を二重に記録しないようにします。ログに同じようなIDが複数あると、見た目の情報量は増えますが、障害対応時の判断はむしろ遅くなります。
Contextの寿命を意識する
HTTPリクエストでは、基本的に一つのリクエストサイクルがContextの単位になります。CLIコマンドやキューワーカーでは、プロセスが長く動き続けるため、処理ごとの境界を意識する必要があります。
ワーカーは一度起動すると、複数のジョブを連続して処理します。前のジョブの情報が次のジョブへ意図せず残るような設計になっていないかは、必ず確認したいところです。
フレームワーク側の処理やジョブ実行のライフサイクルに沿ってContextが扱われるとしても、自分で追加したカスタム処理がその前提を壊すことはあります。ジョブの開始時や終了時に独自の共有状態を持たせている場合、Contextだけでなく、静的変数、シングルトン、サービスコンテナ上の状態も含めて確認します。
「前のユーザーのIDが、次のジョブのログに出ている」という事態は、ログ連携の失敗としてはかなり厄介です。アプリケーションの処理自体は成功しているため、テストでも見落としやすい。こういうところに、長時間稼働するワーカーの罠があります。
テストではログの相関まで見る
Contextを導入したら、処理が成功することだけでなく、ログに期待するメタデータが付くこともテスト対象にします。
最低限、次のケースは確認しておきたいでしょう。
- リクエスト中の通常ログにContextの値が付く
- 例外発生時のログにも同じ値が付く
- リクエスト中に投入したジョブがContextを引き継ぐ
- Contextが存在しない状態で起動したコマンドが正常に動く
- ジョブの再試行や失敗時にも、追跡に必要な情報が残る
- 次のジョブへ前回のContextが漏れない
テストでログの内容を完全一致させると、フォーマット変更のたびにテストが壊れます。ログの本文すべてを固定するより、必要なキーが存在することや、特定のIDが同じ処理に付いていることを確認する方が長持ちします。
Contextを使わない方がよい場面もある
Contextは、どこからでも読める便利な情報置き場ではありません。特に、ドメインロジックの条件分岐をContextに依存させると、処理の入口が見えなくなります。
たとえば、Contextにtenant_idがある場合だけデータを絞り込む、Contextにuser_idがある場合だけ権限を許可する、といった実装です。これはログ連携の範囲を超えています。テストではContextを準備しないと動かず、バッチ実行では別の挙動になり、呼び出し元によって結果が変わります。
業務上必要な値は、引数として明示します。ログを関連付けるための値はContextへ置きます。この線引きが曖昧なら、まずContextの導入範囲を狭めた方がよいでしょう。
また、Contextに入れた値が自動的に外部APIのヘッダーへ渡るわけでもありません。トレースIDを外部サービスへ送信したいなら、HTTPクライアント側でヘッダーへ変換する処理が必要です。Contextはアプリケーション内部の共有とログ連携を担当し、その先のプロトコル変換までは面倒を見ません。
ここを「Contextに入れたから外部連携も完了」と考えると、ログ上は追跡できるのに、外部サービスのログとは結び付かない状態になります。Laravelの機能として便利でも、システム全体のトレーシングが自動で完成するわけではありません。
まとめ:コードで解決する範囲と運用で補う範囲
Laravel 11のContextファサードは、リクエスト、キューイングされたジョブ、コマンド実行にまたがってメタデータを扱い、ログへ自動的に付与するための標準機能です。Context::add('key', 'value')で共通情報を登録でき、ログ出力のたびに同じ配列を手渡す実装から離れられます。
特に効果が大きいのは、HTTPリクエストからキューへ処理が移るアプリケーションです。リクエストIDやトレース用の識別子がジョブへ引き継がれれば、同期処理と非同期処理を一つの流れとして追跡しやすくなります。
一方で、Contextは業務データの受け渡し機構ではありません。注文IDや権限判定に必要な値まで暗黙の共有領域へ隠すと、コードの見通しが悪くなります。Contextへ入れるのは、ログ相関や処理識別に必要な、少量で安全なメタデータに絞るべきです。
Log::withContext()やLog::shareContext()から移行する場合も、全置換を急ぐ必要はありません。既存案件が安定しているなら残し、新しい処理やキュー連携が必要な箇所からContextへ寄せればよい。モダンな機能を導入しただけで運用が整うわけではない、という現実はLaravel 11でも変わりません。
結局のところ、Contextで解決するのは「共通メタデータを安全に運ぶ」部分です。ログのキー設計、個人情報の扱い、外部APMとの連携、ワーカーの状態管理、障害時の検索手順までは、チームと運用の仕事として残ります。
それでも、リクエストIDを毎回引数で渡し、ログを書き忘れ、深夜に時刻だけを頼りに障害箇所を探すよりは、Contextを使う方がずっと現実的です。理想的な分散トレーシングをいきなり完成させる必要はありません。まずは一つのIDで、リクエストとジョブのログをつなぐ。泥臭いですが、その一歩が炎上案件の調査時間を確実に短くします。