1つの処理がHTTPリクエスト、キュー、1つ以上の外部サービスを経由すると、そのログは別々のプロセスに分散します。共通の情報がなければ、インシデントの調査では時刻、ユーザー、内容を手がかりに検索することになり、曖昧さや機密性の問題が生じる場合があります。PHPの相関IDを使えば、1つの処理に関連するイベントをまとめ、その経路を再構成できます。
機能させるには、IDの発行元を定め、非同期の境界を越えて明示的に伝播させ、ログに一貫して記録する必要があります。これは完全なオブザーバビリティの仕組みでも、認証情報でもありません。イベントを関連付けるためのものであり、何が失敗したかを単独で説明したり、誰に権限があるかを示したりするものではありません。
解決できることと対象外のこと

相関IDは、リクエストまたは処理に関連付けられた不透明な値です。アプリケーションのログに含めることで、異なるコンポーネントが同じ値を持つメッセージを検索できます。たとえば、リクエストの受信、ジョブの投入、ジョブの処理、外部APIの応答を関連付けられます。
ログが一元管理され、フィールドで検索できる環境では、診断の効率が上がります。ただし、すべてのコンポーネントがIDを正しく記録する保証はなく、処理時間、量、集計済みのエラーを測定するものでもありません。メトリクスは傾向やアラートに、分散トレースはコンポーネント間の関係や所要時間に、ログはイベントやコンテキストの把握に役立ちます。これらは相互に補完するシグナルです。
また、相関と認証、認可、ユーザーの識別を混同してはいけません。IDを知っていても、データへのアクセスや処理の実行が可能になってはなりません。値がURLやサードパーティーのシステムに含まれると、想定より多くの人に見られる可能性があります。
生成場所と検証方法を決める
アプリケーションでは、リクエストを受け取るHTTPミドルウェアでコンテキストを開始するのが一般的です。プロキシや前段のサービスとの連続性を保つために受信したIDを受け入れる方法も、IDがなければ新たに生成する方法もあります。ポリシーを明確にしてください。外部からの値をすべて信頼すると、不正な形式、過剰な長さ、ログを汚染する目的で細工された入力を許すおそれがあります。
公開トラフィックでは、内部IDを常に生成し、受信値を引き継ぐ必要がある場合は信頼できないデータとして厳格に検証する方法が安全です。別の方法として、境界でルールを設定し、信頼できるコンポーネントからのヘッダー値だけを受け入れることもできます。どちらを選ぶかは、アーキテクチャとサービスの呼び出し元によって異なります。
形式は検証しやすく、値を運ぶシステムと互換性があるものにします。random_bytes()のようなシステムの暗号学的な乱数生成元で値を生成し、文字種を限定してエンコードできます。タイムスタンプ、IPアドレス、個人データからIDを組み立てるのは避けてください。一意なIDが業務情報を明かす必要はありません。
$correlationId = bin2hex(random_bytes(16));
この例はランダムな16進数の値を生成しますが、汎用的な形式を規定するものではありません。最大長、使用可能な文字、コンポーネント間で使うヘッダー(例:X-Correlation-ID)を定義してください。プラットフォームですでにトレース標準を採用している場合は、必要もなく並行する仕組みを作らないでください。ログの相関とトレースコンテキストは併用できますが、同じ概念ではありません。
各境界でコンテキストを伝播させる
ミドルウェアでリクエストの開始時にIDを検証または生成し、ロギングコンテキストに追加します。レスポンスでクライアントに返す場合は、追加情報を公開しないことと、信頼ポリシーが明確であることを確認してください。アプリケーションの他の部分には明示的な依存関係としてコンテキストを渡し、テストしにくいグローバル変数に依存しないようにします。
PHPプロセス内では、関連イベントを記録するサービスにIDを渡します。非同期ジョブの場合は、キューシステムの規約に従い、メッセージのメタデータまたはペイロードの専用フィールドに含めます。メッセージを処理するときは、ジョブの実行前にコンテキストを復元し、終了後にクリアします。同じワーカーが連続してメッセージを処理する長時間稼働プロセスでは特に重要です。コンテキストをクリアしないと、別の処理に漏れる可能性があります。
外部APIを呼び出す場合は、プロバイダーが対応しており、共有が適切な場合に限り、合意したヘッダーで値を送信します。不要な内部データは送らないでください。ヘッダーを保持しない連携先については、呼び出しのローカルな結果とIDを記録します。リモートシステム側のログにもIDが記録されるとは限りません。
リクエストから複数の独立したジョブが生成される場合、それぞれが元の処理のIDを引き継ぐことができます。一方、サブ処理を区別する必要があれば、それぞれに固有のIDを付与し、トレースコンテキストまたは機密性のない業務フィールドで関連付けます。異なる意味を持つ値に同じIDを使う場合は、必ずその意味を文書化してください。
オブザーバビリティの代替ではなく、構造化ログに使う
IDは、テキスト文の中だけに埋め込むのではなく、たとえばcorrelation_idのような構造化フィールドとして記録します。これにより、ログ基盤で曖昧な検索に頼らずにイベントを絞り込み、グループ化できます。アプリケーション、ワーカー、連携先で規約を統一し、シリアライザーがIDを省略しないことを確認してください。
リクエストの受信、メッセージの作成、ジョブの開始と終了、外部呼び出しの結果など、診断に役立つイベントに含めます。システムが処理単位のコンテキストを保持できるなら、すべての行に繰り返し記録する必要はありません。その場合、リクエスト間でコンテキストがリセットされること、また、コンポーネントがIDに対応していなくてもログを解釈できることを確認してください。
高カーディナリティのメトリクスラベルとしてIDを使わないでください。リクエストごとに時系列を作るとコストが上がり、メトリクスの有用性が損なわれる可能性があります。また、IDと一緒にトークン、パスワード、決済データ、メッセージ本文全体、不要な個人情報を記録しないでください。組織のログに関する最小化、アクセス、保持のルールを適用します。
再試行、遅延実行、障害
同じジョブを再試行する場合、通常は元の相関IDを維持します。論理的には同じ処理の一部だからです。試行を区別するには、元のコンテキストを置き換えずに、試行回数または試行固有のIDを記録します。これにより、処理全体の経路と各実行の原因の両方を調査できます。
ジョブの実行が大幅に遅れる場合は、IDを永続的なメタデータとして保持します。プロセスのメモリや、すでに終了したHTTPセッションに依存してはいけません。ジョブが新たに独立した処理を開始する場合は、元のコンテキストを引き継ぐか、新しいIDを割り当てるかを決め、両者の関係を記録します。この判断により、相関の範囲が広くなりすぎて無関係なタスクが混在するのを防げます。
キューや外部サービスがメタデータを破棄する場合は、その境界を文書化し、自分が制御できるコンポーネントでコンテキストを保持してください。IDがなくてもアプリケーションを運用できる必要があります。プロセスへの受け入れ時にIDを生成するか、コンテキストが利用できなかったことを明確に記録します。
テストと運用上の確認

- IDがない場合、有効な場合、不正な形式の場合についてミドルウェアをテストし、定めたポリシーが適用されることと、値が上限を超えないことを確認します。
- リクエストのIDが内部サービスのログと、キューに送信したメッセージのメタデータに記録されることを確認します。
- ワーカーでメッセージを処理し、IDがログに含まれることと、次のメッセージの処理前にコンテキストがクリアされることを確認します。
- 再試行をシミュレートし、元の相関IDが維持され、試行を区別できることを確認します。
- 外部呼び出しの出力とエラー処理を確認し、ログに秘密情報や機密性の高い本文が露出しないようにします。
- ダッシュボードやアラートが、メトリクス内の個別IDの検索に依存していないことを確認します。
エンドツーエンドの確認では、テスト用の処理をHTTPの受信からジョブ、連携先まで追跡し、同じフィールドでログを検索します。途中でIDが欠けていれば、その境界、すなわちミドルウェア、メッセージのシリアライズ、コンシューマー、HTTPクライアント、ロガーの設定のいずれかを特定します。この確認結果から伝播を修正すべき箇所を判断でき、むやみにログを追加する必要はありません。
実務上は、信頼ポリシーとライフサイクルのルールを明確にしたうえで、小さく、明示的で、機密性のないコンテキストを維持するのが適切です。こうすれば、PHPの相関IDはインシデント時の検索時間を短縮しつつ、セキュリティ対策、トレース、メトリクス、慎重に設計されたログの代わりにはなりません。



