wardrobe-os-ml / src /sentry_init.py
fengchen31's picture
Sync from GitHub refs/heads/main @ 3da1b47
b288a25 verified
Raw
History Blame Contribute Delete
6.44 kB
"""Sentry SDK 初始化(FastAPI + asyncio integration)。
【為什麼獨立模組?】
原本想塞進 main.py,但 main.py 已經很長,而且 init 邏輯需要對應
test_sentry_init.py 三個 case(DSN 不設、DSN 設但 environment 不設、
兩者都設),把它放成獨立函式比較好測。caller 只需 `init_sentry()`。
【為什麼一定要 SENTRY_ENVIRONMENT?】
Sentry SDK 預設 environment 從 SENTRY_ENVIRONMENT 取,但若 env 不設、
又同時帶 production DSN,dev/staging 機台的事件會直接打進 production
project,與真實 production 事件混在一起。production 的告警 noise
會被 dev 噪音淹沒。fail-loud:明確要求 operator 設 production /
staging / dev 任一字串才放行。
【為什麼 send_default_pii=False?】
ml-service 上游收的是「使用者照片 / 使用者商品 brand 字串」這類
user-supplied 資料;request body 中可能含使用者圖片 bytes、prompt
injection 嘗試字串、品牌字串等,default PII 開啟會把整段 request
payload 灌進 Sentry event:
- 流量大時可能撐爆 Sentry quota(圖片 base64 巨大)
- 也讓 Sentry 變成另一個 user-data sink,要納入 GDPR 範圍
設成 False 仍能拿到 stack trace + transaction name,足以做 error
triage,沒有 PII 副作用。
"""
from __future__ import annotations
import logging
import os
# 注意:sentry_sdk 為 lazy import — 只在 init_sentry() 內 import,不在模組頂層。
# 這樣 sentry_sdk 安裝失敗(或在 ML 環境暫時 unavailable)不會 block ml-service
# 啟動;DSN 不設時也不需要 import 整包。
_logger = logging.getLogger(__name__)
def init_sentry() -> bool:
"""讀取 SENTRY_DSN 等 env,呼叫 sentry_sdk.init。
Returns:
True if Sentry was initialised, False if DSN was not set.
Raises:
RuntimeError: when SENTRY_DSN is set but SENTRY_ENVIRONMENT is unset
(sentinel against dev events bleeding into prod project).
"""
dsn = os.environ.get("SENTRY_DSN")
if not dsn:
_logger.info("ml-service: SENTRY_DSN not set — Sentry disabled")
return False
environment = os.environ.get("SENTRY_ENVIRONMENT")
if not environment:
# fail-loud 而非 silent default。沒設 SENTRY_ENVIRONMENT 等同
# operator 沒對齊 dev/staging/prod 邊界,告警可能跨環境污染。
raise RuntimeError(
"SENTRY_DSN set but SENTRY_ENVIRONMENT unset — dev/staging events "
"would land in the production Sentry project. Set "
"SENTRY_ENVIRONMENT=production|staging|dev explicitly."
)
# traces_sample_rate 預設 0.1(10% 採樣)。HF Spaces L40S 大流量時
# 100% trace 會把 Sentry quota 燒光;0.1 已足以做 p95 latency 告警
# (ml-service.cold-start-slow / ml-service.5xx-rate)。
traces_raw = os.environ.get("SENTRY_TRACES_SAMPLE_RATE", "0.1")
try:
traces_sample_rate = float(traces_raw)
except ValueError as exc:
raise RuntimeError(
f"SENTRY_TRACES_SAMPLE_RATE invalid: {traces_raw!r} (must be float 0–1)"
) from exc
if not 0.0 <= traces_sample_rate <= 1.0:
raise RuntimeError(
f"SENTRY_TRACES_SAMPLE_RATE out of range: {traces_sample_rate} (must be 0–1)"
)
import sentry_sdk
from sentry_sdk.integrations.asyncio import AsyncioIntegration
from sentry_sdk.integrations.fastapi import FastApiIntegration
from sentry_sdk.integrations.logging import LoggingIntegration
sentry_sdk.init(
dsn=dsn,
environment=environment,
traces_sample_rate=traces_sample_rate,
integrations=[
FastApiIntegration(),
AsyncioIntegration(),
# F1 closer (c-series post-review wave-2):sentry-sdk Python 預設
# LoggingIntegration 的 event_level=ERROR 會把任何 logger.error()
# auto-capture 為 Sentry event;若 caller 傳 exc_info=exc,
# exception.values[].value 會夾完整 raw exc.args(psycopg DSN /
# huggingface_hub hf_ token),bypass 我們在 main.py /
# lib/background.py 的 scrub_exc_for_audit 工。
#
# 顯式 instantiate with event_level=None 關掉 auto-event;
# level=INFO 保留 breadcrumb 路徑(INFO+ 轉 breadcrumb),non-
# security trace 仍能在 explicit capture event 上看 trail。
# security-tagged event 的 site (main.py:_on_preload_done /
# lib/background.py:_log_unhandled_exception) 已用 isolation_scope
# + clear_breadcrumbs 隔離,不會吃 ambient breadcrumb。
# (wave-3 修正:必須是 isolation_scope 不是 new_scope,因為
# sentry-sdk Python 2.x breadcrumb 寫在 isolation scope;詳細
# 解釋見 main.py:_on_preload_done / background.py 的 inline 註解。)
#
# memory: feedback_python_sentry_capture_needs_scrub。
LoggingIntegration(level=logging.INFO, event_level=None),
],
# send_default_pii=False — 顯式關閉 user image bytes / brand string
# / prompt injection payload 進 Sentry event。詳見 module docstring。
send_default_pii=False,
# FastApi integration 預設不擷取 request body;保險再 set
# max_breadcrumbs 限制以免 long-running ML inference 累積過多 trail。
max_breadcrumbs=50,
# include_local_variables=False — 關掉 stack frame 局部變數捕獲。
# batch-3 post-review (security-reviewer P2):sentry-sdk 2.x 預設
# `with_locals=True`,當 `/verify/local` 之類 endpoint 在 prompt
# 組裝後才 raise,stack frame 會把 `brand_safe`、`brand_context`、
# 整段 prompt(含使用者 80-char sanitized brand)灌進 Sentry event。
# send_default_pii=False 只擋 request body / headers / cookies,
# 不擋 frame variables。關掉之後 Sentry 仍能拿到 stack trace +
# exception type/message,足以做 triage,但不再夾帶 user-controlled
# 字串。
include_local_variables=False,
)
_logger.warning(
"ml-service: Sentry initialised (environment=%s, traces=%s)",
environment,
traces_sample_rate,
)
return True