前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • 基础篇

    • 环境搭建
    • node部署
    • 基础应用
  • 内置模块

    • 1.0 本地路径处理 path
    • 2.0 文件系统操作 fs
    • 3.1 基础调试 console
    • 3.2 本地调试远程服务器上的Node代码
    • 4.1 网络服务 http
    • 4.2 网络服务 http res
    • 4.3 网络服务 http req
    • 4.4 网络服务 http server
    • 4.5 网络服务 http client
    • 4.6 网络服务 https
    • 4.7 网络TCP net
    • 4.8 网络UDP dgram
    • 4.9 域名解析 dns
    • 5.0 网络地址解析 url
    • 5.1 URL查询字符串 querystring
    • 6.1 流操作 stream
    • 6.2 逐行读取 readline
    • 7.1 进程相关 process
    • 7.2 子进程 child
    • 8.1 二进制数据 buffer
    • 8.2 二进制解码 string_decoder
    • 9.1 事件机制 events
    • 9.2 实用工具模块 util
    • 9.3 数据加密 crypto
    • 9.4 MD5入门介绍及crypto模块的应用
    • 9.4 资源压缩 zlib
    • 9.5 集群 cluster
    • 9.6 v8
  • 进阶篇

    • 5分钟入门非对称加密用法
    • cluster
    • cookie_parser深入
    • crypto模块之理论篇
    • express+cookie_parser签名机制深入剖析
    • express+session实现简易身份认证
    • https
    • log4js入门实例
    • node8_napi
    • 使用 async 控制并发
    • 使用 eventproxy 控制并发
    • 使用 superagent 与 cheerio 完成简单爬虫
    • 基于express+muter的文件上传
    • 将图片转成datauri嵌入到html
    • 常用中间件 body_parser 实现解析
    • 日志模块morgan
    • 服务端字符编解码&乱码处理 charset_enc_dec
    • 测试用例mocha,should,istanbul
    • 调试日志打印debug模块
  • Express

    • 1.1 Express概览篇
    • 1.2 Express
  • Koa2

    • 1.0 koa2概览篇
    • 1.1 快速开始
    • 1.2 async await使用
    • 1.3 koa2简析结构
    • 1.4 koa中间件开发与使用
    • 10.1 单元测试
    • 11.1 开发debug
    • 12.1 快速启动
    • 12.2 框架设计
    • 12.3 分层操作
    • 12.4 数据库设计
    • 12.5 路由设计
    • 12.6 webpack4环境搭建
    • 12.7 使用react
    • 12.8 登录注册功能实现
    • 12.9 session登录态判断处理
    • 13.1 import export使用
    • 2.1 原生koa2实现路由
    • 2.2 koa router中间
    • 3.1 GET请求数据获取
    • 3.2 POST请求数据获取
    • 3.3 koa bodyparser中间件
    • 4.1 原生koa2实现静态资源服务器
    • 4.2 koa static中间件
    • 5.1 koa2使用cookie
    • 5.2 koa2实现session
    • 6.1 koa2加载模板引擎
    • 6.2 ejs模板引擎
    • 7.1 busboy模块
    • 7.2 上传文件简单实现
    • 7.3 异步上传图片实现
    • 8.1 mysql模块
    • 8.2 async await封装使用mysql
    • 8.3 项目建表初始化
    • 9.1 原生koa2实现JSONP
    • 9.2 koa jsonp中间件
  • 其他

    • 操作数据库
    • Session 与 Token
    • Cookie、Session、Token、JWT
    • Socket

# 日志模块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);
@前端进阶之旅: 代码已经复制到剪贴板

基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • 基础篇

    • 环境搭建
    • node部署
    • 基础应用
  • 内置模块

    • 1.0 本地路径处理 path
    • 2.0 文件系统操作 fs
    • 3.1 基础调试 console
    • 3.2 本地调试远程服务器上的Node代码
    • 4.1 网络服务 http
    • 4.2 网络服务 http res
    • 4.3 网络服务 http req
    • 4.4 网络服务 http server
    • 4.5 网络服务 http client
    • 4.6 网络服务 https
    • 4.7 网络TCP net
    • 4.8 网络UDP dgram
    • 4.9 域名解析 dns
    • 5.0 网络地址解析 url
    • 5.1 URL查询字符串 querystring
    • 6.1 流操作 stream
    • 6.2 逐行读取 readline
    • 7.1 进程相关 process
    • 7.2 子进程 child
    • 8.1 二进制数据 buffer
    • 8.2 二进制解码 string_decoder
    • 9.1 事件机制 events
    • 9.2 实用工具模块 util
    • 9.3 数据加密 crypto
    • 9.4 MD5入门介绍及crypto模块的应用
    • 9.4 资源压缩 zlib
    • 9.5 集群 cluster
    • 9.6 v8
  • 进阶篇

    • 5分钟入门非对称加密用法
    • cluster
    • cookie_parser深入
    • crypto模块之理论篇
    • express+cookie_parser签名机制深入剖析
    • express+session实现简易身份认证
    • https
    • log4js入门实例
    • node8_napi
    • 使用 async 控制并发
    • 使用 eventproxy 控制并发
    • 使用 superagent 与 cheerio 完成简单爬虫
    • 基于express+muter的文件上传
    • 将图片转成datauri嵌入到html
    • 常用中间件 body_parser 实现解析
    • 日志模块morgan
      • 章节概览
      • 入门例子
      • 将日志打印到本地文件
      • 使用讲解
        • 核心API
        • 自定义日志格式
      • 自定义format
      • 自定义token
      • 高级使用
      • 深入剖析
      • 相关链接
    • 服务端字符编解码&乱码处理 charset_enc_dec
    • 测试用例mocha,should,istanbul
    • 调试日志打印debug模块
  • Express

    • 1.1 Express概览篇
    • 1.2 Express
  • Koa2

    • 1.0 koa2概览篇
    • 1.1 快速开始
    • 1.2 async await使用
    • 1.3 koa2简析结构
    • 1.4 koa中间件开发与使用
    • 10.1 单元测试
    • 11.1 开发debug
    • 12.1 快速启动
    • 12.2 框架设计
    • 12.3 分层操作
    • 12.4 数据库设计
    • 12.5 路由设计
    • 12.6 webpack4环境搭建
    • 12.7 使用react
    • 12.8 登录注册功能实现
    • 12.9 session登录态判断处理
    • 13.1 import export使用
    • 2.1 原生koa2实现路由
    • 2.2 koa router中间
    • 3.1 GET请求数据获取
    • 3.2 POST请求数据获取
    • 3.3 koa bodyparser中间件
    • 4.1 原生koa2实现静态资源服务器
    • 4.2 koa static中间件
    • 5.1 koa2使用cookie
    • 5.2 koa2实现session
    • 6.1 koa2加载模板引擎
    • 6.2 ejs模板引擎
    • 7.1 busboy模块
    • 7.2 上传文件简单实现
    • 7.3 异步上传图片实现
    • 8.1 mysql模块
    • 8.2 async await封装使用mysql
    • 8.3 项目建表初始化
    • 9.1 原生koa2实现JSONP
    • 9.2 koa jsonp中间件
  • 其他

    • 操作数据库
    • Session 与 Token
    • Cookie、Session、Token、JWT
    • Socket
完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

日志模块morgan|Nodejs专题系列

# 日志模块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);
@前端进阶之旅: 代码已经复制到剪贴板

node basic.js运行程序,并在浏览器里访问 http://127.0.0.1:3000 ,打印日志如下

➜  2016.12.11-advanced-morgan git:(master) ✗ node basic.js
::ffff:127.0.0.1 - GET / HTTP/1.1 304 - - 3.019 ms
::ffff:127.0.0.1 - GET /favicon.ico HTTP/1.1 200 2 - 0.984 ms
@前端进阶之旅: 代码已经复制到剪贴板

面试官追问

追问 1浏览器访问一次首页,终端出现了两行日志,是请求被处理了两次吗?
参考回答

看路径就知道,一行是 GET /,一行是 GET /favicon.ico,浏览器会自动请求网站图标。如果两行路径完全一样,那才要去查重定向、重试或者前端重复请求。

追问 2为什么第一行日志的状态码是 304?
参考回答

浏览器之前访问过,这次带上了 If-None-Match,Express 的 res.send 会自动生成 ETag 并对比,内容没变就返回 304,不发响应体,所以长度那一列是 -。强制刷新或者禁用缓存就会看到 200。

追问 3morgan 放在路由后面会怎样?
参考回答

路由处理函数已经 res.send 了,不会调 next(),请求根本到不了 morgan,就不会有日志。日志中间件要放在最前面。

追问 4日志里的 IP 是 ::ffff:127.0.0.1,要不要处理成纯 IPv4?
参考回答

不是异常,这是 IPv4 映射到 IPv6 的格式,因为 app.listen(3000) 默认监听 :: 双栈。如果日志系统需要统一格式,可以在自定义 token 里去掉 ::ffff: 前缀,或者监听 0.0.0.0。

追问 5short 和 dev、combined 有什么区别,各自什么时候用?
参考回答

combined 是标准的 Apache 日志格式,带时间、Referer、User-Agent,线上用它方便对接日志分析工具;dev 很短,状态码带颜色,适合本地开发;short、tiny 介于中间,信息少一些。

# 将日志打印到本地文件

← 常用中间件 body_parser 实现解析服务端字符编解码&乱码处理 charset_enc_dec →

fe