遙測(Telemetry)

OpenTelemetry span / metric / event —— 且不導出任何 prompt 內容

一句話

OCR 內建一流 OpenTelemetry 支援。啟用後,每次評審產出結構化 span、metric、event——足以回答「agent 把時間花在哪?」「各模型成本如何?」「這次為何失敗?」。

預設關閉。遙測預設不啟用——要主動打開。

兩種 exporter

Exporter何時使用
console個人使用 / 除錯。span 格式化印到 stdout。
otlp系統整合。送任何 OTLP 相容 collector(Jaeger、Tempo、OTel Collector、Datadog…)。

啟用

# config 方式
ocr config set telemetry.enabled        true
ocr config set telemetry.exporter       otlp
ocr config set telemetry.otlp_endpoint  localhost:4317

# 環境變數方式
export OCR_ENABLE_TELEMETRY=1
export OTEL_EXPORTER_OTLP_ENDPOINT=localhost:4317   # 同時強制 exporter=otlp
export OTEL_SERVICE_NAME=open-code-review

Span

review.run
├── diff.parse
├── event.review.started
├── subtask.execute.<file1>
│   ├── event.plan.skipped
│   ├── event.token.threshold.exceeded
│   └── event.subtask.error
├── subtask.execute.<file2>
└── …

LLM 往返與工具執行不作為獨立 span——只出現在 metric。決策點事件是短生命週期的 event.<name> span。

Metric(節選)

Metric型別標籤
ocr.review.duration_secondshistogram—
ocr.files_reviewed_totalcounter—
ocr.comments_generated_totalcounter—
ocr.llm.requests_totalcountermodel、status
ocr.llm.tokens_usedcountermodel、type
ocr.tool.calls_totalcountertool.name、status

內容日誌(重點:隱私)

遙測導出 LLM 流量的形狀(計數、時長、狀態),但絕不導出實際 prompt 或回應。OCR 不嘗試把 LLM 訊息內容附加到 span 或 event。

content_logging config key 與 OCR_CONTENT_LOGGING=1 已接入配置層,但目前不控制任何發送 prompt 內容的程式路徑——視為保留位。

要看內容?用 Session Viewer 讀本地 JSONL——它們完全存在 ~/.opencodereview/ 下,絕不發往 collector。

解析優先級

  1. 預設(enabled=false、exporter=console、無 endpoint)。
  2. ~/.opencodereview/config.json 的 telemetry.*。
  3. 環境變數(最高優先級,覆蓋檔案)。

配方:CI 送 Tempo

- name: Code review
  env:
    OCR_LLM_URL: ${{ secrets.OCR_LLM_URL }}
    OCR_LLM_TOKEN: ${{ secrets.OCR_LLM_TOKEN }}
    OCR_LLM_MODEL: claude-opus-4-6
    OCR_ENABLE_TELEMETRY: "1"
    OTEL_EXPORTER_OTLP_ENDPOINT: ${{ vars.OTEL_COLLECTOR_URL }}
    OTEL_SERVICE_NAME: open-code-review-ci
  run: ocr review --from origin/main --to HEAD --audience agent

故障排查

症狀可能原因
什麼都沒匯出預設關閉——沒設 OCR_ENABLE_TELEMETRY / telemetry.enabled
OTLP 本地可用、生產失敗OCR 目前僅實作 OTLP/gRPC;http/protobuf 被接受但未接入
span 缺 promptOCR 絕不把 prompt 內容附加到遙測——用 viewer 檢查轉錄
📖 教學解說:遙測深入

為什麼遙測預設關閉

三個原因:① 隱私(不是每個人都想把執行遙測送到外部)② 效能(OTLP exporter 有網路開銷)③ 依賴(需要 collector)。所以預設關閉,要主動打開。

span 樹的實務解讀

每個評審的 span 樹長這樣:

review.run
├── diff.parse              ← diff 解析耗時
├── event.review.started    ← 開始事件
├── subtask.execute.file1   ← 每個檔案一個
│   ├── event.plan.skipped  ← plan 跳過(小 diff)
│   └── ...
└── subtask.execute.file2

LLM 往返不作為獨立 span——只出現在 ocr.llm.requests_total metric。所以看 LLM 延遲要看 metric,不是 span。

「不導出 prompt」的隱私保證

遙測導出的是形狀(計數、時長、狀態),不是內容(prompt、response、diff)。content_logging config key 已存在但目前不控制任何發送 prompt 內容的程式路徑——視為保留位。要看 prompt 內容?用 Session Viewer 讀本地 JSONL。

練習 / 驗收清單

  • 能說出兩種 exporter 的使用場景
  • 能描述三層解析優先級
  • 能解釋 span 樹的形狀
  • 能說明遙測為何絕不導出 prompt 內容
看完這頁你應該能說出:兩種 exporter、三層解析優先級、span 樹的形狀、哪些 metric 存在、以及最重要的——遙測為何絕不導出 prompt 內容。

延伸閱讀:設定 · 架構 · Session Viewer

① 進階真實情境 Worked Example:用 Tempo + Grafana 建一份「評審健康儀表板」

情境

你的 CI 每天跑 200+ 次 OCR 評審,你無法追蹤「評審健不健康」。你要把遙測送到 Tempo(trace)+ Prometheus(metric),在 Grafana 上看到:評審時長、成功率、模型成本、最常出錯的工具。

Step 1 — 起一個 OTLP collector(接收 gRPC)

# otel-collector-config.yaml
receivers:
  otlp:
    protocols:
      grpc:            # OCR 實作的是 OTLP/gRPC
exporters:
  otlp/tempo:
    endpoint: tempo:4317
  prometheus:
    endpoint: "0.0.0.0:8889"
service:
  pipelines:
    traces:  { receivers: [otlp], exporters: [otlp/tempo] }
    metrics: { receivers: [otlp], exporters: [prometheus] }

Step 2 — 在 CI 啟用遙測

env:
  OCR_ENABLE_TELEMETRY: "1"
  OTEL_EXPORTER_OTLP_ENDPOINT: collector:4317
  OTEL_SERVICE_NAME: open-code-review-ci
run: ocr review --from origin/main --to HEAD --audience agent

Step 3 — 在 Grafana 建三張面板

# 面板 1: 評審時長 (histogram)
sum(rate(ocr_review_duration_seconds_sum[5m])) /
sum(rate(ocr_review_duration_seconds_count[5m]))

# 面板 2: 模型成本 (tokens by type)
sum by (model, type) (rate(ocr_llm_tokens_used_total[5m]))

# 面板 3: 工具錯誤率
sum(rate(ocr_tool_calls_total{status="error"}[5m])) /
sum(rate(ocr_tool_calls_total[5m]))

Step 4 — 用 Tempo 追單次失敗 trace

在 Grafana 切到 Tempo datasource,搜 review.run,點開失敗的 trace,看哪個 subtask.execute.<file> 掛掉。

為什麼選這條審查路徑

「評審本身」也是一個要監控的系統。用 OCR_ENABLE_TELEMETRY + collector 把「形狀」(時長、計數、狀態)送出——不含任何 prompt 內容,符合隱私要求。選 Tempo + Prometheus 是因為 OTLP/gRPC 是 OCR 唯一實作的協定,本地驗證用 console exporter、上線用 OTLP 是最低摩擦路徑。

預期產出

Grafana 顯示:
  評審平均時長 42s(昨天 38s → 有漂移,追查發現 concurrency 撞 rate limit)
  模型成本: input 68% / output 32%
  工具錯誤率 3.1% → 集中在 code_search(repo 太大?)
  失敗 trace: subtask.execute.auth.go → context deadline exceeded

② 深入原理擴充:OTLP/gRPC 是「唯一已實作」的傳輸——HTTP 是陷阱

OCR 只實作 OTLP/gRPC

官方文件明確:OTLP/gRPC 已實作;http/protobuf 被接受但未接入。也就是說:OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4318(HTTP 埠)或 --otlp-http 風格設定會被「接受」(不報錯)但不會真的送資料。本機測試用 console exporter 可驗證 span 產生;要送 collector 務必確認走 4317(gRPC)。

「看起來啟用成功但其實沒送出」的案例

# 你設了:
export OCR_ENABLE_TELEMETRY=1
export OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4318   # ← HTTP 埠
export OTEL_SERVICE_NAME=open-code-review

# OCR 不報錯、正常跑完、exit 0 —— 但 Grafana 一片空白
# 原因: 4318 是 OTLP/HTTP 埠;OCR 只實作 gRPC (4317),HTTP 被接受但未接入

「不報錯 + 沒資料」是最難 debug 的組合。防禦:① 本機先跑一次 OCR_ENABLE_TELEMETRY=1(exporter 預設 console)確認 span 有印出;② 確認 endpoint 走 collector:4317(gRPC);③ 在 collector 側開 debug log 確認有 receiver 收到。

③ 診斷式疑難排解

症狀可能原因解決方案
Grafana 完全沒資料遙測沒啟用,或走錯 OTLP 埠(HTTP 4318 vs gRPC 4317)設 OCR_ENABLE_TELEMETRY=1;確認 endpoint 是 gRPC 埠
本機 console 有 span、collector 沒有exporter 被 env 設成 otlp,但 endpoint 不對確認 OTEL_EXPORTER_OTLP_ENDPOINT 指向 collector 的 4317
trace 有、metric 沒有collector pipeline 沒設 metrics exporter在 collector config 加 prometheus exporter 進 metrics pipeline
看不到 LLM 延遲LLM 往返不作為獨立 span,只在 metric看 ocr_llm_requests_total / ocr_llm_tokens_used,不看 span
想找「哪個工具最會失敗」—用 ocr_tool_calls_total{status="error"} 依 tool.name 分組

④ 進階挑戰題

  1. 挑戰一:畫出「console exporter」與「otlp exporter」的資料流差異——本機 debug 時為何 console 夠用、上線為何要 OTLP?
  2. 挑戰二:在 Grafana 寫一個查詢:顯示「過去 7 天、依 model 分組、每小時的 token 用量」。
  3. 挑戰三:判斷題——「OTEL_EXPORTER_OTLP_ENDPOINT 設了 HTTP 埠,OCR 會報錯」。這句話對嗎?為什麼「不報錯」反而更危險?