一个node系统的日志管理

ZMC 2019-06-28

已经很久没有学习了,趁需求不饱和,想通过学习组里的成熟的node系统,模仿搭建一个“健全”的node系统。

一般第一步肯定是怎么配置基础信息,让这个系统跑起来。可是。。。刚好我不会日志管理,想研究下怎么打logger,所以。。第一篇我就来写,这个系统的日志管理怎么做。我用的node框架是express,日志管理工具是log4js。

什么时候什么信息需要打日志?

我总结了下几个可能需要打日志的信息,可根据实际情况增删
1、前端的请求。(为了方便排查问题)
2、返回的响应。
3、系统间的交互行为。一个成熟的项目,应该不会只有一个node系统。我觉得node更多充当一个中间层,如果node能处理的问题可以node处理不麻烦java童鞋,但一些不适合node处理比如CPU密集型或逻辑复杂的肯定交给java童鞋处理会比较好。虽然多了一次http请求,但是这些时间其实是可以忽略不计的。所以在node调用各个系统接口什时,我觉得还是有必要打印body和response。除了利于排查问题外,还能关键时候不背锅。
3、其他的手动logger。

怎么用?

1、一个req请求贯穿整个请求了。前端的请求和返回的响应都跟req请求相关,如果把这个log4js挂载在req上,那么我们使用起来就很方便了。express的中间件可以帮我们实现这个功能。使用方式:req.logger.info(msg)
2、至于node系统与其他java系统交互。一个成熟的系统,肯定会把这个请求交互封装成一个class,所以只要我们在这个class的request方法打logger,就可以实现一个地方写,每个请求都自动打logger,对使用者来说无感且方便。

logger的分类

log4js支持ALLTRACEDEBUGINFOWARNERRORFATALOFF8种,但一般使用info和error两种。
虽然同一个文件可以存不同类型的日志,但把info类型和error类型分开两个文件存,有几个好处:
1、可以对error的日志类型进行监控,及时报警
2、存储的时间可以有所不同,info类型存近15天,error类型存近1个月。
3、如果node系统与多个系统都有交互,比如A系统是跟账号相关的功能、B系统与文章相关的功能、C系统与商品相关的功能等等,这时候也可以根据系统对日志进行分类:A系统的日志在一个文件夹,B系统的日志一个文件夹。而且根据日志量,分不同的年月日时分来存。
把日志分类,都是为了利于排查问题!
日志文件是以下的结构:

systemA
 --error
   --2018-09-26.log
 --info
   --2018-09-26.log
systemB
 --error
   --2018-09-26.log
 --info
   --2018-09-26-17.log
   --2018-09-26-18.log

实现

说了这么多,马上来实现了
先把log4js配置好

//simpleType.js  这个js定义了有哪几个系统类型(就是有哪些文件夹)
module.exports = [
    'systemA',
    'systemB',
    'systemC',
    'systemD'
]
//logger.js  这个js初始化了配置并初始化
const path = require('path')
const fs = require('fs')
const log4js = require('log4js')
const category = require('./simpleTypes')

let logger_conf = {
    appenders: {
        console: {
            type: 'console'
        }
    },
    replaceConsole: true,//控制台日志
    categories: {
        default: {
            appenders: ['console'],
            level: 'info'
        }
    }
}

const DEFAULT_PATTERN = 'yyyy-MM-dd-hh.log'

if(process.env.UAE_MODE) {  //生产环境才有日志文件
    category.forEach(c => {
        let dirPath = path.join(__dirname, `../logs/${c}`)
        if(!fs.existsSync(dirPath)) fs.mkdirSync(dirPath);
        let infoPath = path.join(dirPath, 'info/');
        let errorPath = path.join(dirPath, 'error/');
        ['Info', 'Error'].forEach(type => {  //其实这里的配置我觉得有点毛病,请大神指出
            logger_conf.appenders[`${c}${type}`] = {
                type: 'dateFile',
                pattern: DEFAULT_PATTERN,
                filename: infoPath,
                alwaysIncludePattern: true,
                category: `${c}${type}`
            }
            logger_conf.categories[`${c}${type}`] = {
                appenders: [`${c}${type}`, 'console'],
                level: type.toLowerCase()
            }
        })
    })
}
if(logger_conf.appenders) {
    for(var key in logger_conf.appenders) {
        if(logger_conf.appenders[key].filename)
            checkFile(logger_conf.appenders[key].filename) //一定存在文件夹,不然会出错
    }
}
log4js.configure(logger_conf)

function checkFile(dir) {
    if(!fs.existsSync(dir)){
        fs.mkdirSync(dir);
    }
}
module.exports = log4js;  //对外暴露一个log4js实例

配置好,那就是使用了
首先是req的挂载。使用方式:req.systemAInfo.info('req logger msg')

//middleware/logger.js  middleware文件夹专门存放中间件,后续文章会讲
const log4js = require('../logger/logger')
const simpleTypes = require('../logger/simpleTypes')

module.exports = function(req) {
    simpleTypes.forEach(system => {
        ['Info', 'Error'].forEach(type => {
            var log = `${system}${type}`
            req[log] = log4js.getLogger(log)
        })
    })
}

//index.js
app.use('*', function(req, res, next) {
    reqLogger(req)   //通过express的中间件对req挂载
    next()
})

至于系统层级的,每个系统的class都继承一个Base class,在Base class里实现

// Base.js
const request = require('request')
const log4js = require('../logger/logger')

module.exports = class Base {
    constructor(id) {
        this.id = id;
    }

    request(opts, cb) {
        let infoLogger = log4js.getLogger(`${this.id}Info`)
        let errorLogger = log4js.getLogger(`${this.id}Error`)

        opts = this._requestFilter(opts)//  各个系统鉴权

        let body = JSON.stringify(opts)
        infoLogger.info(body)
        request(opts, (err, res, body) => {
            if(err) errorLogger.error(JSON.stringify(err))
            else if(body && body.error) errorLogger.error(JSON.stringify(body.error))
            else{
                infoLogger.info(JSON.stringify(body))
                cb(err, body)
            }    
        })
    }

    _requestFilter(opts) {
        return JSON.parse(JSON.stringify(opts))
    }
}

//systemA.js
const Base = require('./Base')
class systemA extends Base {
    constructor() {
        super('systemA')
    }

    _requestFilter() {
        //systemA的鉴权,后面文章   
    }
}
mudole.exports = systemA

好了,一个日志管理就初成型了

相关推荐