DingMing

丁大铭的个人空间,用来分享一些前端小技巧,默默成长吧,哈哈

Koa2+Log4js+PM2持久化日志及使用问题

  |  
 阅读次数

最近一直在开发node相关的项目,做了一段时间了,项目已经让大家正常使用上了,但是日志系统一直没有接入,所以经过查阅与平衡复杂度,决定使用koa-log4进行日志的管理。关于 log4js 的介绍在这里就不多叙述了,想了解请点击  log4js详细介绍

说到数据持久化,最普遍的无非就两种方式:

  • 文件存储(导出.log文件)
  • 数据库存储(基础的增加功能)

本文将以log4js为主线,以文件存储形式 并且与运维配合 通过logstash上传ELK到Kibana查询的方式实现的。

持久化至文件

log4js 输入日志到文件有两种形式:

  • file 输出到文件, 指定单一文件名称, 例如: default.log
  • dateFile 输出到文件,文件可以按日期模式滚动,例如: 2021-12-03.log

直接上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// log4js.js
import log4js from 'koa-log4'
import path from 'path'

log4js.configure({
appenders: {
error: {
type: 'file', //日志类型
category: 'errLogger', //日志名称
filename: path.join('logs/', 'error/error.log'), //日志输出位置,当目录文件或文件夹不存在时自动创建
maxLogSize: 104800, // 文件最大存储空间
backups: 100 //当文件内容超过文件存储空间时,备份文件的数量
},
response: {
type: 'dateFile',
category: 'resLogger',
filename: path.join('logs/', 'responses/'),
pattern: 'yyyy-MM-dd.log', //日志输出模式
alwaysIncludePattern: true,
maxLogSize: 104800,
backups: 100
}
},
categories: {
error: {appenders: ['error'], level: 'info'},
response: {appenders: ['response'], level: 'info'},
default: { appenders: ['response'], level: 'info'}
},
replaceConsole: true
})

日志配置文件我们已经完成,在这里定义了两种形式的日志,分别是 errLogger 错误日志, resLogger 响应日志。
接下来我们将这两种日志进行自定义格式化输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// log4js.js

let logger = {}

const formatError = (ctx, err) => {
const {method,url} = ctx
let body = ctx.request.body
const user = ctx.state.user
return {method, url,body,user, err}
}

const formatRes = (ctx,costTime) => {
const {method,url,response:{status,message,body:{success}},request:{header:{authorization}}} = ctx
let body = ctx.request.body
const user = ctx.state.user
return {method, url,user, body, costTime,authorization, response:{status,message,body:{success}}}
}


let errorLogger = log4js.getLogger('error')
let resLogger = log4js.getLogger('response')

// 封装错误日志
logger.errLogger = (ctx, error) => {
if(ctx && error) {
errorLogger.error(formatError(ctx, error))
}
}

// 封装响应日志
logger.resLogger = (ctx, resTime) => {
if(ctx) {
resLogger.info(formatRes(ctx, resTime))
}
}

export default logger

在我们的应用中使用我们自定义的日志模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//app.js
import log4js from './utils/log4js'

// 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`)
log4js.resLogger(ctx, ms)
})

// error-handling
app.on('error', (err, ctx) => {
log4js.errLogger(ctx, err)
console.error('server error', err)
})

此时我们的访问信息都已被输出到了项目 logs 文件夹下面。

集群模式PM2 Log4js log写入失败问题

正常情况下,使用PM2持久化进程输出日志是完全的,但是一旦使用 集群模式(“instances” : number)下时,就出现写不入日志的情况

!!! 每次重启PM2时,需要先delete任务,然后再重新启动

正常直接启动会在配置 log 目录下生成一个yyyy-MM-dd-hh.log格式的文件,但是用pm2启动的时候并没有生成。

log4js 的调用方法为require(‘log4js’).getLogger(),所以就直接在源码中找getLogger:

  1. 通过package.json中的main我们确定它的主入口文件为./lib/log4js;
  2. 找到getLogger方法

其中有个isMaster()方法,判断是否为主进程

1
2
3
4
5
6
7
8
function isPM2Master() {
return config.pm2 && process.env[config.pm2InstanceVar] === '0'
}

function isMaster() {
return config.disableClustering || cluster.isMaster || isPM2Master()
}

可以看到isPM2Master是通过 config 中的pm2参数和pm2InstanceVar来确定的。所以我们需要在log4js的配置中增加这两个配置。

1
2

### !!!!!修正

pm2 install pm2-intercom

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

//process-dev.json
{
"apps": [
{
"name": "buran-api-dev",
"script": "index.js",
"watch": true,
"cwd":"./",
"ignore_watch" : ["jobs/*","node_modules/*","public"],
"instances" : 4,
"instance_var": "INSTANCE_ID", !!这里!!
"exec_mode" : "cluster",
"merge_logs": true,
"autorestart": true,
"log_date_format": "YYYY-MM-DD HH:mm:ss",
"env": {
"NODE_ENV": "production"
}
}
]
}



//log4js.js
log4js.configure({
pm2: true,
pm2InstanceVar: "INSTANCE_ID", // 与pm2的instance_var对应
})

接下来就完全ok了。 剩下的就交给运维同学,告诉他日志生成的目录,他来进行监听上传