構造化ログと高基数ワイドイベント
想定外の障害も後から好きな切り口で掘れる。1リクエスト1ワイドイベントに高基数フィールドを載せ、行指向ログから観測の主役を構造化イベントへ移す設計を原理から解説。
- 1.行指向の人間可読ログから、1リクエスト=1ワイドイベント(多数の構造化フィールドを持つ1レコード)への転換が要点。集約済みメトリクスやテキストの grep では追えない問いに答えられる。
- 2.高基数(high-cardinality)フィールド(user_id・build_id・request_id 等)をイベントに載せることで、事前に決めていない任意の属性で絞り込み・分割(GROUP BY)でき、未知の不具合を探索できる。
- 3.ワイドイベントはトレースの span と本質的に同型。trace_id/span_id を全イベントに付ければ、ログ・トレースが1つのデータモデルに統合される(observability 2.0 の中核)。
行指向ログの限界:なぜ grep では足りないか
伝統的なログは 人間が読むための1行のテキスト でした。2026-06-21 12:03:11 ERROR checkout failed for user 4821 のような行を出し、障害時に tail と grep で目視追跡する。1プロセスのモノリスならこれで十分でした。
破綻するのは、問いが 集計済みメトリクスの粒度を超え、かつテキストの全文検索では絞れない ときです。たとえば「決済の p99 レイテンシが、特定のクライアントバージョンかつ特定のリージョンのユーザーだけで悪化している」を調べたいとします。メトリクスは集約の時点で次元を捨てているため、後からこの組み合わせを取り出せません(/devops/metrics-cardinality/)。一方で行指向ログは、フィールドが文字列に埋め込まれているため、build_id で GROUP BY して各群の p99 を出す、といった集計が原理的にできません。grep は行の有無しか答えられないのです。
ここで効くのが、ログを 機械が集計・結合できる構造 にする発想です。
構造化ログ:テキストからキー・バリューへ
構造化ログ(structured logging)は、ログ1件をキーと値の組の集合として出力します。多くは1行1 JSON オブジェクトです。
{"ts":"2026-06-21T12:03:11Z","level":"error","msg":"checkout failed",
"user_id":"4821","region":"ap-northeast-1","build_id":"v2.18.3",
"http_status":500,"db_ms":1840,"trace_id":"4bf92f35...","span_id":"00f067aa..."}
テキストとの決定的な違いは、各値が型と名前を持ったフィールドになる点です。これにより収集基盤側で region="ap-northeast-1" AND build_id="v2.18.3" で絞り、http_status の分布を出し、db_ms の p99 を計算する、といった列に対する演算が可能になります。ログがテキストの川ではなく、スキーマの緩いテーブルに変わるわけです。
構造化の価値は、後から問いたい次元をフィールドとして分離して持つことにあります。"msg":"failed for user 4821 in ap-northeast-1" のように、肝心の属性を文字列に埋め込んだままJSONで包んでも、user_id や region で集計できず利得はほぼゼロです。「後で切り口にしたいものは、すべて独立したフィールドにする」が原則です。
ワイドイベント:1リクエスト=1レコードへの転換
構造化ログの先にある中心概念が ワイドイベント(wide event) です。狙いは、1リクエスト(あるいは1作業単位)の処理中に分かった事実を 数十〜数百のフィールドを持つ1レコードに集約して、サービス境界ごとに1回だけ出す ことです。
従来は1リクエストの処理中に「受信した」「DB引いた」「外部API呼んだ」「失敗した」と複数行を散発的に吐いていました。ワイドイベントでは、処理の入口で空のイベントを生成し、処理が進むたびにフィールドを**追記(enrich)**し、最後に1回だけ出力します。
# 1リクエストの間、コンテキストにフィールドを足していき、終端で1イベントを emit する
ev = new_event() # リクエスト入口
ev.add(user_id, region, build_id, route, http_method)
ev.add(auth_ms, db_ms, cache_hit, db_rows) # 処理の各段階で追記
ev.add(upstream_status, retry_count)
ev.add(http_status, total_ms, error_kind) # 終端
emit(ev) # サービス境界で1回だけ出力
この「1リクエスト1イベント」が効く理由は、相関(correlation)がレコード内に閉じるからです。複数行ログだと「同じリクエストの行」を後から request_id で突き合わせる(self-join)必要がありますが、ワイドイベントなら db_ms と build_id と http_status が 最初から同じ行に並んでいる。だから「build_id=v2.18.3 のうち cache_hit=false だったリクエストの total_ms 分布」のような、事前に想定していない交差した問いに、結合なしで即答できます。
複数行ログでの調査は、暗黙のうちに「同一 request_id の行を集めて再構成する」結合作業です。リクエストが多重化・並行化するほどこの結合は重く・曖昧になります。ワイドイベントは出力時点で結合済みの1行を作るので、クエリ側は単純な絞り込みと集計だけで済みます。
高基数(high-cardinality)が探索の鍵
ワイドイベントの真価は 高基数フィールド を載せられる点にあります。基数(cardinality)とは、ある属性が取り得る値の種類数です。region(数十種)は低基数、user_id・request_id・build_id・device_fingerprint(数百万〜ほぼ無限)は高基数です。
メトリクスでは高基数フィールドをラベルにできません。時系列ストアはラベル値の直積ごとに別系列を作るため、user_id をラベルにすると系列数が爆発し基盤が崩れます(/devops/metrics-cardinality/)。これが「集約は次元を捨てる」の正体です。
イベント側は事情が違います。イベントは事前集約せず生レコードとして保存し、クエリ時にその場で集計します。だから user_id をフィールドに持っても、保存コストは1リクエスト1行のまま増えません。結果として、
- 「特定の1ユーザーだけが失敗している」を後から
user_idで絞って確認できる - 「あるデプロイ(build_id)以降だけ 遅い」をデプロイ単位で分割して比較できる
- 障害発生後に「実はこの組み合わせが犯人だった」という、計測時に予期しなかった切り口で掘れる
つまり高基数フィールドは、モニタリングが答えられる "known unknowns" を超えて、"unknown unknowns" を探索可能にする装置です。これが「想定外の問題も後から調べられる」というオブザーバビリティ本来の定義(/devops/observability/)に、ログ側から到達する道筋になります。
observability 2.0:イベントが土台、メトリクスとトレースは派生
従来の「3本柱」モデル(メトリクス・ログ・トレースを別々のストアに別々に持つ)は、信号間を trace_id で人手リンクする前提でした。これに対し近年提唱される observability 2.0 は、単一の高基数ワイドイベントを土台のデータソースとし、メトリクスもトレースもそこから導出するという構図です。
ここで重要なのは、ワイドイベントとトレースの span が同型だという事実です。span は「名前・開始/終了時刻・属性・親子関係」を持つ1区間のレコードでした(/devops/distributed-tracing/)。これはまさに、trace_id・span_id・parent_span_id・所要時間・任意属性を持つワイドイベントそのものです。つまり 1サービス境界あたり1ワイドイベントを出し、そこに trace コンテキストを載せれば、それはそのまま span になる。ログとトレースが別物ではなく、同じレコードの2つの見え方になります。
| 観点 | observability 1.0(3本柱) | observability 2.0(ワイドイベント) |
|---|---|---|
| 土台データ | メトリクス/ログ/トレースが別ストア | 高基数ワイドイベント1種が土台 |
| メトリクス | 計測時に集約して別途記録 | イベントへのクエリ時集計で導出 |
| トレース | ログとは別系統で相関は trace_id 頼み | イベント=span なので本来同一物 |
| 高基数の扱い | メトリクスのラベルにできず捨てる | フィールドとして保持し探索に使う |
| 未知の問いへの強さ | 事前に決めた次元しか追えない | 任意フィールドで後から分割・絞込 |
導出関係を具体に落とすと、count() で GROUP BY route すればリクエスト数メトリクスに、heatmap(total_ms) でレイテンシ分布に、同じ trace_id の span 群を時系列に並べればウォーターフォール(トレース)になります。メトリクスとトレースが、同じイベント集合への異なるクエリになるわけです。p99 のような分位点を群ごとに正しく出すには、集約済み値ではなく生イベントが残っている方が有利で、これは分位点統計の性質とも整合します(/devops/percentile-latency-statistics/)。
実装上の勘所と落とし穴
- コンテキストの伝播でフィールドを引き回す:リクエスト入口で生成したイベント(またはトレースコンテキスト)を、処理全体で参照できるようにし、各段階で
addする。これを怠ると終端で属性が揃わず、ワイドにならない。OpenTelemetry の span 属性として載せる実装が定石です(/devops/opentelemetry-internals/)。 - 基数の暴走に注意する分岐:高基数で良いのはイベント側のフィールド。同じ値をメトリクスのラベルに流用すると系列爆発で基盤が壊れる。「どの信号に何の属性を載せるか」を明確に分けることが、observability 2.0 でも変わらない設計の核です。
- PII(個人情報)と容量:
user_idやリクエスト本文など機微・大容量の値をそのまま載せると、保管とプライバシーの両面でリスク。ハッシュ化・マスキング・上限長を境界で適用する。 - サンプリングは「イベント=span」前提で:全リクエストを保存すると量が膨大なので、遅い・失敗したものを確実に残すテールサンプリングが有効。ワイドイベントはトレースと一体なので、トレースのサンプリング戦略がそのまま適用できる。
「メトリクスのラベルに user_id を入れてはいけないのはなぜか」は頻出です。答えは 時系列ストアがラベル値の直積ごとに系列を作るため、高基数ラベルは系列数を爆発させ基盤を破綻させる から。そして「ならば user_id でどう調査するか」への正答が 高基数フィールドを持つ構造化イベント(ワイドイベント)側に載せ、クエリ時に集計する です。この役割分担が言えるかが理解の分かれ目になります。
まとめ
- 行指向の人間可読ログは grep の対象でしかなく、集計も交差した絞り込みもできない。構造化ログは値を名前付きフィールドに分離し、ログを「緩いテーブル」に変える。
- ワイドイベント=1リクエスト1レコード。処理中に追記して終端で1回出すことで、相関がレコード内に閉じ、結合なしで未知の交差問いに答えられる。
- 高基数フィールド(user_id・build_id 等)はイベントには載せられるがメトリクスのラベルには載せられない。これが "unknown unknowns" を探索可能にする。
- ワイドイベントは span と同型。trace コンテキストを載せればログとトレースが統合され、メトリクスもトレースも同一イベント集合への別クエリとして導出できる——これが observability 2.0 の中核。
DevOps/インフラ Article
構造化ログと高基数ワイドイベントを実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
構造化ログ
比較で見る軸
難易度: advanced / カテゴリ: DevOps/インフラ / タグ数: 5
導入後に効く点
高基数(high-cardinality)フィールド(user_id・build_id・request_id 等)をイベントに載せることで、事前に決めていない任意の属性で絞り込み・分割(GROUP BY)でき、未知の不具合を探索できる。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- DevOps/インフラ
- タグ数
- 5
判断チェックリスト
- 自社の用途が「構造化ログ / オブザーバビリティ」に近いか確認する。
- 強みである「行指向の人間可読ログから、1リクエスト=1ワイドイベント(多数の構造化フィールドを持つ1レコード)への転換が要点。集約済みメトリクスやテキストの grep では追えない問いに答えられる。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。