トレースとログの相関

より迅速なデバッグとトラブルシューティングのために、OBI がアプリケーションログを分散トレースとどのように相関付けるかを学びます。

OpenTelemetry eBPF 計装 (OBI) は、JSON ログをトレースコンテキストで補強することで、アプリケーションログを分散トレースと相関付けます。 OBI はログをエクスポートしません。 補強されたログを同じストリームに書き戻し、一方トレースは OTLP 経由でエクスポートされます。

概要

トレースとログの相関は、2 つの相補的なオブザーバビリティシグナルを結びつけます。

  • トレース: タイミングと構造を伴って、サービスをまたぐリクエストの流れを示します
  • ログ: 詳細なイベント情報とアプリケーションの状態を提供します

OBI のトレースとログの相関を使用すると、計装されたプロセスからのログにトレースコンテキストが自動的に補強されます。

  • トレース ID: ログエントリを分散トレースに紐づけます
  • スパン ID: ログエントリを特定のトレーススパンに紐づけます

これにより、アプリケーションへのコード変更なしに、オブザーバビリティバックエンドはログをその発信元のトレースと相関付けることができます。

動作の仕組み

OBI は eBPF を使用して、カーネルレベルでアプリケーションログにトレースコンテキストを注入します。

  1. トレースキャプチャ: OBI はトレースされるすべての操作についてトレースコンテキスト(トレース ID およびスパン ID)をキャプチャします
  2. ログのインターセプト: OBI はアプリケーションログをキャプチャするために write システムコールをインターセプトします
  3. コンテキストの注入: JSON 形式のログに対して、OBI は trace_id および span_id フィールドを注入します
  4. トレースのエクスポート: ログは既存のロギングパイプラインを通じて流れ続けます
  5. バックエンドでの紐付け: オブザーバビリティバックエンドは、これらの ID を使用してログをトレースに紐づけます

技術的なアプローチ

OBI はアプリケーションバイナリを変更することなく、カーネルレベルで相関付けを行います。

  • カーネルの eBPF プローブを使用して write 操作をインターセプトします
  • パフォーマンスのためにファイルディスクリプタのキャッシュを維持します
  • JSON ログを出力する任意のロギングフレームワークと連携します

設定

トレースとログの相関は、トレースエクスポートが設定され、選択されたサービスに対してログの補強が有効化されている場合に有効になります。

基本的な設定

# Enable trace export
otel_traces_export:
  endpoint: http://otel-collector:4318/v1/traces

# Select services to instrument
discovery:
  instrument:
    - open_ports: '8380'

# Enable log enrichment for the same services
ebpf:
  log_enricher:
    services:
      - service:
          - open_ports: '8380'

ログ補強の動作は、ebpf.log_enricher 配下でさらに設定できます。

  • cache_ttl: キャッシュされたファイルディスクリプタの time-to-live
  • cache_size: キャッシュされるファイルディスクリプタの最大数
  • async_writer_workers: 非同期ライターのシャード数
  • async_writer_channel_len: シャードごとのキューサイズ

サービスごとに相関を有効化する

OBI は ebpf.log_enricher.services 配下にリストされたサービスの JSON ログを補強します。 補強が同じプロセスを追跡するように、サービスセレクターを discovery.instrument と一致させてください。

要件

1. JSON ログ形式

トレースとログの相関は JSON 形式のログを必要とします。 OBI は JSON ログオブジェクトに trace_id および span_id フィールドを注入します。

OBI 適用前:

{ "level": "info", "message": "Request processed", "duration_ms": 125 }

OBI による補強後:

{
  "level": "info",
  "message": "Request processed",
  "duration_ms": 125,
  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
  "span_id": "00f067aa0ba902b7"
}

プレーンテキストのログはそのまま通過し、トレースコンテキストでは補強されません

ランタイムのバッファリングの制限

ログエンリッチャーは、ログの書き込みがリクエスト処理スレッドで発生したときにのみトレースコンテキストを認識します。 標準出力を非同期にバッファリングするランタイムは、この前提を破る可能性があります。

  • Docker 上の Python では PYTHONUNBUFFERED=1 が一般的に必要です
  • .NET の Console.Out は、標準出力がパイプの場合、デフォルトでバッファリングされます。AutoFlush = trueStreamWriter を使用してください
  • ASP.NET Core のデフォルトの Microsoft.Extensions.Logging.AddConsole() パイプラインは、バックグラウンドスレッドから書き込むため互換性がありません

2. トレースのエクスポートとログ補強の有効化

トレースをエクスポートし、ログ補強を有効化する必要があります。

otel_traces_export:
  endpoint: http://collector:4318/v1/traces # Required

ebpf:
  log_enricher:
    services:
      - service:
          - open_ports: '8380' # Required

3. Linux カーネル

トレースとログの相関には、特定のカーネル機能を持つ Linux が必要です。

  • Linux カーネル 6.0 以上(トレースとログの相関に必要)
  • サポートされるアーキテクチャ: x86_64、ARM64
  • BPFFS マウント: カーネルで BPF ファイルシステムが /sys/fs/bpf にマウントされている必要があります
  • セキュリティロックダウンされていないカーネル: セキュリティロックダウンモードで動作していないカーネルが必要です(ほとんどの本番ディストリビューションでは一般的)

4. JSON ログを出力するフレームワーク

アプリケーションは JSON を出力するように設定されたロギングフレームワークを使用する必要があります。 例:

import json
import logging

class JSONFormatter(logging.Formatter):
    def format(self, record):
        log_entry = {
            'timestamp': self.formatTime(record),
            'level': record.levelname,
            'message': record.getMessage(),
            'module': record.module,
        }
        return json.dumps(log_entry)

logger = logging.getLogger()
handler = logging.StreamHandler()
handler.setFormatter(JSONFormatter())
logger.addHandler(handler)
import "go.uber.org/zap"

logger, _ := zap.NewProduction() // Outputs JSON by default
defer logger.Sync()
logger.Info("Request processed", zap.Duration("duration", 125*time.Millisecond))
<appender name="FILE" class="ch.qos.logback.core.ConsoleAppender">
  <encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
</appender>
const pino = require('pino');
const logger = pino();
logger.info({ duration_ms: 125 }, 'Request processed');

5. ログ転送パイプライン

OBI はログをその場で補強します。 既存のログフォワーダーや Collector を使用して、ログをバックエンドに転送してください。

パフォーマンスに関する考慮事項

  • 最小限のオーバーヘッド: 相関付けには、効率的なファイルディスクリプタキャッシュを持つ eBPF カーネルプローブを使用します
  • キャッシュの制限: ファイルディスクリプタキャッシュには、無制限のメモリ使用を防ぐためのサイズおよび TTL の制限があります
  • 非同期処理: ログ補強は、カーネルのリングバッファをあふれさせないように、非同期ワーカーを使用します

既知の制限事項

  • JSON のみ: プレーンテキストのログはトレースコンテキストで補強されません
  • ファイルディスクリプタキャッシュ: パフォーマンスのためにキャッシュされ、設定可能な TTL(デフォルト: 30 分)を持ちます
  • スパン整合のみ: ログはスパンがアクティブな間のみ補強されます。スパンのスコープ外のログは補強されません。

トラブルシューティング

トレースコンテキストがログに表示されない

  1. JSON 形式の確認: アプリケーションが有効な JSON ログを出力していることを確認します

    # Check for malformed JSON
    cat app.log | jq empty && echo "Valid JSON" || echo "Invalid JSON"
    
  2. トレースエクスポートとログ補強の確認:

    otel_traces_export:
      endpoint: http://collector:4318/v1/traces
    
    ebpf:
      log_enricher:
        services:
          - service:
              - open_ports: '8380'
    
  3. Linux カーネルの確認: トレースとログの相関には Linux が必要です

    uname -s  # Must return "Linux"
    
  4. ログパイプラインの確認: ログフォワーダーがログをバックエンドに転送していることを確認します

次のステップ