Skip to main content

Node.js 日志记录分析操作

一、原生 Node.js 操作日志

继前面的项目,实现日志操作 ▼

1、日志写入

Server 根目录下新建 logs 文件夹,存放几种类型的日志:

📁 server-project
|—— 📁 logs
|—— |—— 📜 access.log
|—— |—— 📜 error.log
|—— |—— 📜 event.log

添加操作日志的工具函数:

server-project/src/utils/log.js
const fs = require('fs')
const path = require('path');

// 写日志通用函数
const writeLog = (writeStream, log) => {
writeStream.write(log + '\n')
}

// 生成 writeStream 函数
const createWriteStream = (file) => {
const fullFileName = path.join(__dirname, '../', '../', 'logs', file)
const writeStream = fs.createWriteStream(fullFileName, {
flags: 'a'
})
return writeStream
}

// 写访问日志
const accessWriteStream = createWriteStream('access.log')
const access = (log) => {
writeLog(accessWriteStream, log)
}

module.exports = {
access
}

在接口调用的位置执行日志函数:

server-project/app.js
const { access } = require('./src/utils/log')

const serverHandle = (req, res) => {
// 记录 access log
access(`${req.method} -- ${req.url} -- ${req.headers['user-agent']} -- ${Date.now()}`)

访问接口后,可以看到输出的日志文件内容如下:

2、日志拆分

由于日志内容会不断积累,都放在一个文件中不好处理,因此有必要对日志进行拆分,例如按时间拆分日志文件:2022-02-22.access.log,可通过 linux 的 crontab 命令实现,即定时任务,步骤如下 ▼

  1. 设置定时任务,格式为 * * * * * command
  2. access.log 拷贝并重命名为 2022-02-22.access.log
  3. 清空 access.log 文件,继续积累日志。

具体实现如下,首先在 src/utils 下新建拷贝命名文件:

server-project/src/utils/copy.sh
#!/bin/sh
cd /Users/xxx/node-web-server-blog/server-project/logs
cp access.log $(date +%Y-%m-%d).access.log
echo "" > access.log

src/utils 目录下运行命令:

sh copy.sh

运行完,可以看到 access.log 拷贝给了 2022-02-22.access.log 文件,并清空 access.log 文件的内容,说明该命令生效了。接下来运行 crontab 对该命令定时执行 ▼

# 任意目录下运行
crontab -e

# 复制刚刚 copy.sh 文件的路径,在输入框中输入,然后按 ESC 退出编辑模式
* 0 * * * sh /Users/xxx/test/node-web-server-blog/server-project/src/utils/copy.sh

# shift + : 然后输入 wq 回车(保存后退出)即可完成
wq

# 可通过以下命令查看当前系统有哪些任务
crontab -l

执行效果如下:

3、日志分析

例如对 access.log 日志分析 Chrome 的占比。

由于日志是按行存储的,因此日志分析可以使用 Node.js 的 readline 逐行读取来实现 ▼

server-project/src/utils/readline.js
const fs = require('fs')
const path = require('path')
const readline = require('readline')

// 文件名
const fileName = path.join(__dirname, '../', '../', 'logs', 'access.log')

// 读取日志
const readStream = fs.createReadStream(fileName)

// 创建 readline 对象
const rl = readline.createInterface({
input: readStream
})

let sum = 0 // 总访问次数
let chromeNum = 0 // 使用 Chrome 访问次数

// 逐行读取
rl.on('line', lineData => {
if (!lineData) {
return
}
// 记录总访问次数
sum++

const arr = lineData.split(' -- ')
if (arr[2] && arr[2].indexOf('Chrome') > 0) {
// 累加 Chrome 访问次数
chromeNum++
}
})

// 监听读取完成
rl.on('close', () => {
console.log('Chrome 占比:' + chromeNum / sum)
})

执行效果如下:

点击查看原生 Node.js 操作日志完整代码

二、Express 框架操作日志

1、morgan 日志基本用法

Express 可直接通过 morgan 中间件操作日志:

app.js
const logger = require('morgan');

// 默认写法
app.use(logger('dev'));

// 默认 option 写法
app.use(logger('dev', {
stream: process.stdout
}));

2、morgan 写入日志

app.js
const logger = require('morgan');
const path = require('path');
const fs = require('fs');

// 日志操作
const ENV = process.env.NODE_ENV
if (ENV !== 'production') {
// 开发〡测试环境
app.use(logger('dev'));
} else {
// 线上环境
const logFileName = path.join(__dirname, 'logs', 'access.log')
const writeStream = fs.createWriteStream(logFileName, {
flags: 'a'
})
app.use(logger('combined', {
stream: writeStream
}));
}

3、morgan 预设日志

logger 的可用参数如下:

3-1、dev(默认)

dev 按响应状态着色的简明输出,供开发使用,状态颜色标识如下:

  • 成功代码的 :status 标记为绿色;
  • 服务器错误代码为红色;
  • 客户端错误代码为黄色;
  • 重定向代码为青色;
  • 信息代码为无色。
:method :url :status :response-time ms - :res[content-length]

# 示例
# GET /api/user/login-test 200 3.472 ms - 37
# GET /api/blog/count 200 33.869 ms - 21
# POST /api/blog/list 200 25.249 ms - 1331
# POST /api/blog/detail 200 11.286 ms - 160

3-2、combined

combined 使用标准 Apache 组合日志输出(线上环境常用)

:remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"

# 示例
# ::1 - - [01/Feb/2022:11:12:35 +0000] "GET /api/user/login-test HTTP/1.1" 200 37 "http://localhost:8001/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"
# ::1 - - [01/Feb/2022:11:12:35 +0000] "GET /api/blog/count HTTP/1.1" 200 21 "http://localhost:8001/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"
# ::1 - - [01/Feb/2022:11:12:35 +0000] "POST /api/blog/list HTTP/1.1" 200 1331 "http://localhost:8001/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"
# ::1 - - [01/Feb/2022:11:12:37 +0000] "POST /api/blog/detail HTTP/1.1" 200 160 "http://localhost:8001/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"

3-3、common

common 使用标准 Apache 通用日志输出。

:remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length]

# 示例
# ::1 - - [01/Feb/2022:11:14:05 +0000] "GET /api/user/login-test HTTP/1.1" 200 37
# ::1 - - [01/Feb/2022:11:14:05 +0000] "GET /api/blog/count HTTP/1.1" 200 21
# ::1 - - [01/Feb/2022:11:14:05 +0000] "POST /api/blog/list HTTP/1.1" 200 1331
# ::1 - - [01/Feb/2022:11:14:06 +0000] "POST /api/blog/detail HTTP/1.1" 200 160

3-4、short

short 比默认值更短,还包括响应时间。

:remote-addr :remote-user :method :url HTTP/:http-version :status :res[content-length] - :response-time ms

# 示例
# ::1 - GET /api/user/login-test HTTP/1.1 200 37 - 1.039 ms
# ::1 - GET /api/blog/count HTTP/1.1 200 21 - 8.093 ms
# ::1 - POST /api/blog/list HTTP/1.1 200 1331 - 2.931 ms
# ::1 - POST /api/blog/detail HTTP/1.1 200 160 - 2.624 ms

3-5、tiny

tiny 使用最小输出。

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

# 示例
# GET /api/user/login-test 200 37 - 1.305 ms
# GET /api/blog/count 200 21 - 4.672 ms
# POST /api/blog/list 200 1331 - 4.320 ms
# POST /api/blog/detail 200 160 - 2.243 ms
注意

在中间件中写 console.log 语句是比较糟糕的做法,因为 console.log(包括其他同步的代码)会阻塞 Node.js 的异步事件循环,降低服务器的吞吐率。在实际生产中,使用 morgan 日志中间件来自定义日志是推荐的做法。

三、Koa 框架操作日志

Koa 通过 koa-morgan 中间件操作日志,用法与 Express 的 morgan 相同:

npm i koa-morgan --save

使用如下:

app.js
const logger = require('koa-logger')
const path = require('path')
const fs = require('fs')
const morgan = require('koa-morgan')

app.use(logger())
// logger
app.use(async (ctx, next) => {
const start = new Date()
await next()
const ms = new Date() - start
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`)
})
// 日志操作
const ENV = process.env.NODE_ENV
if (ENV !== 'production') {
// 开发〡测试环境
app.use(morgan('dev'));
} else {
// 线上环境
const logFileName = path.join(__dirname, 'logs', 'access.log')
const writeStream = fs.createWriteStream(logFileName, {
flags: 'a'
})
app.use(morgan('combined', {
stream: writeStream
}));
}