コンテンツへスキップ
DedicatedPHP 接触

サポートに役立つPHPアクティビティログの設計方法

バックオフィスで参照できる運用履歴を設計し、機密データを複製したり監査ログと混同したりせずに、サポートがPHPの問題を調査できるようにします。

PHPバックオフィスで、サブスクリプションのイベントを日時順に並べたアクティビティ履歴

サポートが注文の状態が変わった理由、誰が操作を開始したか、処理がどこで中断したかを説明する必要があるとき、分散した技術ログだけでは分かりやすい答えが得られないことがあります。PHPアプリケーションのアクティビティログは、重要な運用イベントを保持し、バックオフィスから参照できるようにします。ただし、業務データをすべて複製するものでも、監査システムの代替でもありません。

設計は、履歴でどのような問いに答えるべきかを定義するところから始まります。次に、イベント、そのデータ、永続化の保証、アクセス制御を決めます。以下の基準はさまざまなPHPフレームワークに適用できますが、テーブル名、サービス名、コードは各アプリケーションのアーキテクチャとポリシーに合わせてください。

アクティビティログ、技術ログ、監査ログはそれぞれ異なる課題を解決する

アクティビティログ、技術ログ、監査ログはそれぞれ異なる課題を解決する — guía visual de DedicatedPHP

技術ログは、エラー、例外、実行時間、失敗した呼び出しなど、アプリケーション内部の動作の診断に役立ちます。主にエンジニア向けで、可観測性に有用な詳細を含むことがありますが、サポートが必要とする業務上の文脈が含まれるとは限りません。

アクティビティログは、「支払いが審査中になった」「返金が申請された」のように、チームが解釈できる言葉で運用上の事実を記録します。監査履歴は、説明責任、コンプライアンス、調査のニーズに応え、「誰が、いつ、どのような管理下で、何にアクセスまたは変更したか」を示します。より広範な記録、改ざん防止、正式な保存ポリシーが必要になる場合があります。

これらのカテゴリは同じ基盤を共有できますが、機能上の履歴が監査要件を自動的に満たすわけではありません。改ざんに耐える証拠や証拠保全の連鎖が必要な場合は、それらの要件を別途設計し、検証する必要があります。

サポートが調査できるようにする内容を定義する

テーブルやインターフェースを作る前に、サポートや運用の具体的な事例を集めます。たとえば、サブスクリプションが停止された理由、連携処理がいつ再試行されたか、リクエストが失敗する前にユーザーがどの手順を完了したかを調べるケースです。それぞれについて、回答を得たサポートがどのような判断をするのか、何が分かれば事象を再構成できるのかを確認します。

重要な変更、状態遷移、主要な処理結果を記録します。データベースの読み取りや内部呼び出しをすべてデフォルトで記録してはいけません。文脈が保証されないままデータ量だけが増えるためです。運用上の問いに答えるのに役立たないイベントは、技術ログに記録するか、保存しないほうがよいでしょう。

履歴はアプリケーションが処理した内容の再構成に役立ちますが、個人が画面上で何を見たかを必ず証明するものではなく、記録されていないデータを復元することもできません。こうした限界をインターフェースとサポートの期待値に明示するとよいでしょう。

有用なイベントを設計し、PHPに組み込む

イベントには、コントローラーごとに異なる文言をその場で作るのではなく、安定した構造が必要です。最小限のリレーショナルスキーマの例を示します。

activity_events
- id              UUIDまたは内部識別子
- tenant_id       アカウント識別子(該当する場合)
- event_type      subscription.paused
- actor_type      user | system | service
- actor_id        識別子(nullable)
- subject_type    subscription
- subject_id      エンティティの識別子
- outcome         succeeded | failed | pending
- occurred_at     UTCの日時
- correlation_id  リクエストまたは処理の識別子
- context         制限・検証済みのJSON

このスキーマは一例です。PHPではマイグレーションとモデルで実装できます。型、キー、インデックス、削除ポリシーは、データベースと想定クエリに合わせて定義してください。エンティティごとの履歴には、tenant_idなどのアクセス範囲、エンティティ、日時を含むインデックスが役立つことがあります。実行計画と代表的なデータで有効性を確認してください。

コントローラーやバックグラウンドタスクごとに異なる方法で行を作るのではなく、たとえばActivityRecorderのようなアプリケーションサービスに書き込みを集約します。以下のコードはイベントの形を示すもので、特定のフレームワークのAPIではありません。

$event = [
    'event_type' => 'subscription.paused',
    'actor_type' => 'user',
    'actor_id' => $authenticatedUser->id,
    'subject_type' => 'subscription',
    'subject_id' => $subscription->id,
    'outcome' => 'succeeded',
    'occurred_at' => new DateTimeImmutable('now', new DateTimeZone('UTC')),
    'correlation_id' => $requestId,
    'context' => ['reason_code' => 'payment_review'],
];

$activityRecorder->record($event);

サービスではイベント種別とcontextで許可するキーを検証し、日時を正規化してください。また、ユーザー識別情報はクライアントから送られた値ではなく、サーバーの認証済みコンテキストから取得します。イベント種別と表示テキストは分離してください。そうすれば、安定したフィルターを保ったままラベルを変更できます。自動処理では実行主体をsystemまたはserviceとして記録し、文脈として有用であれば処理の識別子も保持します。

機密データを除外、要約し、保護する

HTTPリクエスト、エンティティ全体、またはフィールドの変更前後の値を自動的に複製してはいけません。こうした方法は、パスワード、トークン、個人データ、決済情報を漏えいさせるおそれがあり、保存期間やアクセス制御も複雑にします。

サポートの問いに答えるために必要な情報だけを含めます。連絡方法の値ではなく変更された事実だけを記録したり、プロバイダーが操作を拒否したことだけを示して完全な応答は保存しなかったりする方法があります。識別情報が不可欠な場合は、マスキングできるか、認可された担当者だけに表示できるかを検討してください。

JSONの内容を検証して制限し、エクスポートを保護してください。特に機密性の高い情報へのアクセスを記録することも検討します。ポリシーの対象には、メインのテーブルだけでなく、バックアップ、テスト環境、削除も含める必要があります。

トランザクション内の書き込みと非同期処理を選ぶ

業務変更が確定しない限り表示すべきでないイベントなら、同じトランザクションに保存することで両者の整合性を保てます。PHPでは、書き込みに同じ接続を使い、操作が開始したトランザクションに参加させる必要があります。操作がロールバックされた場合、イベントもロールバックされるべきです。

この選択では、書き込みが処理のレイテンシと可用性に影響します。書き込みに失敗したとき、アプリケーションは操作全体を中止するのか、履歴なしで変更を許可するのかを決める必要があります。両者は同じ挙動ではないため、明示的に決定してください。

イベントを他のシステムに配信する場合は、同じトランザクション内にoutboxエントリーを保存し、後から公開する方法があります。コンシューマーは、たとえば冪等性キーを使って、再試行や重複に対応する必要があります。トランザクション連携のない直接的なキュー投入では、業務変更との不整合が起きる可能性があります。

バックオフィスから履歴を参照できるようにする

サブスクリプションの詳細画面には、たとえば次のような行を表示できます。「12:40 UTC · Ana Lópezがサブスクリプションを一時停止 · 理由:支払い審査 · 成功」。詳細を開くと相関IDを表示し、エンジニアがトレースと関連付けられるようにすることもできます。すべてのロールに完全なJSONを公開する必要はありません。

クエリではアクセス範囲、エンティティ、日時で絞り込み、最新のイベントから順に並べられます。パラメーター化SQLの例を示します。

SELECT event_type, actor_type, actor_id, outcome,
       occurred_at, correlation_id, context
FROM activity_events
WHERE tenant_id = :tenant_id
  AND subject_type = :subject_type
  AND subject_id = :subject_id
ORDER BY occurred_at DESC
LIMIT :page_size;

PHPでは、プロジェクトのデータアクセス層を通じてバインドパラメーターを使用し、ユーザーから受け取った値をSQLに連結してはいけません。ページネーションと時間範囲の上限を設け、履歴全体の読み込みやJSONを対象にした高コストな絞り込みを避けてください。詳細画面を開けることが、そのエンティティのすべてのイベントを参照またはエクスポートする権限を意味するとは限りません。公開するデータに応じてアクセス制御を適用します。

保存期間、データ量、完全性を管理する

保存期間、削除またはアーカイブの方法、削除済みエンティティに関連する記録の扱いを定めます。運用上の用途、適用される義務、ストレージコストを考慮してください。無期限の保存は保存ポリシーとはいえません。

データ量はイベントの頻度とコンテキストのサイズによって決まります。可変フィールドを制限し、クエリ時間を測定し、利用パターンが妥当性を示す場合には最近のデータと履歴データを分けます。アプリケーションのデータベースにあるテーブルは、「監査」と名付けても不変にはなりません。十分な権限を持つ者は変更できる可能性があります。証拠としての完全性が必要なら、専用の管理策と独立した評価が必要です。

既存のPHPアプリケーション向けチェックリスト

既存のPHPアプリケーション向けチェックリスト — guía visual de DedicatedPHP
  • サポートが調べる内容と、それに答えるために必要なイベントを文書化する。
  • 必要に応じて、実行主体、操作、エンティティ、結果、日時、相関情報を含むスキーマを定義する。
  • イベントを検証し、コンテキストを制限するサービスに書き込みを集約する。
  • シークレットを除外し、個人データを必要最小限にする。
  • どの書き込みを業務変更と同じトランザクションに含めるか、失敗をどう扱うかを決める。
  • 実際のクエリパターンに基づき、フィルター、権限、ページネーション、インデックスをテストする。
  • 保存期間、削除、履歴へのアクセス、バックアップの扱いを定める。
  • 成功、エラー、再試行、重複、トランザクションのロールバックをテストする。

優れたアクティビティログは、PHPで起きるすべてを記録しようとはしません。判断や結果を説明するために必要な運用上の事実を、安全でバックオフィスから参照できる文脈とともに保持します。正式な証拠、網羅的な記録、改ざん防止の要件が生じた場合は、それらを別個の監査プロジェクトとして扱い、その目的で履歴に依存する前に管理策を定義してください。

これらのアイデアをあなたのプロジェクトに活用してみませんか?あなたのPHPプラットフォームについて話し合いましょう。
関連サービスを見る