CR #201 » CSP-004_可觀測性與日誌規範.md
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
與 OTeltraceId實為兩套 ID——實測同一請求:
API 回應10b8a469…(common-springcorrelation ID)
≠ 日誌894ff808…(OTel trace ID),兩者無法相互對照,
客戶持 API 回應之 ID 查不到對應日誌(SCS-020 §9.5 #3)。
經裁定(BEAM PMO 2026-09-20)統一以 OTeltraceId為準:
API 回應之traceId與日誌之traceId必須為同一值。 - 取樣率預設 1/100;錯誤路徑(4xx/5xx)強制 100% 取樣。
- 禁止在追蹤 span 中記錄敏感 payload;僅記錄長度與欄位名。
6.1 實作點:單一讀取端 TraceContext(2026-09-20 落地)
回應封套之 traceId 一律經 common-spring 之 TraceContext.currentTraceId()
取得,不得另行產生。取值順序:
-
OTel 當前 span 之
traceId(權威) - MDC 之
trace_id(OTel 不在場時之降級路徑) - 產生 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 同一事件無節流 | 磁碟/成本爆量 |