インフラ

OpenTelemetryのログ実装|直接送信とfilelog receiverの選び分け

OpenTelemetryでログを扱うとき、実装者が最初にぶつかるのは「アプリからOTLPで直接送るのか、標準出力に書いてCollectorに拾わせるのか」という分岐です。ここを決めないまま計装を進めると、同じログが二重に届いて課金だけが倍になる、相関用のtrace_idが入っていない、といった手戻りが起こります。送出経路の選び分け、filelog receiverの設定、trace_idとspan_idによる相関、既存ロガーからの移行手順を実装の順序で扱います。

まとめ:OpenTelemetryのログ実装で先に決める送出経路と相関設計

結論から示します。2026年8月時点で、SDKからのOTLP直接送信を本番に載せられるのはLogsがStableに到達した言語だけです。opentelemetry.ioの言語別ステータス表では、C++・.NET・Java・PHPがStable、GoとRustがBeta、JavaScript・Python・Rubyは開発中の扱いになっています。PythonやNode.jsのアプリなら、直送を待たずにCollectorのfilelog receiverで標準出力を拾う構成が現実的な解です。そのCollectorをKubernetes上のどの層に置くかはOpenTelemetryのKubernetes構成|Operator導入とCollector二層設計で整理しています。

相関の設計はログの中身に直結します。ログデータモデルはTraceIdとSpanIdをフィールドとして持ち、ここが埋まって初めてトレース画面からログへ飛べます。自動計装でMDCへ注入するか、ロガー側で構造化フィールドとして書き出すかは、移行前に決めておく設計事項です。

事故が起きるのは移行の途中です。Javaエージェントはotel.logs.exporterの既定値がotlpで、入れた時点で直送が始まります。DaemonSetのCollectorが同じコンテナの標準出力もtailしていれば、1件のログが2件になります。経路は必ず1本に畳んでください。

他シグナルと異なるOpenTelemetryログの設計思想とLog Bridgeの役割

ログはトレースやメトリクスと同じ作法では設計されていません。

ログだけAPIを新設しない理由とLog Bridge APIの設計上の位置づけ

OpenTelemetryの仕様は、ログについて新しいユーザー向けAPIを提供しない方針を明示しています。理由は既存資産の量です。SLF4JやLogback、Log4j、Pythonのlogging、Goのslogは各言語に深く根を張っており、置き換えを迫る設計は現実的でないと判断されました。

代わりに置かれたのがLogs Bridge APIで、仕様上は「ログアペンダを作るライブラリ作者向け」と位置づけられています。アプリ開発者が直接呼ぶものではありません。実務で書くコードは、これまでどおりlogger.infoのままです。シグナル全体の関係やOTLPの位置づけはOpenTelemetry(OTel)とは|3つのシグナル・OTLP・Collector・Prometheusとの違いで扱っています。

LogRecordの必須フィールドとSeverityNumber 1〜24の対応関係

変換先のログデータモデルは、Body、Timestamp、ObservedTimestamp、TraceId、SpanId、TraceFlags、SeverityText、SeverityNumber、Resource、InstrumentationScope、Attributesで構成されます。実装で効くのはSeverityNumberです。仕様は1から24の整数を定め、1〜4がTRACE、5〜8がDEBUG、9〜12がINFO、13〜16がWARN、17〜20がERROR、21〜24がFATALに対応します。17以上がエラー相当という線引きは仕様に明記されているため、アラート条件はこの数値で組むのが確実です。

一方のSeverityTextは元のログレベル文字列を保持するフィールドで、「WARNING」と「WARN」のような表記の揺れがここに残ります。文字列一致で組んだアラートは、ライブラリごとの表記差で漏れると考えてください。時刻が2つある点も収集経路に関わる論点です。Timestampは発生元の時計で記録した発生時刻、ObservedTimestampは収集システムが観測した時刻で、Collectorがファイルをtailする構成では両者の差が収集の遅延量になります。Attributesの設計は構造化ログとは|JSONでログを機械可読にする仕組みとslog・structlog実装で扱っています。

OTLPログのSDK直接送信とfilelog receiver収集の選び分け基準

どちらの経路も仕様に載った正規の方式で、優劣ではなく条件で決まります。

SDK直接送信が向く条件とネットワーク断でログを失う失敗パターン

直接送信は、アプリのプロセス内でログをOTLPのレコードへ変換し、ネットワーク越しに送り出す方式です。Javaエージェントの既定値ではotel.logs.exporterotlpotel.exporter.otlp.logs.protocolがgRPCで、宛先はgRPCなら4317番、HTTPなら4318番ポートのログ用パスになります。標準出力を経由しないため、整形やパースの工程を省け、属性の型も保たれます。

弱点はプロセスと運命を共にする点です。送信先のCollectorが落ちている間、ログはプロセス内のキューに溜まり、溢れれば捨てられます。デプロイやOOMでプロセスが即死すれば、未送信分はディスクのどこにも残りません。障害調査で最も読みたいのは、その落ちる直前のログです。

filelog receiver方式が有利になるコンテナ環境と再起動時の欠損対策

もう一方は、アプリは標準出力かファイルに書くだけにして、Collectorのfilelog receiverが読み取る方式です。receiverはContribディストリビューションに含まれ、2026年8月時点の安定度はbeta、最新は8月4日リリースのv0.158.0系になります。アプリの改修が不要で、ログがノード上に残るためkubectl logsでも追えます。

注意すべきはオフセットの持ち方です。読み取り位置はメモリ上で管理されており、storageを指定しない限り再起動で消えます。start_atの既定値はendなので、再起動後は末尾から読み直す挙動になり、停止中の分が欠けます。ファイルストレージ拡張をstorageに指定した永続化が本番構成の前提です。基盤全体で何をどこに置くかはログ集約とは?収集・転送・保存・可視化の仕組みとエージェント選定・導入判断で扱っています。

二重送信が起きる典型構成と送出経路を1本に畳む切り分けの手順

両方を有効にした構成が、移行期に最も多い事故です。エージェントを入れた時点で直送が始まる一方、クラスタ側のCollectorは以前から標準出力を拾い続けている。どちらも正しく動いているため、エラーは出ません。気づくのは請求額かログ件数の異常です。

構成 ログの流れ 二重送信
SDK直送のみ アプリから直接OTLP 起きない
filelog収集のみ 標準出力をCollectorが読む 起きない
直送+標準出力併記 同じ内容が2系統で届く 起きる
直送+別ファイル出力 ファイル側も収集対象 起きる

切り分けはバックエンド側から始めます。同一のメッセージ本文で件数を数え、送信元のリソース属性が2種類に分かれていれば二重です。畳み方は2つ。直送を残すならCollector側でそのログパスを除外し、ファイル収集を残すならエージェント側のログエクスポータを無効化します。迷ったら後者です。アプリ側の設定変更のほうが影響範囲を読み切れます。

Collectorのfilelog receiver設定とログローテーション対応の要点

ファイル収集を選ぶなら、設定値の意味を知らないまま既定で流すと欠損か重複を踏みます。

includeとstart_at・storageで決まる読み取り開始位置と再送の挙動

必須項目はincludeだけで、読み取り対象をglobパターンで指定します。除外はexcludeです。初回起動時にどこから読むかはstart_atが決め、既定は末尾を意味するendになっています。過去分を取り込みたい場合のみ先頭指定へ変えてください。

ポーリング間隔の既定値は200ミリ秒、同時に読むファイル数の上限は1024、ファイル同一性の判定に使う先頭バイト数は1000バイトです。この先頭1000バイトを指紋に使うため、ローテーションで新しいファイルが作られても追随できます。裏を返せば、先頭が同じ定型ヘッダで始まるログを大量に置けば別ファイルを同一と誤認する余地も残る設計です。送信失敗時の再試行も既定では無効で、retry_on_failureを有効化すると下流の障害中は読み取りを止めて待ちます。

operatorsでのJSONパースとseverity_parserで重大度を正規化

読み取った行は、Bodyに文字列が入っただけのレコードです。ここから構造を起こすのがoperatorsの中心的な役割です。アプリがJSONで出力しているなら、JSONパーサで展開し、時刻をTimestampへ、レベルをSeverityへ写します。レベル変換を担うseverity_parserは、「warn」「WARNING」「30」といった表記を仕様のSeverityNumberへマップします。

複数行のスタックトレースは、パースの前に結合が必要です。行の開始パターンを正規表現で指定し、一致しない行を直前のレコードへ連結します。Javaの例外ログをこの設定なしで流すと、1つの例外が数十件のERRORログとして数えられ、アラートが誤発報します。

Kubernetesでのstdout収集構成とresource属性の付与漏れの防ぎ方

Kubernetesでは、コンテナの標準出力がノード上の/var/log/pods配下にファイルとして落ちます。DaemonSetでCollectorを各ノードに置き、このパスをglobで指定するのが定石です。ファイルパスをレコードに残すinclude_file_pathを有効にしておくと、後段でPod名やNamespaceを取り出せます。

付与漏れが起きやすいのはリソース属性です。ファイルから読んだレコードには、どのPodのどのコンテナかという情報が構造化された形で入りません。k8sattributesプロセッサを挟み、Namespace・Pod名・コンテナ名・ノード名を付けます。飛ばすと、サービス単位の絞り込みも結合条件も作れません。DaemonSet構成では、オフセットの永続化先をノードのホストパスへマウントしてください。

trace_idとspan_idでログとトレースを相関させる実装と検証の手順

トレースからログへ一発で飛べる状態は、自動では成立しません。

自動計装でMDCへtrace_idを注入する方式と手書きロガーでの補い方

Javaの場合、エージェントがLogbackやLog4jのアペンダを差し込み、ログイベントをログSDKへ橋渡しします。ライブラリとして組み込む構成なら、OpenTelemetryAppenderをlogbackの設定ファイルへ登録し、起動時にSDKをインストールする手順です。スレッド名やスレッドIDを拾うcaptureExperimentalAttributesなど、属性の取得範囲を指定するオプションも用意されています。

アペンダ方式を採らないなら、MDCへ識別子を入れる方式になります。スパンコンテキストからトレース識別子とスパン識別子を取り出し、リクエスト処理の入口でMDCへ格納し、出口で消す。フォーマット文字列にそのキーを含めれば、既存の集約基盤を維持したまま相関キーだけを持たせられます。Go・Python・Node.jsでは、同じ値を構造化ログのフィールドへ書き出す実装が中心です。フィールド名はtrace_idspan_idに揃えてください。バックエンド側の相関設定がこの名前を前提にしているためです。

送出後にログとトレースの結合を確認するdebug exporterでの検証

設定を入れたら、バックエンドに届く前に中身を見てください。Collectorのdebugエクスポータを一時的に足すと、受信したレコードが標準出力に印字されます。見る項目は4つです。Bodyにメッセージが入っているか、SeverityNumberが期待した数値か、TraceIdとSpanIdが16進の文字列として埋まっているか、リソース属性にサービス名があるか。

この段階で空のフィールドは、バックエンド側の設定をいじっても埋まりません。検証が済んだらエクスポータを外してください。全件を印字したまま運用に流すと、Collector自身が新たなログの発生源になります。相関先となるトレース側の受け口はJaegerとは?v2でOpenTelemetryを取り込んだ分散トレーシングの仕組みで扱っています。

既存ロガーからの移行手順と欠損・二重計上を避ける切り替え設計

並走期間をどう設計するかで、移行時の事故の有無が決まります。

Collector前段化から始める段階移行の順序と並走期間の決め方

推奨する順序は、アプリを触らない工程から始めることです。まず既存の収集エージェントの隣にCollectorを立て、同じログを読ませて既存バックエンドへ流します。アプリ側の変更はゼロで、見た目の結果も従来どおりです。次に送信先を1つ追加し、新旧のバックエンドへ流して検索性とコストを実測します。並走期間の目安は、月次のバッチや締め処理を含む1サイクルです。

最後にアプリ側の計装を入れて相関を成立させれば移行は完了します。この順序ならどの工程で問題が出ても直前の状態へ戻せますが、逆順で進めると切り戻しにアプリの再デプロイが必要です。

並走期間に二重計上される取り込み量と保持期間まで伸びる課金の見積もり

並走している間、取り込み量は単純に2倍になります。従量課金なら、取り込みGB単価と1日あたりの出力量、並走日数の掛け算で増分を先に出してください。見落とされやすいのは保持期間側です。並走中に取り込んだ分は保持期間いっぱい両方に残るため、30日保持で2週間並走すれば影響は終了後もひと月続きます。並走中だけ保持期間を縮める調整で総量を抑えられます。

切り戻し条件の決め方とCollector障害時にログを落とさない設計

切り戻し条件は、切り替え前に数値で決めておきます。相関が付いたログの割合が想定を下回る、取り込み遅延が閾値を超える、コストが見積もりを超過する。この3つを閾値付きで先に合意しておけば、切り替え後の判断で揉めません。エクスポータの送信キューをディスク上のストレージ拡張に置けば、Collectorが再起動しても未送信分が残ります。ファイル収集方式が直送方式より障害に強いのは、ファイルとオフセットという二重の逃げ道があるためです。

言語別Logsサポート状況で決まるSDK直送の採用可否と見送る場面

既存の解説は設定例に寄っており、「そもそも直送を選んでよいのか」に踏み込んでいません。

2026年8月時点の言語別Logs成熟度と直送を選べる言語の線引き

opentelemetry.ioの言語別ステータスを2026年8月14日時点で確認すると、ログシグナルの成熟度は言語ごとに大きく開いています。トレースがほぼ全言語でStableなのとは対照的です。

言語 Logsの状態 直送の可否
Java Stable 本番投入できる
.NET Stable 本番投入できる
PHP Stable 本番投入できる
Go Beta 条件つきで可
Rust Beta 条件つきで可
Python Development 見送る
JavaScript Development 見送る
Ruby Development 見送る

各SDKの版も実測しました。Javaは2026年8月7日公開のv1.65系、Goは同8月3日のv1.45系、Pythonは7月16日のv1.44系、JavaScriptは7月21日のv2.10系です。ただしこれはトレースを含むSDK全体の版で、ログシグナル単体の安定度とは一致しません。「v1系だから安定」と読むと破壊的変更に当たります。

SDK直送を見送るべき3条件と代わりにfilelog receiverへ寄せる判断

次の条件に1つでも当てはまるなら、直送は見送ってファイル収集に寄せてください。

  1. 対象言語のログ実装がBeta以下である。破壊的変更をアプリの再デプロイで受け止め続けることになる
  2. アプリの改修権限がない、または改修に稟議を要する既存システムである
  3. プロセス異常終了の直前のログを失えない要件がある

逆に、Javaの新規サービスで標準出力を経由させずに属性の型を保ったまま送りたいなら直送を選ぶ理由があります。判断を分ける軸は「言語の実装成熟度」と「アプリを触れるか」の2つで、規模やトラフィック量ではありません。

ベンダーロックイン回避を理由にした導入が成立しない構成の条件

導入理由として挙がりやすいのが、監視ベンダーからの乗り換え自由度です。ただし効果が出るかは構成に依存します。断言すると、Collectorを挟まずSDKからベンダーのエンドポイントへ直接送る構成では、自由度はほとんど得られません。送信先の変更にアプリの再デプロイが要り、ベンダー固有エージェントと手間が変わらないためです。効果が出るのは、アプリはCollectorへ送り、切り替えを設定変更だけで完結させる構成に限られます。

もう1つ、実務で起きやすい誤算があります。OpenTelemetryへ寄せると、収集した後の運用は自社持ちです。パイプラインの詰まり、Collectorの版上げ、パース設定の保守が、そのまま自社の運用項目へ移ります。体制が整わないまま基盤だけ載せ替えると、障害時に見るべき画面が増えるだけです。外部に預ける前提なら、システム保守運用・内製化支援のように監視・運用自動化まで受け持つ委託先を移行計画の段階から入れてください。

OpenTelemetryのログ送出・相関・移行に関するよくある質問

実装時に判断が止まりやすい論点を、5つに絞って回答します。

OpenTelemetryのログもトレースのように計装コードが必要ですか?

不要です。仕様がログ用の新しいユーザー向けAPIを設けない方針のため、アプリのコードは既存のログライブラリの呼び出しのままで構いません。必要なのは、その出力をデータモデルへ橋渡しするアペンダの設定か、Collectorでファイルを読み取る設定のどちらかです。文言や出力箇所を書き換える作業は発生しません。

filelog receiverとFluent Bitはどちらを使うべきですか?

既にFluent Bitが安定稼働しているなら、無理に置き換える必要はありません。Fluent Bit側からOTLPで送出する構成が組めるためです。判断が変わるのは、トレースやメトリクスも同じCollectorで扱う場合です。3シグナルを1つのパイプラインに寄せれば、プロセッサ設定や属性の付け方を1か所で管理できます。メトリクス側の計器選定とPrometheusへの受け渡しはOpenTelemetryのメトリクス実装で扱っています。

ログにtrace_idが入りません。どこから確認すべきですか?

Collectorのdebugエクスポータで、受信時点のレコードにTraceIdが入っているかを最初に見てください。空なら原因はアプリ側です。空でないのに結合できないなら、バックエンド側の相関フィールド名を疑います。アプリ側が原因なら、スパンの外で出力されたログか、非同期処理で伝播が切れているかのどちらかがほとんどです。同一リクエストのログを時系列で並べ、どの処理を境にIDが消えるかを見れば切り分けられます。

Collectorが落ちている間のログは失われますか?

構成によって変わります。SDK直送ではアプリ内のキューに溜まり、溢れた分から捨てられます。プロセスが落ちればキューごと消える点に注意してください。ファイル収集ならノード上にファイルが残るため、復帰後に続きから読み直せます。ただしオフセットをストレージ拡張で永続化していることが前提です。永続化していない構成では、再起動後は末尾から読む挙動になり停止中の分が欠けます。

Pythonで書かれた既存アプリのログを今すぐ直送してよいですか?

見送りを推奨します。2026年8月時点でPythonのログ実装はDevelopment段階にあり、破壊的変更をアプリの再デプロイで受け止め続ける負担が残るためです。標準出力にJSONで構造化ログを出し、filelog receiverで拾う構成なら、変更は出力形式の整備だけで済みます。相関用の識別子は、スパンコンテキストから取り出してフィールドへ書き出せば直送しなくても成立します。

関連記事

資料請求

RELATED POSTS 関連記事