# 日志模块morgan
30 秒速记
morgan只记HTTP访问日志:谁、什么时候、请求了什么、返回什么状态、花了多久- 业务日志、错误堆栈、链路追踪要用
pino、winston或OpenTelemetry,morgan不管 - 默认在响应结束时记录,所以能拿到状态码和耗时
- 有代理时客户端
IP要配trust proxy,别直接信X-Forwarded-For - 敏感信息要脱敏:
URL里的token、Authorization、Cookie不能原样打出来
morgan 是 Express 生态里专门记访问日志的中间件,一个请求一行,记录方法、路径、状态码、耗时这些信息。 它就像门卫的登记本,只记谁进出了、几点、待了多久,至于进门干了什么、有没有出错,那是业务日志的事。所以看到 morgan 有输出只能说明请求进来了,接口对不对还要看状态码和业务日志。线上用的话我会关注三点:代理后面要配好 trust proxy 拿真实 IP,敏感参数要脱敏,健康检查这类高频请求用 skip 过滤掉。
理解「日志模块morgan」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。
工程中还要验证退出码、资源清理和异常输入。属于 testing 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。
morgan 的定位很单一:在请求处理完成后,按指定格式输出一行访问日志。最简单的用法:
const express = require('express');
const morgan = require('morgan');
const app = express();
app.set('trust proxy', 1); // 在 Nginx 等反向代理后面时才需要
app.use(morgan('combined', {
skip: (req) => req.url === '/health',
}));
app.get('/orders', (req, res) => res.json([]));
app.listen(3000);
// ::1 - - [08/Oct/2026:10:00:00 +0000] "GET /orders HTTP/1.1" 200 2 "-" "curl/8.0"
它能记录的信息来自请求和响应:客户端地址、时间、方法、路径、HTTP 版本、状态码、响应体长度、Referer、User-Agent、响应耗时。因为默认在响应结束时才写日志,状态码和耗时都是准确的。
它不负责的事情也要清楚:业务里发生了什么(下单成功、库存不足),错误的堆栈信息,跨服务的调用链路,这些要靠 pino、winston 这样的日志库以及 OpenTelemetry 这样的追踪方案。生产环境里一般会把 morgan 的输出通过 stream 接到统一的日志库,再由采集系统汇总,而不是各自往文件里写。
面试官追问
追问 1控制台出现了一行 morgan 日志,能说明 /orders 接口没问题吗?
不能,它只说明请求经过了中间件。日志里的状态码如果是 500,就是接口出错了;就算是 200,返回的数据对不对也要靠业务日志和测试断言来确认。
追问 2服务部署在 Nginx 后面,morgan 记录的 IP 全是 127.0.0.1,怎么改?
:remote-addr 取的是 req.ip,没配代理信任时就是 Nginx 的地址。在 Express 里设 app.set('trust proxy', 1),信任一层代理,req.ip 就会从 X-Forwarded-For 取真实地址。不要设成 true 无条件信任,否则客户端伪造这个头就能冒充任意 IP。
追问 3接口 URL 里带了 ?token=xxx,morgan 会原样打出来吗?怎么处理?
会,:url 是完整的 URL。自定义一个 token,把敏感参数替换掉:morgan.token('safe-url', req => req.originalUrl.replace(/token=[^&]+/, 'token=***')),格式里用 :safe-url 替代 :url。
追问 4morgan 和 winston、pino 是什么关系,能互相替代吗?
不能替代,是配合用。morgan 只管访问日志这一种,winston、pino 是通用日志库,业务事件和错误都靠它们。常见做法是把 morgan 的 stream 接到 winston 或 pino 上,统一格式和输出位置。
追问 5每秒几千请求,一半是 /health 健康检查,日志量太大怎么办?
用 skip 过滤:morgan('combined', { skip: req => req.url === '/health' })。正常请求如果也太多,可以只记错误,skip: (req, res) => res.statusCode < 400。
# 章节概览
30 秒速记
morgan是Express官方维护的访问日志中间件,也能在原生http服务里用- 学习路线:入门例子 → 写本地文件 → 核心
API→ 日志分割、写数据库 → 源码里的格式预编译 - 格式字符串如
:method :url :status会被预编译成函数,每次请求直接调用 - 只管访问日志,业务和错误日志另找方案
morgan 是 Express 常用的访问日志中间件,但它不绑定 Express,原生 http 服务也能用。 因为它本质上就是一个 (req, res, next) 函数,只要你在请求进来时调用它,它就会在响应结束时输出日志。这一章从最简单的控制台输出讲起,然后是写文件、配置项,再到日志分割和写数据库,最后看源码里格式字符串是怎么变成函数的。
morgan是express默认的日志中间件,也可以脱离express,作为node.js的日志组件单独使用。本文由浅入深,内容主要包括:
- morgan使用入门例子
- 如何将日志保存到本地文件
- 核心API使用说明及例子
- 进阶使用:1、日志分割 2、将日志写入数据库
- 源码剖析:morgan的日志格式以及预编译
面试官追问
追问 1一个不用 Express 的原生 Node.js 服务,能用 morgan 吗?
能,morgan() 返回的就是 (req, res, callback) 形式的函数,在 http.createServer 回调里手动调用就行:
追问 2日志文件一直增长,按天切分怎么做?
morgan 本身不管切分,配合 rotating-file-stream:rfs.createStream('access.log', { interval: '1d', path: logDir }),把它传给 stream 选项。容器部署的话更常见的做法是直接输出到 stdout,由平台采集。
追问 3访问日志写数据库还是写文件?
一般不直接写数据库,每个请求一次数据库写入,压力大不说,数据库出问题还会拖慢接口。常见做法是写 stdout 或文件,由采集工具异步送到 ELK、Loki 这类日志系统里去检索。
追问 4源码里的格式预编译是什么意思?
':method :url :status' 这样的格式字符串在初始化时就被 morgan.compile 转成一个函数,每个 :xxx 换成对应的 token 调用。这样每次请求不用重新解析字符串,直接执行函数拼日志,性能更好。
# 入门例子
30 秒速记
- 两步:
npm install express morgan,app.use(morgan('short'))挂在路由前面 - 一次访问两行日志很正常:浏览器会自动多请求一个
/favicon.ico - 第一行状态码
304:浏览器带了If-None-Match,Express判断内容没变返回协商缓存 ::ffff:127.0.0.1是IPv4映射的IPv6地址,服务监听在双栈上,不是异常- 预定义格式:
combined(线上常用)、common、short、tiny、dev(开发带颜色)
入门就两步:装 express 和 morgan,在路由之前 app.use(morgan('short')),之后每个请求都会在终端打一行日志。 第一次跑这个例子时可能会疑惑两件事:一是访问一次出现两行,那是浏览器顺带请求了 /favicon.ico;二是 IP 显示成 ::ffff:127.0.0.1,这是 IPv4 地址在 IPv6 双栈监听下的表示方式,本质还是本机。开发时我更习惯用 dev 格式,状态码带颜色,一眼能看到哪些请求出错。
首先,初始化项目。
npm install express morgan
然后,在basic.js中添加如下代码。
var express = require('express');
var app = express();
var morgan = require('morgan');
app.use(morgan('short'));
app.use(function(req, res, next){
res.send('ok');
});
app.listen(3000);
