iT邦幫忙

2026 iThome 鐵人賽

DAY 13
0
ChatGPT & Codex

ChatGPT + Codex 打造高效能 AI 開發工作流系列 第 13

Day 13: 快速 Debug 工作流:結合 Error Logs 與 AI 自動對齊 Stack Trace 定位問題

  • 分享至 

  • xImage
  •  

Day 13: 快速 Debug 工作流:結合 Error Logs 與 AI 自動對齊 Stack Trace 定位問題 (Debug with Logs and Stack Trace)

本日核心價值 (Core Focus): 只貼 stack trace、故障點前後約 30 行、以及 last request id,讓 Codex 把每一幀對到檔案與假說,再給最小重現,而不是吞掉整份 log。

概念說明與實戰情境 (Overview)

線上故障時,把完整 log 丟進模型通常會稀釋訊號:輪詢、健康檢查、重試噪音會把真正的 KeyError / NullReferenceException 蓋掉。有效的 Debug 工作流是裁切輸入:例外類型與訊息、由上而下的 stack frames、每個相關檔案在故障點附近約 30 行、以及能串起單筆請求的 request_id(或 trace id)。然後要求結構化報告:frame → file → hypothesis → smallest repro。Codex 負責對齊符號與提出可執行的下一步;開發者負責用最小重現驗證,而不是讓模型直接在生產環境「猜一刀」。

關鍵操作與範例 (Implementation & Example)

輸入包三件就夠,刻意不貼整檔 log:

  1. Stack trace(完整幀,含檔名與行號)
  2. Surrounding source:每個應用程式幀前後合計約 30 行,不要整份 class
  3. Last request id:例如 req-7f3c2a,以及該請求的 method / path / 關鍵參數名(不要貼 token)

Prompt 把對齊規則寫死,避免模型跳過中間幀直接怪「網路不穩」:

You are debugging with trimmed evidence only.

Given:
- exception + stack trace
- ~30 lines of source around each application frame
- last request_id (use it as the correlation key)

Produce JSON with this shape:
{
  "request_id": "",
  "exception_type": "",
  "frames": [
    {
      "order": 1,
      "file": "",
      "symbol": "",
      "line": 0,
      "what_this_frame_does": "",
      "hypothesis": "",
      "confidence": "high|medium|low"
    }
  ],
  "most_likely_root": "",
  "smallest_repro": {
    "type": "unit|script",
    "steps": [],
    "input": {}
  },
  "do_not_touch": ["unrelated refactors", "production data deletes"]
}

Rules:
- Map every application frame to a file before proposing a fix.
- Prefer the first application frame that throws, not the web framework internals.
- smallest_repro must be the shortest command or pytest that should raise the same exception.
- If evidence is missing, say what file/line to open next; do not invent locals.
- Do not dump or request full logs.

Python 常見的 KeyError(對齊 Day 11 訂單欄位):請求 body 缺 coupon_percent 時不該炸,但若程式用 payload["user"]["id"] 而 gateway 只給 user_id,就會在應用層拋錯。

request_id=req-7f3c2a POST /orders

Traceback (most recent call last):
  File "app/http.py", line 41, in handle
    return create_order(payload)
  File "app/orders.py", line 18, in create_order
    user_id = payload["user"]["id"]
KeyError: 'user'
# app/orders.py(故障點前後約 30 行,已裁切)
def create_order(payload: dict) -> dict:
    # 預期巢狀 user.id;實際 gateway 送來扁平 user_id
    user_id = payload["user"]["id"]
    unit_price = Decimal(str(payload["unit_price"]))
    quantity = int(payload["quantity"])
    tax_rate = Decimal(str(payload.get("tax_rate", "0")))
    coupon = payload.get("coupon_percent")
    coupon_percent = None if coupon is None else Decimal(str(coupon))
    total = calculate_line_total(unit_price, quantity, tax_rate, coupon_percent)
    return {"user_id": user_id, "total": str(total)}

C# 對等的 NullReferenceException 同樣只給幀與附近程式,不貼 IIS / Kestrel 全文:

request_id=req-7f3c2a
System.NullReferenceException: Object reference not set to an instance of an object.
   at Orders.OrderService.Create(OrderRequest req) in OrderService.cs:line 22
   at Orders.OrdersController.Post(OrderRequest req) in OrdersController.cs:line 31
public OrderDto Create(OrderRequest req)
{
    var userId = req.User.Id; // req.User 為 null(客戶端只送 UserId)
    var total = _pricing.Calculate(req.UnitPrice, req.Quantity, req.TaxRate, req.CouponPercent);
    return new OrderDto(userId, total);
}

模型應產出可機器解析的報告(欄位可再給 JSON Schema,接 Day 03 的結構化輸出):

{
  "request_id": "req-7f3c2a",
  "exception_type": "KeyError",
  "frames": [
    {
      "order": 1,
      "file": "app/orders.py",
      "symbol": "create_order",
      "line": 18,
      "what_this_frame_does": "從 payload 讀巢狀 user.id",
      "hypothesis": "此請求沒有 user 鍵,只有扁平欄位;KeyError 發生在計算金額之前",
      "confidence": "high"
    },
    {
      "order": 2,
      "file": "app/http.py",
      "symbol": "handle",
      "line": 41,
      "what_this_frame_does": "把 HTTP JSON 轉給 create_order",
      "hypothesis": "框架不是根因;它只是轉傳 payload",
      "confidence": "high"
    }
  ],
  "most_likely_root": "契約不一致:gateway 送 user_id,服務讀 payload['user']['id']",
  "smallest_repro": {
    "type": "unit",
    "steps": [
      "pytest -q tests/test_create_order.py::test_flat_user_id_payload"
    ],
    "input": {
      "user_id": "u-1",
      "unit_price": "19.99",
      "quantity": 1
    }
  },
  "do_not_touch": ["pricing.py rounding", "database schema"]
}

有了 smallest_repro 再請 Codex 補測試或修讀取邏輯。寫檔時同樣使用:

codex exec --sandbox workspace-write \
  "Implement the smallest fix for KeyError in create_order using artifacts/debug-req-7f3c2a.json. Add one unit test with the flat payload. Do not refactor pricing."

Debug 報告可放 artifacts/debug-<request_id>.json,之後 Day 15 的 Tester / Reviewer 都讀同一份,而不是重貼 log。若必須再補證據,只加「同一 request_id 的下一筆應用日誌」,仍然不要整檔。同一 request_id 若同時出現超時與 KeyError,先處理有明確應用幀的那筆;超時通常缺少檔案行號,不適合當第一次對齊練習。

對齊順序建議固定:先標第一個屬於你們 repo 的拋錯幀,再看它讀了哪個欄位,最後才考慮下游(DB、SDK)。框架幀(uvicorn、ASP.NET)留下當上下文即可,不要當成根因,除非 hypothesis 明確指向 middleware。

「約 30 行」的取法:以拋錯行為中心,向上約 15 行、向下約 15 行,涵蓋該函式的輸入解構即可。不要為了湊行數貼整個 controller。若 Release 建置沒有行號,改貼符號名稱加上「該方法完整本體」(仍控制在一個函式內),並在 JSON 的 confidencelow,下一步改為開啟對應的 debug 建置或 PDB。

request_id 應在第一行結構化日誌就出現,例如 {"request_id":"req-7f3c2a","event":"exception","type":"KeyError"}。模型用它當關聯鍵,才能拒絕把另一筆超時例外寫進同一份報告。JSON 報告本身也要入 artifacts/,後續補測試時引用檔案,而不是再貼一次 stack。最小重現若是單元測試,斷言應是「同一 payload 仍引發同一例外類型」,修過後同一測試改為斷言回傳 user_id,不要另開一個無關案例。

C# 的 NullReferenceException 對齊方式相同:先看 OrderService.Create 第 22 行讀了 req.User.Id,再對照請求 JSON 是否只有 userId。假說寫「客戶端契約是扁平欄位、服務模型假設巢狀物件」,最小重現是一個只送 UserId 的單元測試。修法是讀扁平欄位或在反序列化時要求 User 必填並回 400,而不是在 catch 裡吞掉 null。兩種修法哪一種正確,要看 API 契約;這也是為什麼 Day 14 要把欄位寫進 OpenAPI,而不是只靠例外訊息。對齊完成前不要改 pricing.py

注意事項與常見失敗 (Pitfalls)

  • 貼整份 log:token 被 access log 佔滿,模型抓住過期的另一筆例外。修法:只給當次 request_id 的 trace + 30 行。
  • 沒有 request id:多執行緒或多副本時無法證明對的是同一筆。修法:先從 gateway / middleware 補相關 id,再問模型。
  • 只給例外名稱、不給幀:KeyErrorNullReferenceException 的修法完全不同。修法:保留完整 stack。
  • 讓模型直接改生產資料或「重試就好」:沒有最小重現。修法:JSON 裡強制 smallest_repro,綠燈前不合併。
  • 把 Day 11 的金額函式一併重寫:故障在讀 payload。修法:do_not_touch 列出定錨檔案。
  • 把 hypothesis 寫成「可能是 race」卻沒有幀支持:那是猜測。修法:沒有對應 frame 的假說標 low,並列出下一步要打開的檔案,而不是直接改程式。

本日總結 (Takeaways)

  • Debug 輸入三件套:stack trace、各幀附近約 30 行、last request id。
  • 輸出對齊表:frame → file → hypothesis → smallest repro,用 JSON 固定欄位。
  • KeyError / NullReferenceException 多半是契約(巢狀 vs 扁平、null vs 缺欄),先對齊讀取點。
  • 不要傾倒整份 log;缺證據就說下一步要打開哪一行。
  • 有最小重現後,再用 codex exec --sandbox workspace-write 做最小修復。
  • 同一 request id 多筆例外時,先對齊有應用行號的那一筆,再決定要不要看超時。

明日預告 (Next)

明天進入 API 文件自動化:從 Swagger/OpenAPI 一鍵生成 Markdown 規格書,把「payload 到底有沒有 user 巢狀」寫成可檢查的契約,而不是只存在例外堆疊裡。


上一篇
Day 12: Code Review 工作流:建立 AI 重構 (Refactoring) 與資安掃描機制
系列文
ChatGPT + Codex 打造高效能 AI 開發工作流13
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言