Project

General

Profile

CR #201 » CSP-004_可觀測性與日誌規範.md

內容更新:內部稱謂去識別化(2026-10-18) - Redmine Admin, 10/18/2026 05:32 AM

 

CSP-004 可觀測性與日誌規範(Observability & Logging)

項目 內容
文件編號 CSP-004
版本 1.2(C-29 程式面落地:新增 §6.1 單一讀取端 TraceContext,並記錄 CorrelationIdFilter 從未註冊之實測根因)
狀態 已核定
撰寫者 SA / SRE
審查者 Security Architect
對應需求 NFR-PERF 效能群組、NFR-OPS 維運群組;SAD-006

1. 目的

定義統一的結構化日誌、指標、分散式追蹤、健康檢查標準,使 17 個服務具備
一致的可觀測性,滿足 SoW 之效能門檻(授權端到端 P95 ≤ 500ms、核心 P99 ≤ 200ms、路由 ≤ 5ms)。


2. 三支柱

支柱 技術基線 產出
Logs SLF4J + Logback,JSON 結構化輸出 每行一筆 JSON 事件
Metrics Micrometer → Prometheus 計數器、計時器、分佈摘要
Traces OpenTelemetry → Tempo/Jaeger 跨服務呼叫鏈

3. 結構化日誌規範

產出方式:一律使用 Spring Boot 4.1 內建之結構化日誌
(logging.structured.format.file=ecs),不自訂 Logback encoder——
官方 Logback→OTel 橋接於 4.1 尚未內建、可用 appender 全為 -alpha
(詳 SCS-020 §9.6)。日誌寫檔後由 Collector 之 filelog receiver 讀取,
零第三方相依。

3.1 欄位對照(實測為準)

ECS 輸出為「巢狀/扁平混用、駝峰與底線並存」,勿依 ECS 慣例猜測:

邏輯欄位 實際 JSON 路徑 說明
時間 @timestamp ECS 慣例以 @ 開頭,非 ts
層級 log.level(巢狀) 非頂層 level
服務 service.name(巢狀) 非頂層 service
訊息 message 非 msg
追蹤 ID traceId(頂層駝峰) = OTel trace ID,見 §6
跨度 ID spanId(頂層駝峰)
事件碼 —— 本期未實作,見 §3.2
{"@timestamp":"2026-09-20T10:20:30.123Z","log.level":"INFO",
 "service.name":"authorization-service","traceId":"894ff808…","spanId":"e5f6",
 "message":"AUTH_APPROVED cardRef=****1234 amount=30000 currency=TWD"}

3.2 事件碼(event)本期不實作

原 v1.0 §3 要求之 event 事件碼,本期不實作——專案尚未定義事件碼表,
且事件碼須與 SDD-006 §3.4 之錯誤碼一併規劃(CSP-003 §2:Coder 不得自創碼)。

在事件碼表核定前,以結構化參數記錄事件事實:

log.info("AUTH_APPROVED cardRef={} amount={} currency={}", mask(card), amt, cur);

由 message 與附帶欄位承載語意,不另設 event 欄位——
以免產生恆為空值之欄位(空欄位=假訊息,與「不可假綠」同旨)。

  • 禁止 string concatenation 拼日誌:一律使用結構化參數。
  • 日誌層級:ERROR=需告警、WARN=需關注、INFO=關鍵業務事件、DEBUG=僅開發。

4. 個資遮蔽(對應 SAD-005)

日誌與追蹤中出現之敏感欄位必須經 common-security 遮蔽工具處理:

欄位 遮蔽規則
卡號 僅留末 4 碼(****1234)
身分證字號 僅留首 1 + 末 1(A*****8)
手機 中段遮罩(0912***567)
CVV / 密碼 禁止出現於任何日誌/追蹤
flowchart LR
    A[業務邏輯] --> B[log.info 結構化]
    B --> C{含敏感欄位?}
    C -- 是 --> D[common-security.mask]
    C -- 否 --> E[輸出 JSON]
    D --> E
    style D fill:#FFF6E0,stroke:#C8A45C

5. 指標規範

每服務必須暴露(Prometheus 格式):

指標 型別 標籤 用途
http_server_requests_seconds Histogram service,route,status 延遲分佈(算 P95/P99)
auth_decision_total Counter result(approve/decline/standin) 授權決策量
posting_invariant_violation_total Counter type 餘額不變式違反(必須 ≈0)
downstream_calls_total Counter target,result 下游健康
jvm_memory_used_bytes Gauge — 資源

SLO 範例:授權 http_server_requests_seconds{route=/authorizations} P95 ≤ 0.5s、可用性 ≥ 99.99%。


6. 分散式追蹤

  • 所有對外 HTTP/Kafka 呼叫自動注入 traceparent(W3C)。
  • 跨系統之唯一識別為 OTel traceId(W3C 標準,32 位十六進位),
    由 OTel 於入口產生並貫穿全鏈(含 Kafka header)。
  • ⚠️ 不再另設第二套 correlation ID。原 v1.0 §3/§6 規定之 correlationId
    與 OTel traceId 實為兩套 ID——實測同一請求:
    API 回應 10b8a469…(common-spring correlation ID)
    ≠ 日誌 894ff808…(OTel trace ID),兩者無法相互對照,
    客戶持 API 回應之 ID 查不到對應日誌(SCS-020 §9.5 #3)。
    經裁定(BEAM PMO 2026-09-20)統一以 OTel traceId 為準:
    API 回應之 traceId 與日誌之 traceId 必須為同一值。
  • 取樣率預設 1/100;錯誤路徑(4xx/5xx)強制 100% 取樣。
  • 禁止在追蹤 span 中記錄敏感 payload;僅記錄長度與欄位名。

6.1 實作點:單一讀取端 TraceContext(2026-09-20 落地)

回應封套之 traceId 一律經 common-spring 之 TraceContext.currentTraceId()
取得
,不得另行產生。取值順序:

  1. OTel 當前 span 之 traceId(權威)
  2. MDC 之 trace_id(OTel 不在場時之降級路徑)
  3. 產生 32 碼臨時值(確保永不為空,SAD-001 AP-09)

⚠️ 實測發現之根因(比「有兩套 ID」更嚴重):CorrelationIdFilter
從未被註冊為 bean——AutoConfiguration.imports 僅登錄 Security×2 + ServiceClient,
該類別亦無 @Component。故 MDC.get("trace_id") 恆為 null,
currentTraceId() 每次呼叫都現產一個新的亂數,回應帶著一個「看似合法」
卻與任何日誌都對不上之追蹤碼——「靜默不做」型缺陷。
改以 OTel 為權威後,只要 OTel 在場即不再受此缺陷影響。

  • OTel 缺席時:降級至 MDC/臨時值,並只記一次 WARN
    (使「刻意不接 OTel 之模組」與「忘了接」在日誌上可區分)。
  • ⚠️ 待裁定:CorrelationIdFilter 註冊或退役(其職責已被 OTel 取代;
    註冊涉及過濾器順序,順序錯會讓 MDC 與 OTel 出現兩個值,故未擅自註冊)。

7. 健康檢查與告警

  • GET /actuator/health/liveness:程式存活(不依賴外部)。
  • GET /actuator/health/readiness:依賴(DB、下游、Kafka)就緒。
  • 告警分級:P1(可用性跌幅/不變式違反)即時通訊軟體+電話;P2(延遲超標)工單。

8. 禁止事項

編號 禁止 原因
O1 明文印卡號/密碼/CVV 資安(SAD-005)
O2 生產開啟 DEBUG 全域 效能與個資風險
O3 以 System.out.println 代替日誌框架 無結構、無遮蔽

9. 修訂記錄

版本 日期 修訂者 說明
1.0 2026-09-17 SA / SRE 初版
1.1 2026-09-20 WorkBuddy AI 依實測結果修正規格(BEAM PMO 2026-09-20 裁定):①**§3 欄位對照改為實測值**——原規定 ts/level/service/msg 與 Spring Boot 4.1 內建 ECS 之實際輸出 @timestamp/log.level(巢狀)/service.name(巢狀)/message 不符;②**§3.2 event 事件碼本期不實作**——專案尚未定義事件碼表,且須與 SDD-006 §3.4 錯誤碼一併規劃(CSP-003 §2 不得自創碼),改以結構化參數承載語意,不設恆為空值之欄位;③**§6 廢除第二套 correlationId,統一以 OTel traceId 為跨系統唯一識別——原設計使 API 回應與日誌之 ID 互不相通(實測 10b8a469… ≠ 894ff808…),查測時無法對照。本次為規格遷就實測**(ECS 輸出為框架既成事實,自訂 encoder 成本過高),非放寬要求
1.2 2026-09-20 WorkBuddy AI C-29 程式面落地:新增 §6.1 單一讀取端 TraceContext(取值順序:OTel span → MDC → 臨時值;OTel 缺席時降級並只記一次 WARN)。⚠️ 並記錄實測發現之根因:CorrelationIdFilter 從未被註冊為 bean(AutoConfiguration.imports 無 web 自動配置、類別亦無 @Component)→ MDC.get("trace_id") 恆為 null,currentTraceId() 每次現產一個亂數,回應看似帶合法追蹤碼實則與日誌對不上——「靜默不做」型缺陷。⚠️ 待裁定:該過濾器註冊或退役(職責已被 OTel 取代;順序錯會造成兩個值,故未擅動)

| O4 | 指標名稱含具體實例 IP/Pod 名 | 聚合失效 |
| O5 | 每秒高頻 log 同一事件無節流 | 磁碟/成本爆量 |

(25-25/67)