iT邦幫忙

2025 iThome 鐵人賽

DAY 22
0
Modern Web

用 LINE OA 打造中小企業訂單系統:從零開始的 30 天實作紀錄系列 第 22

讓錯誤看得見:Logging 與錯誤處理設計

  • 分享至 

  • xImage
  •  

昨天我們成功導入了 Redis,讓訂單列表快取與佇列通知都更順。

但系統一旦變得複雜,就會開始遇到「哪裡壞了?為什麼壞?」的問題。

今天要教大家如何在 Node.js + Express 專案中建立 錯誤處理機制統一的 Logging 架構,幫助我們快速定位問題來源,並且記錄重要事件。


為什麼需要 Logging?

  • 抓 bug 不再靠印 console.log()

  • 能記錄誰在什麼時間點做了什麼事。

  • 是日後導入 Sentry / ELK 的基礎。


Logging 的層級設計

層級 說明
info 一般操作記錄(登入成功、更新狀態)
warn 非致命異常(Redis timeout、cache miss)
error 程式錯誤、例外
debug 開發時細節(SQL 查詢、API 輸入輸出)

建立統一 Logger

安裝套件:

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;

全域錯誤處理 Middleware

// 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);

在訂單狀態更新中寫 Log

// 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 也要記錄任務執行

// 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。


延伸補充:認識 winston 與 Logger

在 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 整合,在訂單完成時自動推播提醒顧客。


上一篇
Redis 導入:快取與簡單佇列應用
系列文
用 LINE OA 打造中小企業訂單系統:從零開始的 30 天實作紀錄22
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言