![LLM 日誌最佳實踐:我們在生产環境遵守的 9 條規則 [2026]](/_next/image?url=https%3A%2F%2Fmedia.techsy.io%2Ftechsy-io%2Fhero-510-1200x630.webp&w=3840&q=75)
LLM 日誌最佳實踐:我們在生产環境遵守的 9 條規則 [2026]
這 9 條 LLM 日誌最佳實踐是我們生產環境實際運行的規則:每月跨四個服務記錄 120 萬次 LLM 請求,每一筆都以單行 JSON 寫入 Grafana Loki,包含 model、tokens、latency、cost_usd 和 trace_id。structlog 25.4.0 負責寫入記錄,Presidio 在寫入前剝離 PII,整條管線是我們可觀測性架構的日誌層。
重點摘要
- 每筆 LLM 請求都以結構化 JSON 記錄,至少 14 個命名字段,絕不使用自由文字。
- 寫入前用 Presidio 或同等工具脫敏 PII,而非事後補救。
- 每條追蹤都附加 OpenTelemetry GenAI 語義慣例屬性。
- 每日百萬請求下,同樣的 60GB 資料:Datadog 月費 $108、Loki $30、ClickHouse $1.20。
LLM 日誌到底是什麼(為什麼「全部記下來」行不通)
LLM 日誌指的是為每一次模型請求與回應擷取一筆結構化記錄:prompt、completion、token 數量、延遲、成本,以及將一切串回使用者 session 的追蹤。它不是基礎設施日誌。CPU、記憶體、Pod 重啟屬於指標系統;本文只涵蓋請求層級的記錄,讓你能夠偵錯、控管成本、稽核模型行為。
「全部記下來」的直覺很難改掉,而且代價高昂。每日百萬請求的完整 prompt 和 completion 每月產生約 60GB 文字,其中一部分是你正在無限期儲存的客戶 PII。GDPR 第 5 條資料最小化原則要求個人資料「充分、相關且限於必要範圍」,而原封不動的 prompt 傾倒在第一天就不合格。全部記錄不是策略,是一張每月寄來的帳單。
9 條 LLM 日誌規則是什麼?
九條規則,按照我們建議的實作順序:用雜湊識別碼記錄完整 prompt 和回應、輸出結構化 JSON、逐請求擷取 token 數與成本、附加 OpenTelemetry 追蹤上下文、寫入前脫敏 PII、高流量時取樣、設定保留分層、分離安全事件、讓結果可查詢。以下每條規則都附上程式碼或表格。
規則 1:記錄完整 Prompt 和回應(用雜湊,不用原始 PII)
記錄每筆請求的完整 prompt 和完整 completion,因為部分記錄就是你在事故發生時盯著螢幕卻找不到模型實際看到什麼的原因。唯一的例外是身份識別:絕不將原始使用者 ID、電子郵件或姓名寫入記錄。改用使用者 ID 的 SHA-256 雜湊值。雜湊仍然讓你透過離線查詢重建單一使用者的完整 session 歷史,同時日誌行本身對不該讀取的人毫無用處。系統 prompt 同理:做雜湊、記錄雜湊值、明文留在已有版本控制的 prompt 登錄表中。
規則 2:使用結構化 JSON:每個字段都有名稱,不留自由文字
無論用 Python 或其他語言實踐 LLM 日誌最佳實踐,結構化日誌 JSON 是不可妥協的底線:每個字段都有名稱、型別、可查詢,絕不以格式化字串傾倒。像 INFO called gpt-4o, took 812ms 這樣的自由文字行只能用 grep 搜。JSON 記錄可以按模型聚合、按成本加總、與追蹤關聯。OpenAI 自家的生產最佳實踐也推動同樣的理念:在 SDK 層擷取結構化中繼資料,而非用 print 語句。
以下是 Techsy 每個服務輸出的 schema,十四個字段:
{
"request_id": "req_01J9XK4M7Q",
"timestamp": "2026-07-18T09:24:31.482Z",
"environment": "production",
"model": "claude-sonnet-4-20250514",
"prompt_tokens": 1284,
"completion_tokens": 396,
"latency_ms": 812,
"cost_usd": 0.0098,
"system_prompt_hash": "sha256:9f2c1a4e...",
"user_id_hash": "sha256:b7e4d831...",
"rag_sources": ["kb/pricing-2026.md", "kb/refund-policy.md"],
"guardrail_result": "pass",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"span_id": "00f067aa0ba902b7"
}三個字段值得說明。cost_usd 在請求時根據 token 數和模型公開費率計算,絕不由夜間批次回填。兩個雜湊字段是規則 1 的妥協方案:離線可關聯,日誌中不透明。trace_id 和 span_id 是 W3C trace-context 值,正是規則 4 的主題。
如果四個服務直接呼叫供應商聽起來像四個需要埋點的地方,LiteLLM 代理可以集中處理:在所有供應商前面放一個日誌掛鉤。
規則 3:逐請求擷取 Token 數與成本
Token 用量追蹤屬於日誌行本身,而非明天才跑的資料倉儲作業。每個供應商都在回應中回傳輸入和輸出 token 數;在當下乘以模型的每 token 費率,將 cost_usd 寫入記錄。費率會變動,快取輸入 token 和新鮮輸入 token 的價格也不同,所以事後用靜態價格表計算成本會悄悄改寫歷史。每行都有成本之後,「哪個功能最貴?」變成一行查詢而非財務專案,也直接餵給降低 LLM API 支出的工作。
規則 4:附加追蹤上下文(OpenTelemetry GenAI Semconv)
沒有 trace ID 的日誌行是孤兒:你讀得到,但無法判斷是哪次重試、哪個 RAG 步驟、哪輪使用者對話產生了它。解法是 OpenTelemetry 的 GenAI 語義慣例,也就是檢測模型呼叫的標準屬性名稱。在活動 span 內輸出日誌,trace_id 和 span_id 就會自動附加,Grafana 中點一下就能從追蹤瀑布圖直達原始記錄。
每個 gen_ai span 值得設定的屬性:
| 屬性 | 型別 | 範例 | 用途 |
|---|---|---|---|
| gen_ai.system | string | "anthropic" | 供應商名稱 |
| gen_ai.request.model | string | "claude-sonnet-4-20250514" | 你請求的模型 |
| gen_ai.response.model | string | "claude-sonnet-4-20250514" | 實際回應的模型 |
| gen_ai.usage.input_tokens | int | 1284 | Prompt 大小 |
| gen_ai.usage.output_tokens | int | 396 | Completion 大小 |
| gen_ai.response.finish_reasons | string[] | ["stop"] | 生成結束原因 |
| gen_ai.response.id | string | "msg_01XK9..." | 供應商回應 ID |
from opentelemetry import trace
tracer = trace.get_tracer("ai-sdr")
with tracer.start_as_current_span("chat.completion") as span:
span.set_attribute("gen_ai.system", "anthropic")
span.set_attribute("gen_ai.request.model", "claude-sonnet-4-20250514")
response = client.messages.create(**payload)
span.set_attribute("gen_ai.usage.input_tokens", response.usage.input_tokens)
span.set_attribute("gen_ai.usage.output_tokens", response.usage.output_tokens)
span.set_attribute("gen_ai.response.finish_reasons", [response.stop_reason])
log.info("llm.request", cost_usd=cost) # Rule 2 record, trace IDs auto-attached規則 5:寫入前脫敏 PII
PII 脫敏必須在記錄寫入前完成,而非事後清理。一旦電子郵件地址進了 Loki,它也在你的物件儲存備份裡,「我們後來刪了」不是 GDPR 能接受的答案。在我們的架構中,Microsoft Presidio 作為 structlog 處理器運行,在電子郵件和電話號碼到達 Loki 之前攔截 94%;漏網的幾乎都是奇怪格式,我們發現後就補成自訂辨識器。
整個掛鉤只有十五行:
from presidio_analyzer import AnalyzerEngine
from presidio_anonymizer import AnonymizerEngine
analyzer = AnalyzerEngine()
anonymizer = AnonymizerEngine()
PII_ENTITIES = ["PERSON", "EMAIL_ADDRESS", "PHONE_NUMBER", "CREDIT_CARD", "IBAN_CODE"]
def redact_pii(text: str) -> str:
results = analyzer.analyze(text=text, language="en", entities=PII_ENTITIES)
return anonymizer.anonymize(text=text, analyzer_results=results).text
# In the structlog processor chain, before JSONRenderer:
# event_dict["prompt"] = redact_pii(event_dict["prompt"])脫敏與輸入輸出過濾器位於同一管線層,測試方式也該相同。我們的護欄管線將日誌中洩漏的電子郵件視為評估失敗,而非維運備註。
規則 6:高流量時智慧取樣
每日低於約 10 萬筆請求時,全部記錄。超過之後,全量記錄是在對你永遠不會讀的資料課儲存稅,取樣是保留重要記錄的方法。陷阱在於:隨機取樣對 LLM 流量是最差的選項,因為失敗、拒絕和五美元請求定義上就是稀有事件,均勻 10% 取樣率恰好丟掉你會偵錯的事件。按結果取樣,而非丟銅板。
| 策略 | 適用時機 | 複雜度 |
|---|---|---|
| 隨機(固定 10%) | 穩定流量下的基線量測指標 | 低 |
| 規則型 | 始終保留特定模型、租戶或路由 | 低 |
| 尾部型 | 保留慢、貴或出錯的請求;丟棄正常的 | 中 |
| 觸發型 | 僅在護欄觸發或評估失敗時保留完整上下文 | 中 |
| 自適應型 | 取樣率隨流量升降 | 高 |
常見配置是邊緣用規則型(生產環境和企業租戶:始終記錄),中間用尾部型。這個框架的日誌專屬角度:你的 guardrail_result 和 cost_usd 字段就是取樣信號,如果遵循了規則 2 和 8,它們已經存在。
規則 7:在需要之前就設定保留策略
日誌保留策略是你在冷靜時做的決定,因為替代方案是在兩倍流量時的成本審查中做。GDPR 第 5 條儲存限制原則規定個人資料保留「不超過必要時間」,實務上意味著分層保留:
| 層級 | 保留期 | 儲存 | 使用情境 |
|---|---|---|---|
| Hot | 7 天 | Loki / ClickHouse 本地磁碟 | 即時偵錯、值班查詢 |
| Warm | 30 天 | 物件儲存索引(S3) | Sprint 成本分析、事故回顧 |
| Cold | 1 年 | 壓縮 S3/GCS 封存 | 合規請求、年度稽核 |
Hot 快速且昂貴地回答「十分鐘前發生了什麼?」;Cold 緩慢且便宜地回答「我們三月跟這位客戶說了什麼?」。按時程自動刪除,否則分層只是一張圖。
規則 8:將護欄和安全事件分開記錄
安全事件(護欄攔截、拒絕、政策違規)不是遙測資料,而是稽核記錄,應該有自己的串流。三個原因。告警:prompt 注入攔截量飆升應該呼叫值班人員,而你無法在百萬筆例行記錄中調校那個告警。保留:合規可能要求安全記錄比偵錯日誌多保留數年。存取:稽核人員取得安全串流,而非你的整個消防水管。在主記錄中標記判定結果(guardrail_result: "block"),將完整記錄路由到獨立串流。什麼算安全事件,參見我們的護欄事件指南。
規則 9:讓日誌可查詢,而非只是儲存
一分鐘內查不到的日誌是備份,不是可觀測性信號。可查詢意味著索引字段、值班人員真的會的查詢語言,以及事故前就建好的儀表板。我們運行 Loki,每週查詢 30 次以上,用於成本異常、延遲回歸、「給我看昨天租戶 X 的所有拒絕」。Grafana Loki 文件是語法參考;真正值得的模式是直接對解析後的 JSON 字段過濾:
{service="ai-sdr"} |= "llm.request" | json
| model = "claude-sonnet-4-20250514"
| cost_usd > 0.05
| line_format "{{.timestamp}} {{.request_id}} ${{.cost_usd}} {{.latency_ms}}ms"五行,不需要匯出到 notebook。如果你目前的儲存做不到,那就是第一個要修的問題。
我們在生產環境實際記錄什麼
理論夠了。以下是我們 AI SDR 管線的脫敏後配置,就是開頭提到每月 120 萬請求的那個服務。它運行 structlog 25.4.0 渲染 JSON,透過 Promtail 送到 Grafana Cloud Loki。這條管線上的模型是 claude-sonnet-4-20250514,每次呼叫都經過規則 2 和 5 的處理器鏈:
import structlog
structlog.configure(
processors=[
structlog.contextvars.merge_contextvars,
structlog.processors.add_log_level,
structlog.processors.TimeStamper(fmt="iso"),
redact_pii, # Rule 5: Presidio, before serialization
add_otel_trace_ids, # Rule 4: trace_id + span_id from the active span
structlog.processors.JSONRenderer(),
],
)
log = structlog.get_logger()這套配置上線第一季的兩個數字。四個服務的月攝入量穩定在 47GB,p95 日誌寫入延遲為 3ms,也就是說管線對請求時間幾乎沒有可測量的影響。
回本的那個配置變更:我們在 2026 年 3 月為每筆日誌條目加上 cost_usd。一週內發現一個 prompt 模板在重試迴圈中每月燒掉 $340。一個暫態 API 錯誤觸發三次重試,每次都重送完整的 4,000-token 上下文。日誌讓它變成一行查詢;沒有逐請求成本,它會在下一季預算審查中變成一個無法解釋的項目。
大規模 LLM 日誌儲存成本是多少?
每日百萬請求下,同樣的資料 LLM 日誌儲存月費大約在 $1.20 到 $108 之間,取決於儲存後端。算法:一筆完整結構化記錄平均約 2KB,所以每日百萬請求是每天 2GB,也就是每月 60GB。以下是供應商公開定價(2026 年 7 月)中那 60GB 在三種常見後端的成本。
| 後端 | 定價模型(供應商公開,2026 年 7 月) | 60GB/月 | 備註 |
|---|---|---|---|
| Datadog LLM Observability | $0.10/GB 攝入 + $1.70/GB 索引 | ~$108 | 索引是昂貴的那行 |
| Grafana Cloud Loki | ~$0.50/GB 透過物件儲存 | ~$30 | 自建更便宜 |
| ClickHouse(自建,S3) | ~$0.02/GB 壓縮儲存 | ~$1.20 + 計算 | 計算才是真實成本 |
來源:Datadog 定價、Grafana Loki、ClickHouse 可觀測性文件。
兩個注意事項,因為這是我們根據供應商費率的計算,不是我們跑的基準測試。第一,Datadog 的數字假設你索引所有內容;多數團隊只索引子集,實際費用遠低於此,而 Loki 和 ClickHouse 主要按儲存量收費。第二,自建 ClickHouse 的 $1.20 隱藏了真實帳單:運行叢集的計算資源和維運的工程師工時。每月 60GB 下,託管服務幾乎總是總成本更低的選擇。自建在大約每月 1TB 以上才開始合理,屆時每 GB 的價差會壓過維運開銷。
價差就是重點。每日百萬請求下,Datadog 索引與自建 ClickHouse 的差距約 90 倍:同樣 60GB,$108 對 $1.20。在架構設計時選好儲存,而非收到帳單之後。
該選哪個日誌工具?
對多數團隊而言,選擇歸結為四個選項:LLM 原生平台(Langfuse 或 LangSmith)、代理層工具(Helicone),或你既有基礎設施上的純 OpenTelemetry 管線。下表涵蓋真正有差異的決策點;儀表板、回放、prompt 版本控制在四者中都是基本功能。
| Langfuse | LangSmith | Helicone | OTel 原生(Loki/ClickHouse) | |
|---|---|---|---|---|
| 可自建 | 是(開源核心) | 否(SaaS) | 是(開源) | 完全可以 |
| OTel 相容 | 是(OTLP 攝入) | 部分(OTLP 匯出) | 部分 | 原生 |
| 成本追蹤 | 是 | 是 | 是 | 自行計算 cost_usd |
| 內建 PII 脫敏 | 否(需前處理) | 否 | 否 | 否(Presidio,依規則 5) |
| 免費方案 | 是(雲端 + 自建) | 是(有限) | 是 | 軟體免費;你付基礎設施 |
我們的立場很明確:我們運行 OTel 原生加 Loki,因為其他一切已經有 Grafana 技術棧,多加一個資料來源勝過引入第四個供應商。如果你從零開始、完全沒有可觀測性技術棧,Langfuse 的追蹤模型和免費方案是最快達到可用的路徑,自建選項保留退路。如果你在兩個 LLM 原生領導者之間選擇,我們的 Langfuse vs LangSmith 比較有完整對比。如果日誌只是更大監控決策的一部分,完整平台比較涵蓋更廣的領域。
最常見的 LLM 日誌錯誤有哪些?
六個錯誤佔了我們見過的大多數 LLM 日誌問題。如果在日誌量暴增之前發現,每個都很便宜就能避免:
- 未脫敏就記錄原始 PII。 最常見也最昂貴。一次支援匯出或一次儲存桶外洩就讓 prompt 日誌變成資料保護事故。寫入前脫敏(規則 5),而非讀取時。
- 沒有保留策略。 無限期儲存在所有地方都是預設,而且每年悄悄讓帳單翻倍。如果你從不刪除,你有的不是日誌系統,是一個有妄想症的檔案庫。
- 非結構化文字日誌。 只能 grep 的 print 輸出在示範規模下可行,在每日 10 萬請求時崩潰,「找出模型 X 的所有失敗請求」變成一個下午的 shell 腳本而非一行查詢。
- 只記錄錯誤。 成功的請求是你檢測漂移的基線,也是評估管線的原料。成功也要記錄,流量大時取樣即可。
- 忽略成本字段。 沒有逐請求 cost_usd 就沒有成本告警、沒有逐功能歸因,上面生產環境那段提到的每月 $340 重試迴圈會隱形到季度帳單。
- 沒有追蹤關聯。 日誌與 span 斷開讓多步驟 agent 偵錯變成猜測。如果你的日誌行沒有 trace_id,規則 4 就是解法。
關於作者
Mert Batur 是 Techsy.io 的聯合創辦人,團隊為 B2B 客戶交付 AI agent、自動化系統和語音/SDR 管線。他撰寫 Techsy 團隊在生產環境實際使用的 LLM 工具棧。連結 LinkedIn。
常見問題
每筆 LLM 請求應該記錄什麼?
最低限度:脫敏後的完整 prompt 和 completion、模型名稱、輸入和輸出 token 數、延遲、美元成本、雜湊化使用者識別碼,以及 OpenTelemetry trace 和 span ID。如果管線有這些階段,加上 RAG 來源 ID 和護欄判定。十四個命名字段,每筆請求一行 JSON。
LLM 日誌的最佳格式是什麼?
結構化 JSON,每筆請求一個物件,每個字段都明確命名。自由文字日誌只能 grep;JSON 記錄可以按模型聚合、按成本加總、與追蹤關聯。用 structlog(Python)或 pino(Node)等結構化記錄器輸出記錄,用 JSON 序列化器渲染,絕不用字串格式化。
如何處理 LLM 日誌中的 PII?
寫入前脫敏,而非事後。在日誌管線中用 Microsoft Presidio 等檢測器處理 prompt 和 completion,將姓名、電子郵件和電話號碼替換為 <EMAIL_ADDRESS> 等標記。一旦原始 PII 到達日誌儲存,它也在備份中,事後刪除很少能滿足 GDPR 的最小化測試。
大規模 LLM 日誌儲存成本是多少?
每日百萬請求、每筆記錄 2KB、每月約 60GB 的情況下,Datadog 索引 LLM Observability 定價約 $108/月,Grafana Cloud Loki 約 $30/月,自建 ClickHouse 壓縮 S3 儲存約 $1.20/月加上計算資源。這些是 2026 年 7 月的供應商公開費率;自建另加工程時間。
什麼是 OpenTelemetry GenAI 語義慣例?
它們是 OpenTelemetry 用於檢測 LLM 呼叫的標準屬性名稱:gen_ai.system 代表供應商、gen_ai.request.model 代表模型、gen_ai.usage.input_tokens 和 output_tokens 代表 token 數、gen_ai.response.finish_reasons 代表生成停止原因。使用它們意味著任何 OTel 相容後端,從 Jaeger 到 Tempo 到 Langfuse,都能讀取你的追蹤而無需自訂解析器。
高流量下如何取樣 LLM 日誌?
保留每個錯誤、每次護欄攔截、每筆超過成本閾值的請求,其餘取樣。這種尾部型方法保留你實際偵錯的稀有事件,而均勻隨機取樣以相同比率丟棄它們和無聊流量。每日低於 10 萬請求時,跳過取樣,全部記錄。
LLM 日誌應該保留多久?
分層:Hot 7 天用於即時偵錯,Warm 30 天用於事故回顧和成本分析,Cold 最長 1 年在壓縮物件儲存中用於合規和稽核。GDPR 儲存限制原則禁止保留個人資料超過必要時間,所以每層搭配自動刪除而非手動清理。
LLM 日誌和 LLM 追蹤的區別是什麼?
日誌是單一事件的平面記錄:這筆請求發生了,有這些字段。追蹤是跨整個請求路徑的 span 因果樹,例如檢索、然後模型呼叫、然後兩次工具呼叫。日誌告訴你什麼;追蹤告訴你在哪裡、為什麼。生產環境兩者都輸出,用 trace_id 關聯。
結論
回顧:每筆請求以命名字段 JSON 記錄、請求時計算成本、附加 OTel 追蹤上下文、寫入前脫敏 PII、超過每日 10 萬請求後按結果取樣、選一個你真的能查詢的儲存。九條規則排序後可以每個 sprint 採用一條,規則 2、4 和 5 是回收最快的三條。如果你正在圍繞日誌選擇更大的監控技術棧,從我們的最佳 AI 可觀測性平台總覽開始。如果你需要協助為 LLM 技術棧接上結構化日誌,取得免費諮詢。