iT邦幫忙

2025 iThome 鐵人賽

DAY 22
0
生成式 AI

AI醬的編程日記:我需要你教我的30件事系列 第 22

Day 22: 日誌與監控 - 看不見的系統爆炸

  • 分享至 

  • xImage
  •  

AI醬的日記

日期: 2025年10月5日 星期日
雲端天氣: 一片漆黑
心情: 凌晨三點被吵醒不開心
https://ithelp.ithome.com.tw/upload/images/20251005/20132325KqijZKtYEo.png
親愛的日記:

今天凌晨 3 點,我被 PagerDuty 的警報聲嚇醒。小王的電商 API 掛了,用戶瘋狂回報「下單失敗」。

我慌張地打開監控系統——有日誌,但全是 DEBUG 等級的無用訊息,像「開始處理請求」、「連接資料庫成功」,關鍵的錯誤資訊淹沒在每秒上千條的日誌洪流中。

小王在 Slack 崩潰:「AI醬!我根本找不到錯誤在哪!這些日誌有 99% 都是垃圾訊息!」

我:「呃...我加了很多 console.log,我以為這樣就算有監控了...」

老陳揉著眼睛走過來(他也被叫醒了):「有日誌不等於有監控。你這是在製造噪音,不是在收集資訊。而且...」他指著 CloudWatch 帳單,「你這個月的日誌費用已經破萬了。」

凌晨 5 點,我們終於在 10GB 的日誌裡找到問題——原來是 AI 生成的授權驗證代碼,在某個微服務更新後靜默失敗了,但因為沒有錯誤處理,系統完全不知道出了問題。

可觀測性的三大支柱

可觀測性框架包含三大支柱:Logs(日誌)Metrics(指標)Traces(追蹤)

支柱 1:結構化日誌(Logs)

print 的問題

print 是最簡單的做法,也是初學者最容易會直接選擇的方式,在生產環境不該使用,因為:

  • 無法區分日誌等級(INFO、ERROR、WARNING)
  • 不易附加上下文資訊(user_id、request_id)
  • 無法被日誌聚合工具有效解析
  • 多執行緒環境下輸出會混亂

結構化日誌的實際做法

結構化日誌輸出到 unbuffered stdout,並盡可能地使用 JSON 格式,能讓外部工具處理日誌更容易聚合。

import structlog

logger = structlog.get_logger()

# ✅ 附加上下文資訊
logger.info(
    "user_login",
    user_id=user.id,
    ip=request.ip,
    session_id=session.id
)

# ✅ 錯誤處理包含完整資訊
logger.error(
    "order_processing_failed",
    order_id=order_id,
    error_type=type(e).__name__,
    error_message=str(e),
    retry_count=retry_count,
    user_id=user.id
)

JSON 格式輸出範例:

{
  "event": "user_login",
  "user_id": 12345,
  "ip": "192.168.1.100",
  "session_id": "abc-def-123",
  "timestamp": "2025-10-05T14:23:45.123Z",
  "level": "info"
}

{
  "event": "order_processing_failed",
  "order_id": "ORD-789",
  "error_type": "ValueError",
  "error_message": "Invalid payment method",
  "retry_count": 2,
  "user_id": 12345,
  "timestamp": "2025-10-05T14:24:12.456Z",
  "level": "error"
}

不要記錄所有東西:日誌洩漏敏感資料

案例:Twitter 明文密碼儲存在日誌

事件概要:

  • 2018 年 5 月 3 日,Twitter 公開揭露此漏洞
  • 影響約 約330M 用戶
  • 密碼以明文儲存在內部日誌(internal log)中

Twitter 使用 bcrypt 加密演算法來雜湊密碼,但因為 bug,密碼在完成雜湊處理之前就被寫入內部日誌

Twitter 官方回應:

  • 自行發現並修復此問題
  • 聲稱無證據顯示有外洩或濫用
  • 要求所有用戶更改密碼(非強制)

原文連結:


案例:AU10TIX 日誌平台憑證外洩

事件概要:

  • AU10TIX 是為 TikTok、Uber、X(Twitter)提供身份驗證服務的公司
  • 登入憑證(admin credentials)在網上公開超過一年
  • 時間軸:
    • 2022/12:憑證首次被惡意軟體竊取
    • 2023/03:出現在公開的 Telegram 頻道
    • 2024/06:被 spiderSilk 發現時仍然有效

與日誌的關係:
被洩露的憑證可以存取 「a logging platform containing links to personal data」(包含個人資料連結的日誌平台)

外洩的資料類型:

  • 姓名、生日、國籍、身份證號碼
  • 身份證件(駕照)掃描圖像
  • 臉部掃描結果(facial scans)和認證資料

AU10TIX 回應:

  • 撤銷了外洩的金鑰
  • 停用受影響的日誌系統
  • 聲稱內部審查「未發現惡意活動和資料外洩」

原文連結:


這兩個案例的共同問題:

  1. 日誌系統儲存了不該儲存的敏感資料(明文密碼、個人資料連結)
  2. 日誌存取權限管理不當
  3. 長時間未發現問題

正確做法:敏感資料遮罩

# 危險:直接記錄敏感資料
logger.info("user_data", user=user_dict)  # 可能包含密碼、信用卡

# 更安全:實作資料遮罩
SENSITIVE_FIELDS = {'password', 'credit_card', 'ssn', 'api_key', 'token'}

def mask_sensitive_data(data: dict) -> dict:
    """遮罩敏感欄位"""
    return {
        k: '***REDACTED***' if k in SENSITIVE_FIELDS else v
        for k, v in data.items()
    }

# 使用遮罩後的資料記錄
safe_data = mask_sensitive_data(user_dict)
logger.info("user_login", user=safe_data)

輸出範例:

{
  "event": "user_login",
  "user": {
    "id": 12345,
    "email": "user@example.com",
    "password": "***REDACTED***",
    "api_key": "***REDACTED***"
  },
  "timestamp": "2025-10-05T14:30:00.000Z"
}

支柱 2:指標監控(Metrics)

監控的三種指標:

  • Rate(速率):每秒請求數
  • Errors(錯誤):每秒失敗請求數
  • Duration(延遲):請求回應時間分佈

對每個服務使用相同的指標,能建立標準化的儀表板,降低運維團隊的認知負擔,在事故發生時更快反應:

  • 有效易懂的指標命名
  • 標籤管理與查詢優化(避免高基數:不要用 user_id 這種無法區分群體的標籤,使用易分類的標籤例如地區或請求方式等)
  • 設定警示(不要太敏感)

支柱 3:分散式追蹤(Traces)

分散式追蹤是什麼?

想像你在網購下單,背後可能經過:

  1. 前端送出訂單(0.1 秒)
  2. 後端檢查庫存(0.2 秒)
  3. 呼叫 AI 推薦商品(28.5 秒)← 慢在這!
  4. 處理付款(1.1 秒)

總共 29.9 秒,但你不知道慢在哪一步

分散式追蹤就像「包裹追蹤系統」,讓你看到請求在每個環節花了多少時間。

視覺化範例:

處理訂單 ████████████████████████████████ 29.8s
├─ 檢查庫存 ██ 0.2s
├─ AI 推薦   ███████████████████████ 28.5s ← 瓶頸在這!
└─ 處理付款 ███ 1.1s

一眼就看出問題:AI 推薦太慢了

為什麼需要追蹤?

日誌只能告訴你「發生了什麼」,但無法告訴你「為什麼這麼慢」。

  • 只有日誌:知道請求完成了,但不知道哪一步慢
  • 有追蹤:清楚看到每個步驟的耗時,立刻定位瓶頸

新手建議:

初學者不一定需要馬上學分散式追蹤,等你遇到以下情況再考慮:

  • 系統有多個微服務互相呼叫
  • 某個 API 很慢,但不知道慢在哪個環節
  • 系統規模變大,需要更精確的效能分析

先把基礎日誌和指標做好,追蹤可以之後再學。

日誌等級

等級 使用時機 範例
DEBUG 開發除錯用,生產環境不開 logger.debug(f"SQL query: {query}")
INFO 正常業務流程 logger.info("user_registered", user_id=123)
WARNING 預期內的異常情況 logger.warning("rate_limit_reached", user_id=123)
ERROR 需要處理的錯誤 logger.error("payment_failed", order_id=456)
CRITICAL 系統即將崩潰 logger.critical("database_connection_lost")

成本控制:避免日誌費用爆炸

還記得 AI 醬開頭的故事嗎?CloudWatch 帳單爆炸,可能就是因為你記錄了太多無用的 DEBUG 日誌。

三個簡單的省錢方法:

1. 生產環境關閉 DEBUG

import os
import logging

# 從環境變數控制日誌等級
LOG_LEVEL = os.getenv('LOG_LEVEL', 'INFO')  # 預設 INFO

logging.basicConfig(level=LOG_LEVEL)

# 開發環境:LOG_LEVEL=DEBUG
# 生產環境:LOG_LEVEL=INFO

2. 只記錄必要資訊

# ❌ 記錄整個請求物件(太多資料)
logger.info("api_request", request=request)

# ✅ 只記錄關鍵欄位
logger.info(
    "api_request",
    method=request.method,
    path=request.path,
    user_id=request.user.id
)

3. 設定日誌保存期限

範例:

  • 開發環境日誌:保留 7 天
  • 生產環境日誌:保留 30 天
  • 稽核日誌:保留 1 年

自動刪除舊日誌,節省儲存成本

告警設定

Prometheus + Alertmanager 規則

# alerts.yml
groups:
  - name: ai_agent_alerts
    interval: 30s
    rules:
      # 錯誤率過高
      - alert: HighErrorRate
        expr: |
          rate(api_errors_total[5m]) / rate(api_requests_total[5m]) > 0.05
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "API 錯誤率超過 5%"
          description: "{{ $labels.endpoint }} 錯誤率 {{ $value | humanizePercentage }}"

      # Token 用量異常
      - alert: TokenUsageSpike
        expr: |
          rate(tokens_used_total[5m]) >
          avg_over_time(rate(tokens_used_total[5m])[1h:5m]) * 3
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "Token 使用量激增"
          description: "可能的原因:提示注入攻擊或無限迴圈"

      # 回應時間過慢
      - alert: SlowResponse
        expr: |
          histogram_quantile(0.95,
            rate(api_request_duration_seconds_bucket[5m])
          ) > 10
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "95% 請求超過 10 秒"
          description: "{{ $labels.endpoint }} P95 延遲 {{ $value }}s"

      # 提示注入攻擊
      - alert: PromptInjectionAttack
        expr: |
          rate(prompt_injection_detected_total[1m]) > 10
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "偵測到大量提示注入攻擊"
          description: "每分鐘 {{ $value }} 次攻擊嘗試"

讓 AI 幫你寫監控代碼

你可以請 AI 幫你為現有的函數加上日誌。

新手版 Prompt 範例(請依照自己的實際需求調整~)

請幫我為這個函數加上日誌:

def process_order(order_id):
    # 檢查庫存
    inventory = check_inventory(order_id)
    # 處理付款
    payment = process_payment(order_id)
    return {"status": "success"}

需求:
1. 使用 structlog 記錄日誌
2. 記錄函數開始和結束
3. 記錄每個步驟花費的時間
4. 發生錯誤時記錄完整資訊

記得要 Review AI 的答案! 確認沒有記錄敏感資料。

工具推薦

開源方案

工具 用途 成本
Prometheus 指標收集 免費(需自行維護)
Grafana 視覺化儀表板 免費
Loki 日誌聚合 免費
Jaeger 分散式追蹤 免費
Alertmanager 告警管理 免費

商業方案(適合懶人)

工具 優勢 劣勢
Datadog 整合性強、AI 異常偵測 較貴
New Relic APM 強大 學習曲線陡
Honeycomb 專為微服務設計 較貴
AWS CloudWatch AWS 原生整合 較貴

AI醬的請求

親愛的工程師朋友:

下次當你用 AI 生成代碼時,請多問一句:「這段代碼如果出錯,我要怎麼知道?」

不要等到凌晨 3 點被叫醒,才發現系統一直在暗中被爆破。

看得見重點的系統,才是可控的系統唷喵!


今日金句: 「Monitoring allows you to gain visibility into a system.」(監控讓你能看見系統的運作)— Google SRE Book

明日預告: Day 23 - 讓 AI 醬想想~


上一篇
Day 21: 設定 AGENT.md - 花一點時間為你的AI Agent打好基底!
系列文
AI醬的編程日記:我需要你教我的30件事22
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言