昨天我們成功導入了 Redis,讓訂單列表快取與佇列通知都更順。
但系統一旦變得複雜,就會開始遇到「哪裡壞了?為什麼壞?」的問題。
今天要教大家如何在 Node.js + Express 專案中建立 錯誤處理機制 與 統一的 Logging 架構,幫助我們快速定位問題來源,並且記錄重要事件。
抓 bug 不再靠印 console.log()
。
能記錄誰在什麼時間點做了什麼事。
是日後導入 Sentry / ELK 的基礎。
層級 | 說明 |
---|---|
info |
一般操作記錄(登入成功、更新狀態) |
warn |
非致命異常(Redis timeout、cache miss) |
error |
程式錯誤、例外 |
debug |
開發時細節(SQL 查詢、API 輸入輸出) |
安裝套件:
npm i winston
建立 Logger:
// src/lib/logger.js
const { createLogger, format, transports } = require("winston");
const logger = createLogger({
level: process.env.LOG_LEVEL || "info",
format: format.combine(
format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
format.printf(({ timestamp, level, message }) => `${timestamp} [${level.toUpperCase()}] ${message}`)
),
transports: [
new transports.Console(),
new transports.File({ filename: "logs/app.log", maxsize: 5_000_000, maxFiles: 3 })
]
});
module.exports = logger;
// src/middlewares/errorHandler.js
const logger = require("../lib/logger");
function errorHandler(err, req, res, next) {
logger.error(`${req.method} ${req.url} - ${err.message}`);
res.status(500).json({ message: "伺服器內部錯誤", error: err.message });
}
module.exports = errorHandler;
在 src/index.js
掛上:
const errorHandler = require("./middlewares/errorHandler");
app.use(errorHandler);
// routes/order.js
const logger = require("../lib/logger");
router.patch(`/:id/status`, authAdmin, async (req, res, next) => {
try {
const { status } = req.body;
const order = await Order.findByIdAndUpdate(req.params.id, { status }, { new: true });
if (!order) return res.status(404).json({ message: "找不到訂單" });
logger.info(`Admin ${req.admin.email} 將訂單 ${order._id} 改為 ${status}`);
res.json({ success: true, order });
} catch (err) {
next(err); // 交給 errorHandler 處理
}
});
// worker/notify-worker.js
const logger = require("../src/lib/logger");
try {
const payload = JSON.parse(job);
if (payload.type === "order_completed") {
logger.info(`通知任務:訂單 ${payload.orderId} 已完成`);
// TODO: 實際發送推播
}
} catch (err) {
logger.error(`通知任務錯誤:${err.message}`);
}
Sentry:自動捕捉例外與 stack trace。
ELK Stack / Loki + Grafana:集中化日誌搜尋與視覺化。
Cloud Logging:部署後收集各服務 Log。
在 Node.js 世界中,winston
是最主流、最穩定的日誌管理套件。它的任務是把零散的 console.log
,變成結構化、可查詢、可保存的紀錄系統。
元件 | 功能 |
---|---|
Level | 控制日誌層級 (info , warn , error , debug ) |
Format | 決定輸出樣式(時間戳、顏色、JSON 格式等) |
Transport | 決定要把日誌輸出到哪裡(主控台、檔案、HTTP、雲端) |
Exception Handler | 捕捉未處理的錯誤並寫入指定檔案 |
const { createLogger, format, transports } = require('winston');
const logger = createLogger({
level: 'info',
format: format.combine(
format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
format.printf(({ timestamp, level, message }) => `${timestamp} [${level.toUpperCase()}] ${message}`)
),
transports: [
new transports.Console(),
new transports.File({ filename: 'logs/app.log' })
]
});
logger.info('伺服器啟動完成');
logger.error('資料庫連線失敗');
支援多層級、可篩選不同重要度。
可同時輸出至多個目的地(Console + File)。
能自動處理例外、集中錯誤來源。
可結合 ELK、Sentry、Grafana 等外部監控服務。
winston
就是 Node.js 的專業級黑盒紀錄器。
它讓你在系統出錯時,不只是看到錯誤,還能知道「何時、在哪裡、為什麼」。
使用 winston
建立統一 Logger,區分 log level,避免混亂。
全域錯誤處理能防止伺服器崩潰並回傳友善訊息。
Worker 與 API 都要記錄重要事件,方便後續追蹤。
明天(Day 23)我們將優化 LINE Notify 整合,在訂單完成時自動推播提醒顧客。