アクセスログ

OxPHP は、すべての HTTP リクエストに対して構造化された JSON アクセスログを出力し、stdout に書き込みます。ログ出力は非同期で行われるため、リクエスト処理をブロックすることはありません。

仕組み

ACCESS_LOG が設定されている場合、OxPHP は完了した各リクエストの後に 1 行の JSON を stdout に書き込みます。これらの書き込みはバックグラウンドのライタースレッドでバッファリングされるため、ログ出力がリクエストパイプラインをブロックすることはありません。

モードによって記録される内容が制御されます。ACCESS_LOG=all はすべてのリクエストをログに記録し、ACCESS_LOG=error はステータス 400 以上のレスポンスのみをログに記録します。

これらの行を書き込むかどうかは ACCESS_LOG だけで決まり、LOG_LEVEL はまったく影響しません。診断ログを warn に設定したサーバーでも、ACCESS_LOG が有効にしたアクセスログは書き込まれます。唯一の例外は、access_log ターゲットを明示的に指定した RUST_LOG ディレクティブです。きめ細かなフィルタリング を参照してください。

すべてのログ行には request_id フィールドが含まれており、アクセスログのエントリとアプリケーションログを関連付けます。W3C Trace Context の伝播が有効な場合、エントリには trace_id および span_id フィールドも含まれます。

設定

変数 デフォルト 説明
ACCESS_LOG (未設定) アクセスログのモード。all はすべてのリクエストを、error は 4xx および 5xx のレスポンスのみをログに記録します。無効にするには未設定または空のままにします
Note

有効な値は all と error のみです。認識されない値を設定すると警告がログに記録され、アクセスログが無効になります。

ログ形式

すべてのアクセスログエントリは、stdout に書き込まれる 1 行の JSON です。

json
{ "timestamp": "2026-02-11T12:34:56.789012Z", "level": "INFO", "fields": { "request_id": "67890abc12341a2b0042", "method": "GET", "path": "/api/users", "status": 200, "duration_us": 1234, "remote_ip": "10.0.0.1", "user_agent": "Mozilla/5.0 (X11; Linux x86_64; rv:131.0) Gecko/20100101 Firefox/131.0", "message": "request completed" } }

W3C Trace Context が有効な場合、標準フィールドに加えて trace_id と span_id が含まれます。

json
{ "timestamp": "2026-02-11T12:34:56.789012Z", "level": "INFO", "fields": { "request_id": "67890abc12341a2b0042", "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736", "span_id": "00f067aa0ba902b7", "method": "POST", "path": "/api/orders", "status": 201, "duration_us": 8421, "remote_ip": "10.0.0.1", "user_agent": "Mozilla/5.0 (X11; Linux x86_64; rv:131.0) Gecko/20100101 Firefox/131.0", "message": "request completed" } }

フィールド

フィールド 型 説明
request_id string 一意のリクエスト識別子。リクエストID を参照してください
method string HTTP メソッド(GET、POST など)
path string リクエスト URI のパス
status number HTTP レスポンスのステータスコード
duration_us number リクエスト処理の合計時間(マイクロ秒単位)
remote_ip string クライアントの IP アドレス(ポートなし)。TRUSTED_PROXIES が設定されている場合、転送ヘッダーから抽出された実際のクライアント IP を表示します。プロキシの IP を表示するのは、転送ヘッダーからアドレスが得られない場合だけです(信頼済みプロキシ を参照してください)
user_agent string リクエストの User-Agent ヘッダー(複数送信された場合は最初のもの)。512 バイトより長い場合は 512 バイトに切り詰められます(…(truncated) マーカーを含みます)。有効な UTF-8 でないバイトは U+FFFD に置き換えられます。リクエストが送信しなかった場合は存在しません
trace_id string W3C トレース ID(TRACE_CONTEXT=true の場合のみ存在します)
span_id string W3C スパン ID(TRACE_CONTEXT=true の場合のみ存在します)

きめ細かなフィルタリング

OxPHP はアクセスログのエントリを内部の access_log ロギングターゲットで出力し、ACCESS_LOG がアクセスログを要求している場合は、そのターゲット用のディレクティブを自身のログフィルターに追加します。これにより 2 つの設定は互いに独立します。LOG_LEVEL=warn でサーバーの出力を抑えても、追加の設定なしでアクセスログは出力され続けます。

bash
# Quiet diagnostics, access log intact LOG_LEVEL=warn ACCESS_LOG=all

ターゲット名を完全に指定した RUST_LOG ディレクティブは、OxPHP が追加するディレクティブも含め、そのターゲットについて優先されます。レベルだけの指定は、LOG_LEVEL と同様にこれを上書きしません(RUST_LOG=off はサーバー自身の診断ログを抑えますが、アクセスログは出力され続けます)。access=off のような短縮名も同様で、2 つのディレクティブのうち具体性の低い方になるため負けます。アクセスログだけを止めるには、完全な名前を指定したディレクティブを使用します。

bash
# Access log off, warnings and errors still reported ACCESS_LOG=all RUST_LOG=warn,access_log=off # `warn` here is what LOG_LEVEL would have been
Warning

RUST_LOG は、設定されていてパース可能な場合、LOG_LEVEL に追加されるのではなく 置き換え ます。そのため、使用するレベルも RUST_LOG に含めてください。また、上記の組み合わせは矛盾しています(アクセスログを有効にしたうえで、フィルターで取り除いている)ため、サーバーは起動時に両方の値を示してこれを一度報告します。ACCESS_LOG を未設定にする方法をおすすめします。

Note

access_log ターゲットは RUST_LOG のフィルタリングに使用されますが、JSON 出力には含まれません。下流のシステムでアクセスログのエントリを識別するには、"message": "request completed" フィールドと特徴的なフィールドの組み合わせ(method、path、status、duration_us)を利用してください。

トラブルシューティング

アクセスログのエントリが表示されない

ACCESS_LOG が未設定または空の場合、アクセスログは無効になります。

対処法: 変数を all または error に設定します。

bash
ACCESS_LOG=all

すでに設定されている場合、残る可能性は、access_log ターゲットを指定してそれをオフにする RUST_LOG です。運用者自身のディレクティブは、OxPHP が追加するディレクティブより優先されます。サーバーは起動時にその旨を報告します。

json
{"timestamp":"…","level":"WARN","fields":{"message":"ACCESS_LOG is set but the log filter drops the access_log target — no access log entries will be written","access_log":"all","filter":"warn,access_log=off"}}

filter フィールドには有効なディレクティブが表示され、この例ではあなたが設定したものそのままです。あなた自身のディレクティブが上書きする箇所では、OxPHP がそのターゲット用に追加するディレクティブは取り除かれます。RUST_LOG から自分で書いた access_log=… を削除すれば、エントリは再び書き込まれます。

フィルターが WARN まで取り除くほど厳しい場合は、同じ報告が JSON ではなくプレーンな行として stderr に書き込まれます。そのため、この組み合わせは必ず報告されます。

text
WARN ACCESS_LOG is set but the log filter drops the access_log target — no access log entries will be written (access_log=all, filter=error,access_log=off)

通常の LOG_LEVEL(trace、debug、info、warn、error)がこの問題を引き起こすことはありません。レベルだけの指定はすべてのターゲットに一括で適用され、このターゲットを名指しするディレクティブの方が具体性が高いためです。

アクセスログが `error` モードだが成功したリクエストが記録されない

ACCESS_LOG=error はステータス 400 以上のレスポンスのみをログに記録します。これは仕様です。値を確認し、すべてのリクエストをログに記録する必要がある場合は all に切り替えてください。

確認: 現在有効な設定を確認します。

bash
curl -s http://localhost:9090/config | jq '.access_log'
ログエントリに `trace_id` と `span_id` が含まれていない

トレースコンテキストのフィールドは、W3C Trace Context の伝播が有効な場合にのみ存在します。

対処法: 以下で有効にします。

bash
TRACE_CONTEXT=true

さらに、上流のクライアントまたはロードバランサーがリクエストに traceparent ヘッダーを送信していることを確認してください。

Docker の例

compose.yaml
services: app: image: ghcr.io/oxphp/oxphp:0.13.0 ports: - "80:80" - "9090:9090" volumes: - ./src:/var/www/html:ro environment: ACCESS_LOG: "all" ENTRY_FILE: "index.php" INTERNAL_ADDR: "0.0.0.0:9090"

ベストプラクティス

  • 本番環境では ACCESS_LOG=error を使用することで、失敗したすべてのリクエストを捕捉しつつログの量を削減できます。成功したリクエストはログに記録されませんが、ステータス 400 以上のエラーは常に捕捉されます。
  • oxphp_request_id() を使ってアプリケーションログにリクエストID を含めることで、PHP レベルのログエントリとアクセスログのエントリを関連付けられます。
  • Elasticsearch、Loki、Datadog などの構造化ログアグリゲーターを使用すると、JSON ログ行を効率的にクエリしフィルタリングできます。

連携

ログは stdout 上の JSON 行であるため、コンテナのログドライバーや集約ツールと直接連携します。

  • Docker — コンテナのログドライバー(json-file、fluentd など)を介して自動的に収集されます
  • Kubernetes — ノードのログエージェント(Fluentd、Fluent Bit、Filebeat など)によって取得されます
  • systemd — journald 経由の stdout ロギングを備えた systemd サービスとして実行される場合に捕捉されます

サイドカーやファイルベースのログ転送は不要です。

エージェント向けプロンプト

これらのプロンプトは、OxPHP ドキュメント MCP サーバー を接続したエージェントを前提としています — 必要なページはエージェントが自分で読み取ります。

prompt
OxPHP のアクセスログを私のログアグリゲーターに取り込んでください。OxPHP ドキュメント MCP サーバー経由で `features/access-logging` ページを読んでください。 このプロジェクトが動くべきモードを選び、私の `LOG_LEVEL` や `RUST_LOG` の設定が `access_log` ターゲットを取り除いていないか確認してください。そのうえで、このリポジトリがすでに書き出しているアプリケーションログと並べて、私のアグリゲーターが JSON フィールドをどうパースすべきかを示してください。

関連項目