已經好久沒有學習了,趁需求不飽和,想經過學習組裏的成熟的node系統,模仿搭建一個「健全」的node系統。前端
通常第一步確定是怎麼配置基礎信息,讓這個系統跑起來。但是。。。恰好我不會日誌管理,想研究下怎麼打logger,因此。。第一篇我就來寫,這個系統的日誌管理怎麼作。我用的node框架是express,日誌管理工具是log4js。java
我總結了下幾個可能須要打日誌的信息,可根據實際狀況增刪
一、前端的請求。(爲了方便排查問題)
二、返回的響應。
三、系統間的交互行爲。一個成熟的項目,應該不會只有一個node系統。我以爲node更多充當一箇中間層,若是node能處理的問題能夠node處理不麻煩java童鞋,但一些不適合node處理好比CPU密集型或邏輯複雜的確定交給java童鞋處理會比較好。雖然多了一次http請求,可是這些時間實際上是能夠忽略不計的。因此在node調用各個系統接口什時,我以爲仍是有必要打印body和response。除了利於排查問題外,還能關鍵時候不背鍋。
三、其餘的手動logger。node
一、一個req請求貫穿整個請求了。前端的請求和返回的響應都跟req請求相關,若是把這個log4js掛載在req上,那麼咱們使用起來就很方便了。express的中間件能夠幫咱們實現這個功能。使用方式:req.logger.info(msg)
二、至於node系統與其餘java系統交互。一個成熟的系統,確定會把這個請求交互封裝成一個class,因此只要咱們在這個class的request方法打logger,就能夠實現一個地方寫,每一個請求都自動打logger,對使用者來講無感且方便。express
log4js支持ALL
、TRACE
、DEBUG
、INFO
、WARN
、ERROR
、FATAL
、OFF
8種,但通常使用info和error兩種。
雖然同一個文件能夠存不一樣類型的日誌,但把info類型和error類型分開兩個文件存,有幾個好處:
一、能夠對error的日誌類型進行監控,及時報警
二、存儲的時間能夠有所不一樣,info類型存近15天,error類型存近1個月。
三、若是node系統與多個系統都有交互,好比A系統是跟帳號相關的功能、B系統與文章相關的功能、C系統與商品相關的功能等等,這時候也能夠根據系統對日誌進行分類:A系統的日誌在一個文件夾,B系統的日誌一個文件夾。並且根據日誌量,分不一樣的年月日時分來存。
把日誌分類,都是爲了利於排查問題!
日誌文件是如下的結構:app
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
好了,一個日誌管理就初成型了ui