8.4.4 ログと監視
多くの LLM アプリはローカルの Demo ではうまく動きますが、本番に出すとすぐにひとつの問題が見えてきます。
問題が起きても、どこが壊れたのかまったく分からない。
ログと監視の価値は、「たくさん記録すること」ではなく、次の点にあります。
システムに問題が起きたときに、原因を特定し、説明し、再現し、修復できるようにすること。
学習目標
- ログ、指標、追跡がそれぞれ何の問題を解決するのか理解する
- 構造化ログのフィールド設計を学ぶ
- LLM システムで特に監視すべき指標を理解する
- 最小限のログ + 監視の例を読めるようになる
初学者向けの用語ブリッジ
可観測性は、次の言葉を分けて考えると理解しやすくなります。
| 用語 | 何に答えるか | LLM アプリでの例 |
|---|---|---|
log | ある瞬間に何が起きたか | 検索開始、モデル呼び出し失敗、エクスポート完了 |
metric | 全体の傾向はどうか | エラー率、P95 レイテンシ、平均 token コスト |
trace | 1件のリクエストはどの経路を通ったか | API -> 検索 -> モデル -> テンプレート描画 -> 返却 |
P95 / P99 | 遅い 5% または 1% のリクエストはどれくらい遅いか | 平均は悪くないのに、たまに遅いと感じる原因を見る |
observability | 外側からシステムの状態を理解できるか | ログ、指標、trace、ダッシュボード、アラートの組み合わせ |
まずはこう覚えると十分です。ログは個別イベント、指標は集計値、trace は1件のリクエストの複数ステップをつなぐものです。
まずは全体像をつかもう
ログと監視は、「何が起きたか -> 全体の様子はどうか -> 1件のリクエストは何を通ったか」という順で理解すると分かりやすいです。
この節で本当に解決したいのは次のことです。
- 問題が起きたとき、まずどの層を見るべきか
- なぜログ、指標、trace のどれかひとつでも欠けると、原因調査が難しくなるのか
なぜこの話がとても重要なのか?
LLM システムの障害は、普通の API より見えにくい
普通の API のエラーは、たいてい分かりやすいです。
- 500
- タイムアウト
- パラメータの間違い
でも LLM システムには、次のような“やわらかい障害”があります。
- 回答の質が下がる
- 検索結果がずれる
- token コストが急増する
- 特定の場面でだけ失敗する
だから観測できる仕組みがないと、システムはよくこうなります。
まだ動いているように見えるのに、実はもう半分壊れている。
ログと監視は、何を解決するのか?
ざっくり 3 層に分けて考えられます。
- ログ:何が起きたか
- 指標:どれくらい起きているか、どれくらい速いか、どれくらい高いか
- 追跡:1件のリクエストがどの手順を通ったか
初心者向けのたとえ
可観測性は、次のように考えるとイメージしやすいです。
- システムにメーターパネル、ドライブレコーダー、整備記録を付ける
これがないと、システムが壊れても言えるのはせいぜいこうです。
- なんとなく変だ
でも、これがあれば分かるようになります。
- どこから異常が始まったか
- それは一時的か、ずっと続いているか
- 1件だけの問題か、システム全体の問題か
まずログを見る:いちばん基本で、いちばん壊されやすい
「構造化ログ」とは?
ただ1行の文字列を出すよりも、たとえばこういう出力です。
print("request received")
それより価値が高いのは、次のような構造化されたフィールドを記録することです。
- request_id
- user_id
- stage
- latency_ms
- model_name
最小の構造化ログの例
log = {
"trace_id": "trace_001",
"stage": "retrieval",
"query": "返金ポリシーは何ですか",
"latency_ms": 120,
"top_k": 3
}
print(log)
想定出力:
{'trace_id': 'trace_001', 'stage': 'retrieval', 'query': '返金ポリシーは何ですか', 'latency_ms': 120, 'top_k': 3}
このログのいちばん大きな利点は、
あとからフィールドごとに検索したり、集計したりできることです。文字列を人間が目で読むだけではありません。
指標:システム全体の体温計
監視すべき指標の代表例
LLM システムでよく見る指標には、次のようなものがあります。
- リクエスト数
- エラー率
- 平均レイテンシ
- P95 / P99 レイテンシ
- token 使用量
- ツール呼び出し回数
- 検索ヒット率
最小の指標集計の例
requests = [
{"latency_ms": 800, "tokens": 600, "ok": True},
{"latency_ms": 1200, "tokens": 750, "ok": True},
{"latency_ms": 3000, "tokens": 900, "ok": False}
]
avg_latency = sum(r["latency_ms"] for r in requests) / len(requests)
error_rate = sum(not r["ok"] for r in requests) / len(requests)
avg_tokens = sum(r["tokens"] for r in requests) / len(requests)
print("avg_latency_ms =", avg_latency)
print("error_rate =", error_rate)
print("avg_tokens =", avg_tokens)
想定出力:
avg_latency_ms = 1666.6666666666667
error_rate = 0.3333333333333333
avg_tokens = 750.0
これが、監視ダッシュボードの最小の形です。
初学者がまず覚えるとよい指標表
| 指標 | 何に答えているか |
|---|---|
| リクエスト数 | システムは忙しいか |
| エラー率 | システムはよく失敗しているか |
| 平均 / P95 レイテンシ | ユーザーは待たされすぎていないか |
| token 使用量 | コストが異常ではないか |
| 検索ヒット率 | RAG の流れが悪くなっていないか |
| ツール呼び出し成功率 | Agent の実行層は安定しているか |
この表は、初心者にとても向いています。
「指標はたくさんある」という話を、理解しやすい問いに戻してくれるからです。

ログは「何が起きたか」、指標は「全体の傾向はどうか」、trace は「1件のリクエストがどこを通ったか」を答えます。LLM システムの障害対応では、この3つをつなげて見る必要があります。500 とタイムアウトだけを見ていても足りません。
追跡(trace):1件のリクエストは、何をたどったのか?
なぜ LLM システムでは trace が特に重要なのか?
1件のリクエストが、1つのモジュールだけを通るとは限らないからです。たとえば次のような流れがあります。
- API 受付
- 検索
- ツール呼び出し
- モデル生成
- 後処理
もし最終的な回答が間違っていたら、知りたいのは次の点です。
- 検索が間違っていたのか
- モデル生成が間違っていたのか
- それともツール層が落ちたのか
最小の trace の例
trace = [
{"trace_id": "trace_001", "stage": "api_in", "latency_ms": 20},
{"trace_id": "trace_001", "stage": "retrieval", "latency_ms": 120},
{"trace_id": "trace_001", "stage": "llm_generate", "latency_ms": 850},
{"trace_id": "trace_001", "stage": "response_out", "latency_ms": 15}
]
for item in trace:
print(item)
想定出力:
{'trace_id': 'trace_001', 'stage': 'api_in', 'latency_ms': 20}
{'trace_id': 'trace_001', 'stage': 'retrieval', 'latency_ms': 120}
{'trace_id': 'trace_001', 'stage': 'llm_generate', 'latency_ms': 850}
{'trace_id': 'trace_001', 'stage': 'response_out', 'latency_ms': 15}
trace の核心は、
同じリクエストの「完全な旅の記録」を見られることです。
はじめて本番障害を調べるときの、いちばん安全な順番
通常は、次の順で見るのが安定しています。
- まず指標に全体的な異常があるかを見る
- 次にログで、どの種類のリクエストが失敗しているかを見る
- 最後に trace をたどって、全体の流れを確認する
この順番のほうが、いきなり大量のログを読むよりずっと原因を見つけやすいです。
実際に近い、最小の観測ループ
import time
def timed_stage(name, fn, *args, **kwargs):
start = time.time()
result = fn(*args, **kwargs)
latency_ms = int((time.time() - start) * 1000)
log = {
"trace_id": "trace_demo_001",
"stage": name,
"latency_ms": latency_ms
}
print(log)
return result
def fake_retrieve(query):
time.sleep(0.1)
return ["返金ポリシー"]
def fake_llm(docs):
time.sleep(0.2)
return f"{docs} に基づいて回答を生成する"
docs = timed_stage("retrieval", fake_retrieve, "返金ポリシーは何ですか")
answer = timed_stage("llm_generate", fake_llm, docs)
print(answer)
出力例です。実際の latency_ms は少し変わることがあります。
{'trace_id': 'trace_demo_001', 'stage': 'retrieval', 'latency_ms': 100}
{'trace_id': 'trace_demo_001', 'stage': 'llm_generate', 'latency_ms': 200}
['返金ポリシー'] に基づいて回答を生成する
この例は小さいですが、すでに次の重要な項目が入っています。
- trace_id
- stage
- latency
LLM システムで、追加で特に監視すべきもの
普通の API と比べて、LLM システムでは次の項目もよく監視します。
token コスト
これは、次のことに直結するからです。
- いくらお金がかかっているか
- prompt がどんどん長くなっていないか
検索品質
たとえば次のようなものです。
- top-1 が当たっているか
- 検索結果が空の割合
ツール呼び出しの品質
たとえば次のようなものです。
- ツール呼び出し成功率
- パラメータ検証の失敗率
- リトライ率
回答品質のシグナル
たとえば次のようなものです。
- ユーザーの追加質問率
- ユーザーの修正率
- 低評価率
こうした指標はオフライン評価の代わりにはなりませんが、とても重要です。
なぜアラートは「サービスが落ちたか」だけを見てはいけないのか?
LLM システムの多くの問題は、直接 500 にはならない
たとえば次のような問題があります。
- 回答品質が落ち続ける
- token 使用量が急に2倍になる
- 検索ヒット率が大きく落ちる
こうした問題では、システムはまだ「動いている」かもしれません。
でも、業務としてはもう明らかに悪化しています。
だからアラートは 2 層に分けるのがよい
-
基本可用性アラート
- エラー率
- タイムアウト率
-
業務品質アラート
- 検索ヒット率の低下
- 平均 token 数の異常増加
- ユーザーのネガティブ反応の増加
初学者がまず覚えるとよいアラートの分け方
| アラートの種類 | 典型例 |
|---|---|
| 可用性アラート | エラー率が高い、タイムアウト率が高い |
| コストアラート | token が急増する、呼び出し回数が異常 |
| 品質アラート | 検索ヒット率が下がる、ユーザーの追加質問率が上がる |
この表は、LLM システムの「壊れ方」は1種類ではない、と教えてくれます。
「知識ベース駆動の教材生成アシスタント」なら、最初に何を監視すべき?
この種のシステムは、普通の質問応答よりも「見た目は大丈夫そうなのに、実はずれている」問題が起きやすいです。
最初に作るときは、特に次のフィールドを追うとよいです。
| 監視ポイント | 何を見ているか |
|---|---|
retrieved_count | 内部資料をちゃんと回収できたか |
example_count | 本当に例題を抽出できたか |
source_origin_mix | 内部資料と外部資料、どちらが主になっているか |
export_success | Word の出力に成功したか |
schema_valid | 構造化結果がテンプレート要件を満たしているか |
最小のログオブジェクトは、たとえばこう書けます。
log = {
"trace_id": "trace_001",
"topic": "割引の文章題",
"retrieved_count": 5,
"example_count": 2,
"schema_valid": True,
"export_success": True,
}
print(log)
想定出力:
{'trace_id': 'trace_001', 'topic': '割引の文章題', 'retrieved_count': 5, 'example_count': 2, 'schema_valid': True, 'export_success': True}
この例は初心者にとても向いています。
なぜなら、この種のプロジェクトの監視ポイントが、
- モデルが速いかどうか
- だけではなく、
- ちゃんと資料を取れているか
- 構造が形になっているか
- 文書を書き出せているか
まで含むと分かるからです。
とても実用的なログフィールドの一覧
LLM サービスを作るなら、実用的なフィールドはだいたい次のとおりです。
| フィールド | 役割 |
|---|---|
| trace_id | 全体の流れをつなぐ |
| user_id / session_id | ユーザーや会話を特定する |
| stage | 今どの工程にいるか |
| latency_ms | その工程にどれくらいかかったか |
| model_name | どのモデルを使ったか |
| prompt_tokens / completion_tokens | コスト分析 |
| tool_name | どのツールを呼んだか |
| retrieval_topk | 検索設定 |
| error_code | 失敗の種類 |
すべてのログに全部入れる必要はありませんが、設計の出発点としてはとても使いやすい一覧です。
初学者がよくやるミス
文字列だけを記録して、フィールドを記録しない
あとで集計しづらくなります。
成功だけを記録して、失敗を記録しない
これだと、問題の切り分けがとてもつらくなります。
trace_id がない
問題が起きても、1本の流れを最後まで追えません。
システム可用性だけを監視して、業務品質を監視しない
LLM プロジェクトでは、これは特に起こりやすい落とし穴です。
まとめ
この節でいちばん大事なのは、「ログの出し方を覚えること」ではなく、次を理解することです。
ログ、指標、trace が一緒になって、システムの可観測性を作る。これが、本番に出した LLM サービスを本当に運用できるかどうかを決める。
観測できなければ、多くの障害は推測するしかありません。
観測できれば、システムは初めて保守可能になります。
これをプロジェクトやシステム設計として見せるなら、何を見せるべきか
見せる価値が高いのは、たいてい次のようなものです。
- 「ログシステムをつないだ」こと
- ではなく、
- 1件のリクエストの trace
- 主要な指標のセット
- 典型的なエラーをどう特定したか
- 品質アラートと可用性アラートをどう分けたか
こうすると、見る人には次のことが伝わりやすくなります。
- あなたが理解しているのは、可観測性の閉ループである
- 単にログを print できるだけではない
練習
- 本節の
timed_stage()にerror_codeフィールドを追加してみましょう。 - 検索フェーズ専用の、自分のログ構造を設計してみましょう。
- 考えてみましょう。サービスのエラー率は変わっていないのに、ユーザーの追加質問率が急に上がったら、普通は何を意味するでしょうか?
- 自分の言葉で説明しましょう。なぜ LLM システムのアラートは、500 とタイムアウトだけを見ていてはいけないのでしょうか。