一个node系统的日志管理
已经很久没有学习了,趁需求不饱和,想通过学习组里的成熟的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支持ALL
、TRACE
、DEBUG
、INFO
、WARN
、ERROR
、FATAL
、OFF
8种,但一般使用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
好了,一个日志管理就初成型了