Morgan HTTP 訊息格式化工具

更新 發佈閱讀 19 分鐘

簡介

Morgan 是 Node.js Express 官方推薦 的 HTTP request logger middleware。

Morgan

Morgan 是用來整理整個 HTTP 從接收 request 到發出 response 整個 HTTP request lifecycle 訊息並將原本存文字的訊息做 聚合(aggregation)處理,將原本過程產生多筆的純文字訊統整成有意義的資訊,並把重要訊息做分類及格式化處理,使之可以被處理成 JSON 等方便閱讀的資料格式。

沒有使用 Morgan 的話

首先會沒有明確的 HTTP request lifecycle 結束時間,在接收 request 要自己紀錄 logger.info('request received');,然後整個流程會在系統中的 middleware 中不斷流轉,會難以追蹤,且你會不知道是否有成功送出 response ,可能中途被 throw Error ,但是你仍然會看到「request received」等錯誤的語意。最後會變成自己寫「完整的 HTTP logging middleware」,監聽 res.on('finish') ,自己計算時間,自己規範欄位。本質上就是自己把整個 Morgan 的功能重寫一遍。

再來就是整個 header 的資訊是以純文字方式呈現且資訊量過大,有些要正規化,有些要做格式轉換,如果要自記處理會非常麻煩,變成要在每個 middleware 使用接收資料的 logger (紀錄系統 log 日誌的工具) 都要寫入要處理的 request 欄位資料,且每個 middleware 都有 request 和 response ,在不同流程要記錄不同欄位,不僅會讓程式碼變得複雜化,也很容易產生 copy-paste 汙染。

logger.info({
  method: req.method,
  url: req.url,
  headers: req.headers,
});

使用 Morgan 之後

  • Morgan Middleware 先攔截 request
  • 記錄 req.method
  • 記錄 req
  • 記錄 req.url
  • 記錄開始時間
  • 設定 listener 監聽 res.on("finish")

或等價事件,然後取得:

  • req.method
  • req.url
  • res.statusCode
  • response time
  • headers

Morgan 會在接收 request 後綁定 HTTP lifecycle

  • request start
  • response finish
  • error / abort

再放行到下一個 middleware

當 response 準備送出時,Morgan 會:

  • 計算 response time
  • 讀取 res.statusCode
  • 將所有資訊套入你設定的格式(如 combined)

保證 log 一定「一個 request 一筆紀錄」。

Morgan 會標準化欄位,例如:

  • method
  • url
  • status
  • response-time
  • content-length

讓資料更容易可以被聚合,比較。

再來是 Morgan 可以使用標準化 token 來處理這些資料並標轉化欄位,變成容易處理的格式化資料,例如:

::method :url :status :response-time ms - :res[content-length]

每一個都是 token(Morgan 內建或自定義)

最後就是 Morgan 可插拔(stream)模式,在其函數可以接入 console ,或是其他 logger 工具來處理 Morgan 產生的資訊。

使用

基本使用方式

import express from "express";
import morgan from "morgan";

const PORT = 3000;
const app = express();

app.use(morgan("combined")); // 這種方式只會讓資訊以 console 的方式顯示

app.get("/", (req, res, next) => {
  res.send("server is running.");
});

app.listen(PORT, () => {
  console.log(`🚀 Server running on http://localhost:${PORT}`);
});

📦 內建格式(常用三種)

1. dev

開發最常用: app.use(morgan("dev"))

會顯示:

GET /api/users 200 12.4 ms - 217

2. combined

使用: app.use(morgan("comvined"))

最完整(與 Apache access log 同格式)

是 Apache access log 格式的複製版,它只包含

  • remote-addr
  • remote-user
  • time
  • request-line
  • status
  • content-length
  • referrer
  • user-agent

輸出:

127.0.0.1 - - [12/Feb/2025:10:29:03 +0000] "GET /api/login HTTP/1.1" 200 123 "-" "Mozilla/5.0..."

3. tiny

非常精簡

使用: app.use(morgan("tiny"))

GET / 200 3.439 ms - 13

4. 自定義資料格式

這部分細節較多,分開成幾個部分說明

首先要先了解一下 Morgan 的 toke 是什麼?

在這裡的 toke 其實是 Morgan 專為 HTTP request / response 中,取出某一個欄位值的『取值器』,如以下的例子:

morgan(':method :url :status :response-time ms')

這裡的

  • :method
  • :url
  • :status
  • :response-time ms

每一個都是 token ,Morgan 在 request 結束時,會把它們「換成實際值」:

GET /test 200 12.3 ms

知道 token 是什麼了,接下來就是了解有那些 toke 可以使用

Request 基本資訊(最常用)

| Token             | 說明              | 範例                 |
| ----------------- | ----------------- | -------------------- |
| :method         | HTTP method       | `GET`               |
| :url           | 原始 URL         | `/api/users?page=1` |
| :status         | HTTP status code | `200`               |
| :http-version   | HTTP 版本         | `1.1`               |
| :response-time | 回應時間(ms)   | `12.345`             |

📌 :response-time

只有 response 完成後才有

單位是 毫秒(ms)

Client / Network 相關

| Token          | 說明                       | 範例                  |
| -------------- | -------------------------- | --------------------- |
| :remote-addr | client IP                  | 127.0.0.1           |
| :remote-user | HTTP auth user(幾乎不用) | -                   |
| :user-agent  | User-Agent                 | Mozilla/5.0...      |
| :referrer    | Referrer                    | https://example.com |

📌 實務提醒

如果有使用 有

  • reverse proxy(Nginx / Cloudflare / ELB)
  • Docker
  • Kubernetes
  • CDN

真實 client IP 通常要搭配 x-forwarded-for(自訂 token)才能取的,不然的話很大可能會只取得上游的 Private IP ,如: 192.168.3.1 、 172.16.31.3 之類的私有 IP

經過多層代理,x-forwarded-for 結構可能會是這樣:

x-forwarded-for: 203.0.113.42, 10.0.0.1, 172.16.0.1

不過使用上需要對 node.js 程式入口做調整,讓 node.js 可以處理 x-forwarded-for 資料,我直接把說明用註解的方式放在範例程式碼:

// app.ts

// ✅ 第 1 步:告訴 Express「我有在代理後面」

app.set('trust proxy', true);
// 這一步讓 Express:
// 願意相信 x-forwarded-for
// 正確處理 req.ip

// ✅ 第 2 步:定義一個 Morgan 自訂 token
import morgan from 'morgan';

morgan.token('client-ip', (req) => {
  const xff = req.headers['x-forwarded-for'];
  if (typeof xff === 'string') {
    // 多層代理時,取第一個
    return xff.split(',')[0].trim();
  }

  // 沒有代理時,退回 socket IP
  return req.socket.remoteAddress || '';

  // ✅ 第 3 步:在 format 裡使用這個 token
  app.use(
  morgan(':client-ip :method :url :status :response-time ms')
);

  // 可以在近一步把資料格式整理成 JSON 格式
  morgan.format('http-json', (tokens, req, res) => {
  return JSON.stringify({
    ip: tokens['client-ip'](req, res),
    method: tokens.method(req, res),
    url: tokens.url(req, res),
    status: Number(tokens.status(req, res)),
    responseTimeMs: Number(tokens['response-time'](req, res)),
  });
});

app.use(morgan('http-json'));

});

Response 相關

| Token           | 說明                | 範例                   |
| --------------- | ------------------- | ---------------------- |
| :res[header] | 讀 response header | :res[content-length] |
| :status       | response status     | 404                   |

📌 Request Header 常用用法

| Token              | 說明             |
| ------------------ | ---------------- |
| :req[user-agent] | 同 :user-agent |
| :req[host]       | Host             |
| :req[accept]     | Accept           |

⚠ 安全提醒

  • authorization
  • cookie

不建議直接記(需遮罩),有洩漏隱私風險

時間相關

| Token         | 說明                  | 範例                             |
| ------------- | --------------------- | -------------------------------- |
| :date       | 當下時間(預設格式) | Sat, 12 Dec 2025 10:20:30 GMT |
| :date[clf] | Common Log Format     | 12/Dec/2025:10:20:30 +0000     |
| :date[iso] | ISO 8601             | 2025-12-12T10:20:30.123Z       |

內建 format 專用(間接 token)

| Token                   | 用途            |
| ----------------------- | --------------- |
| :method               | dev / combined |
| :url                 | dev / combined |
| :status               | dev / combined |
| :response-time       | dev             |
| :res[content-length] | combined       |

5. 將 Morgan 的 token 資料整理成 json 字串,讓 Winston 可以用 json 格式化資料紀錄

‼ 重要

因為 Morgan 只能輸出純字串資料,所以在最外面要到一層 JSON.stringify() 將 javascript object 資料轉換成字串資料,才能被正確輸出。

在知道 token 是什麼了之後如果不做處理直接輸出,會變成一堆只有數值的字串:

GET /test 200 12.3 ms

可以用 Morgan 的自定義輸出處理函式來將原始資料做 條件判斷、欄位清洗、遮罩時 等,記住:最終只能輸出字串資料,不然會變成 [object, obtect]

import morgan from 'morgan';

morgan.format('http-json', (tokens, req, res) => {
  return JSON.stringify({
    type: "http", // 用於表示 log 的來源
    remoteAddr: tokens['remote-addr']?.(req, res) ?? "-",
    method: tokens.method?.(req, res) ?? "-", // ?. ??
    url: tokens.url?.(req, res) ?? "-",
    status: Number(tokens.status?.(req, res) ?? 0),
    contentLength: tokens.res?.(req, res, 'content-length') ?? "-",
    responseTimeMs: Number(tokens["response-time"]?.(req, res) ?? 0),
    timestamp: new Date().toISOString(),
  });
});

// 使用
app.use(morgan('http-json'));

// 在傳入 winston 前轉成 object
import { logger } from './logger';

app.use(
  morgan('http-json', {
    stream: {
      write: (message) => {

        // message 本身就是 JSON 字串轉型成 object 給 logger 接收
        logger.info(JSON.parse(message));
      },
    },
  })
);


留言
avatar-img
留言分享你的想法!
avatar-img
weijie 的各種記事
0會員
5內容數
原本是打算用來當作技術部落格的,不過以後也會把各種雜記紀錄於此
weijie 的各種記事的其他內容
2025/12/07
步驟 一般在 git 預設 commit 訊息編輯器會是 vim ,但是如果不習慣使用 vim 命令列,且有安裝 visual studio code 的環境下,可以使用以下命令將 commit 訊息編輯器改為 visual studio code : git config --global c
Thumbnail
2025/12/07
步驟 一般在 git 預設 commit 訊息編輯器會是 vim ,但是如果不習慣使用 vim 命令列,且有安裝 visual studio code 的環境下,可以使用以下命令將 commit 訊息編輯器改為 visual studio code : git config --global c
Thumbnail
2025/12/03
前言 這裡主要是記錄我想把學習在 node.js 使用 prisma 中的踩坑紀錄,把整個過程整理成筆記 環境建立 1. 建立專案資料夾 mkdir node-prisma-pg cd ./node-prisma-pg 2. 設定 mpn 環境 npm init -y 3. 修改 pa
Thumbnail
2025/12/03
前言 這裡主要是記錄我想把學習在 node.js 使用 prisma 中的踩坑紀錄,把整個過程整理成筆記 環境建立 1. 建立專案資料夾 mkdir node-prisma-pg cd ./node-prisma-pg 2. 設定 mpn 環境 npm init -y 3. 修改 pa
Thumbnail
2025/11/15
前言 原本想讓 VirtualBox 內的 ubuntu 虛擬機可以跟 windows host 互相傳輸檔案,但是客體應用程式 (virtualbox guest additions) 一直無法安裝,所以就想說在 ubuntu VM 內安奘 samba server 用來傳輸檔案好了。 另外雖
2025/11/15
前言 原本想讓 VirtualBox 內的 ubuntu 虛擬機可以跟 windows host 互相傳輸檔案,但是客體應用程式 (virtualbox guest additions) 一直無法安裝,所以就想說在 ubuntu VM 內安奘 samba server 用來傳輸檔案好了。 另外雖
看更多
你可能也想看
Thumbnail
安裝完成 nodejs 後選用一個工作目錄執行 npm init,npm 會產生一個 package.json 檔案,之後為此專案安裝套件時都會記錄在此,讓專案可以很容易的重建和移植,也可設定 npm start 執行時以哪一個 js 檔當作系統入口。直接開寫了,以下我用 app.js 當作系統入口
Thumbnail
安裝完成 nodejs 後選用一個工作目錄執行 npm init,npm 會產生一個 package.json 檔案,之後為此專案安裝套件時都會記錄在此,讓專案可以很容易的重建和移植,也可設定 npm start 執行時以哪一個 js 檔當作系統入口。直接開寫了,以下我用 app.js 當作系統入口
Thumbnail
NodeJS 學習來到 file systems 操作,在文檔操作上有分為同步跟異步的處理,接下來分階段介紹操作函數
Thumbnail
NodeJS 學習來到 file systems 操作,在文檔操作上有分為同步跟異步的處理,接下來分階段介紹操作函數
Thumbnail
最近跟著影片學習NodeJS,第一部分先學習對Buffer的處理跟理解,以下是對於NodeJS Buffer的理解筆記
Thumbnail
最近跟著影片學習NodeJS,第一部分先學習對Buffer的處理跟理解,以下是對於NodeJS Buffer的理解筆記
Thumbnail
NodeJS作用? NodeJS 作為一個後端程式語言,與伺服器交互,能夠開發服務器應用、開發工具類應用,例如:Webback、Vite 這些好用工具,或是透過NodeJS的electron框架開發桌面端應用,而常見的桌面端應用如 VScode
Thumbnail
NodeJS作用? NodeJS 作為一個後端程式語言,與伺服器交互,能夠開發服務器應用、開發工具類應用,例如:Webback、Vite 這些好用工具,或是透過NodeJS的electron框架開發桌面端應用,而常見的桌面端應用如 VScode
Thumbnail
這篇想來寫,剛碰到js得時候,為了讓程式可以運作而安裝Node.js 。Node.js 是能夠在伺服器上面運行 JavaScript 的應用平台環境,透過 Node.js 提供的函式庫與執行環境能完成伺服器端服務。此篇幅就直接從純後端的角度切入摟(對不起拉我寫來寫去還是不知道怎麼順順的寫好文章開頭Q
Thumbnail
這篇想來寫,剛碰到js得時候,為了讓程式可以運作而安裝Node.js 。Node.js 是能夠在伺服器上面運行 JavaScript 的應用平台環境,透過 Node.js 提供的函式庫與執行環境能完成伺服器端服務。此篇幅就直接從純後端的角度切入摟(對不起拉我寫來寫去還是不知道怎麼順順的寫好文章開頭Q
Thumbnail
這一篇文章,我想來談談模板語言(template language/engine)。而其中比較有名的為handlebar、pug、ejs。那我會的事後兩著,因此拿這兩個出來寫一篇文章。 Pug 指令:npm install pug 比起 HTML 的語法,pug 語法可以說簡潔很多。 那下面
Thumbnail
這一篇文章,我想來談談模板語言(template language/engine)。而其中比較有名的為handlebar、pug、ejs。那我會的事後兩著,因此拿這兩個出來寫一篇文章。 Pug 指令:npm install pug 比起 HTML 的語法,pug 語法可以說簡潔很多。 那下面
Thumbnail
由於Javascript本身設計就適合於單線程的應用, 但一般後端應用程式都會支援多個服務來處理client的請求, nodejs中也提供了cluster模組來達成此功能。 Cluster的原理很簡單,由於每個Process都只能用單核心的CPU來運行,那麼就多開幾個來幫忙處理吧! 而這個Clust
Thumbnail
由於Javascript本身設計就適合於單線程的應用, 但一般後端應用程式都會支援多個服務來處理client的請求, nodejs中也提供了cluster模組來達成此功能。 Cluster的原理很簡單,由於每個Process都只能用單核心的CPU來運行,那麼就多開幾個來幫忙處理吧! 而這個Clust
Thumbnail
在第一次建立rails專案時,通常都會遇到不少問題,像我是使用windows系統,所以也經歷了一些麻煩。如果各位第一次建立rails專案就可以看到下圖綠色字體的成功字樣,那真的如天選之人般幸運阿~ 如果你跟我一樣注定要經歷windows系統的苦痛,就請繼續看下去吧QQ
Thumbnail
在第一次建立rails專案時,通常都會遇到不少問題,像我是使用windows系統,所以也經歷了一些麻煩。如果各位第一次建立rails專案就可以看到下圖綠色字體的成功字樣,那真的如天選之人般幸運阿~ 如果你跟我一樣注定要經歷windows系統的苦痛,就請繼續看下去吧QQ
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News