昨日我們成功將 AI 個人財務追蹤器部署上雲(Vercel + Neon PostgreSQL),擁有了正式公開的網址!然而,應用程式順利上線並不代表開發過程的終結,而是運維(Observability)的開始。
當真實使用者開始進入系統,各種在本地開發或 CI 測試中沒被觸發的「幽靈 Bug」就會陸續浮現:例如使用者輸入了特殊字元導致 LLM API 噴出 500 錯誤、或是特定瀏覽器核心產生的 Hydration Mismatch。
今天我們的目標是建立產品發布後的防禦天眼:
在整合第三方 SDK 時,AI 經常給出「舊版套件寫法」或「直接在元件裡用 console.error」這種粗暴的做法。今天我們需要訓練 AI 給出符合 Next.js App Router 最新架構的非同步錯誤攔截機制與結構化 Logger 封裝。
我們希望 AI 幫我們在 Next.js App Router 專案中配置 Sentry 錯誤追蹤與 Logtail 結構化 Log Logger。
你是一位資深 SRE (Site Reliability Engineer) 與全棧架構師。
請幫我在 Next.js (App Router) 專案中整合 Sentry 與 Logtail (Better Stack) 監控系統。
需求如下:
1. 建立一個可重用的結構化 Logger 工具(`lib/logger.ts`),封裝 Winston 或 Pino / Logtail,支援 debug, info, warn, error 層級,並能紀錄運作時間 (duration) 與 userId。
2. 撰寫 Next.js `global-error.tsx` 與 `error.tsx` 組件,將前端 React 渲染崩潰自動回報至 Sentry。
3. 封裝一個 Server-side API 錯誤包裝函式(`withMonitoring`),自動記錄 API 請求狀態碼、耗時、捕獲 Exception 送往 Sentry,同時將 log 結構化推送到 Logtail。
AI 給出了一份 Logger 封裝與 API Wrapper 範例:
// AI 生成的 API 監控 Wrapper (lib/withMonitoring.ts)
import * as Sentry from "@sentry/nextjs";
import { Logtail } from "@logtail/node";
const logtail = new Logtail(process.env.LOGTAIL_SOURCE_TOKEN || "");
export function withMonitoring(handler: Function) {
return async (req: Request) => {
const startTime = Date.now();
try {
const response = await handler(req);
logtail.info("API Request Success", {
url: req.url,
duration: Date.now() - startTime,
});
return response;
} catch (error) {
Sentry.captureException(error);
logtail.error("API Request Error", {
url: req.url,
error: String(error),
});
return new Response(JSON.stringify({ error: "Internal Server Error" }), { status: 500 });
}
};
}
這段看似簡單乾淨的 Wrapper,一套用到我們的 API 上,立刻引發了兩個嚴重的正式環境問題!
@logtail/node,它使用了 Node.js 原生的 http 與 zlib 模組。然而,我們的 Next.js 串流 API (SSE) 使用了 edge 或 Serverless 輕量化執行環境,導致 Vercel 直接拋出 Module not found: Can't resolve 'net' 錯誤!在 Serverless / Next.js 環境中,每一次 API 請求都可能觸發一次檔層級的 Logger 實體建立。AI 的寫法不僅沒有進行 Singleton 模式 控制,還遺失了 Sentry 的 setUser 資訊,導致 Sentry 雖然收到報錯,但我們根本不知道是「哪一位使用者」遭遇到這起 Bug!
我們調整了監控架構:
@logtail/browser / HTTP API),避免綁定 Node.js 原生模組。password、creditCard、token 等敏感 key。userId 與 email 雜湊值。修正後的程式碼:
lib/logger.ts(相容 Serverless / Edge 的結構化 Logger):
import { Logtail } from "@logtail/browser"; // 使用全環境通用 SDK
const logtailToken = process.env.NEXT_PUBLIC_LOGTAIL_SOURCE_TOKEN;
const logtail = logtailToken ? new Logtail(logtailToken) : null;
export const logger = {
info: (message: string, context?: Record<string, any>) => {
const cleanContext = sanitizeContext(context);
console.log(`[INFO] ${message}`, cleanContext || '');
logtail?.info(message, cleanContext);
},
error: (message: string, context?: Record<string, any>) => {
const cleanContext = sanitizeContext(context);
console.error(`[ERROR] ${message}`, cleanContext || '');
logtail?.error(message, cleanContext);
}
};
// 關鍵:脫敏函式,防止 PII 敏感數據落入 Log 系統
function sanitizeContext(context?: Record<string, any>) {
if (!context) return undefined;
const sanitized = { ...context };
const sensitiveKeys = ['password', 'token', 'secret', 'cardNumber', 'apiKey'];
for (const key of Object.keys(sanitized)) {
if (sensitiveKeys.some(k => key.toLowerCase().includes(k))) {
sanitized[key] = '[REDACTED]';
}
}
return sanitized;
}
lib/withMonitoring.ts(結合 Sentry 與 Logtail 的 API Wrapper):
import * as Sentry from "@sentry/nextjs";
import { logger } from "./logger";
import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";
export function withMonitoring(handler: Function) {
return async (req: Request, ...args: any[]) => {
const startTime = Date.now();
const url = new URL(req.url).pathname;
let userId = 'anonymous';
try {
// 1. 注入使用者上下文
const session = await getServerSession(authOptions);
if (session?.user?.id) {
userId = session.user.id;
Sentry.setUser({ id: userId });
}
// 2. 執行原始 API 邏輯
const response = await handler(req, ...args);
const duration = Date.now() - startTime;
// 3. 記錄成功結構化日誌
logger.info(`API Success: ${req.method} ${url}`, {
method: req.method,
url,
status: response.status,
duration: `${duration}ms`,
userId,
});
return response;
} catch (error) {
const duration = Date.now() - startTime;
// 4. 自動觸發 Sentry 告警並攜帶上下文
Sentry.withScope((scope) => {
scope.setTag("api_path", url);
scope.setExtra("duration_ms", duration);
Sentry.captureException(error);
});
// 5. 記錄 Error 日誌
logger.error(`API Error: ${req.method} ${url}`, {
method: req.method,
url,
duration: `${duration}ms`,
userId,
errorMessage: error instanceof Error ? error.message : String(error),
});
return new Response(
JSON.stringify({ error: "Internal Server Error", requestId: Sentry.lastEventId() }),
{ status: 500, headers: { "Content-Type": "application/json" } }
);
}
};
}
console.log,但到了正式環境(Serverless / Vercel),console.log 會隨即散落在無數個微短生命週期的 Function 執行個體中。只有透過 Logtail 這樣的結構化日誌收集器,結合 Sentry 的 Exception 堆疊追蹤,才能在使用者提出客訴前,主動發現並修復 Bug。sanitizeContext)與 Auth Session 重構進高階 Wrapper(HOC / Middleware)中,維持系統的乾淨與安全。