自定義Egg.js的請求級別日誌

蘇格團隊發表於2018-11-03
  • 蘇格團隊
  • 作者:MaxPan
  • 交流QQ群:855833773

背景

組織為了更好的對各個業務的請求日誌進行統一的分析,制定了統一的日誌列印規範,比如:

[time][processId][traceId][userid] Hello World....複製程式碼

統一格式之後,業務現有業務的日誌工具列印出來的格式是無法滿足該規範的,所以我們需要對此進行改造。

我們前端目前Node中間層使用的框架是Egg.js,所以下文講述下如何在Egg.js上自定義請求日誌格式。

開始動手

Egg.js中自帶了三種logger,分別是

  • Context Logger
  • App Logger
  • Agent Logger

Context Logger主要是用來記錄請求相關的日誌。每行日誌都會在開頭自動的記錄當前請求的一些資訊,比如時間、ip、請求url等等。

App Logger用於記錄應用級別的日誌,比如程式啟動日誌。

Agent Logger用於記錄多程式模式執行下的日誌。

我們想自定義請求級別的日誌,那重點就要從Context Logger去研究怎麼做。最理想的方案就是,Context Logger本身支援配置化的自定義格式,通過在egg.js的config配置檔案中,通過傳入formatter的引數就能自定義。

//config.default.jsexports.customLogger = { 
log: {
file: 'appname.log', formatter: (message)=>
{
return `${message.time
}
${message.processid
}
`

}
}
}複製程式碼

但不久我們發現這條路走不通,設定了這個formatter並不起作用。從Context Logger的原始碼中,我們發現的端倪context_logger.js

[ 'error', 'warn', 'info', 'debug' ].forEach(level =>
{
const LEVEL = level.toUpperCase();
ContextLogger.prototype[level] = function() {
const meta = {
formatter: contextFormatter, paddingMessage: this.paddingMessage,
};
this._logger.log(LEVEL, arguments, meta);

};

});
module.exports = ContextLogger;
function contextFormatter(meta) {
return meta.date + ' ' + meta.level + ' ' + meta.pid + ' ' + meta.paddingMessage + ' ' + meta.message;

}複製程式碼

在原始碼中我們可以看到,formatter引數已經被內部的一個自定義格式化函式覆蓋了,配置中寫的是不會啟作用的。

此路不通,只能嘗試自己實現logger去解決。自己實現我們需要考慮一些點,比如:

  • 日誌要寫到檔案中,錯誤日誌單獨寫一個檔案
  • 需要能按天或按小時切割日誌
  • IO效能

如果這些都自己實現的話,那就太麻煩了。好在瞭解到Egg的這幾個logger都是基於egg-loggeregg-logrotator去實現的,所以我們可以站在巨人的肩膀上搞事情。

Context Logger是基於egg-loggerFileTransport類去進行檔案落地的,同時FileTransport也預設配置了egg-logrotator的日誌拆分。所以,我們只需要繼承FileTransport類,實現介面就可以了,程式碼如下:

//CoustomTransport.jsconst FileTransport = require('egg-logger').FileTransport;
const moment = require('moment');
class CoustomTransport extends FileTransport {
constructor(options, ctx) {
super(options);
this.ctx = ctx;

} log(level, args, meta) {
const prefixStr = this.buildFormat(level);
for (let i in args) {
if (args.hasOwnProperty(i)) {
if (parseInt(i, 10) === 0) {
args[i] = `${prefixStr
}
${args[i]
}
`
;

} if (parseInt(i, 10) === args.length - 1) {
args[i] += '\n';

}
}
} super.log(level, args, meta);

} buildFormat(level) {
const timeStr = `[${moment().format('YYYY-MM-DD HH:mm:ss.SSS')
}
]`
;
const threadNameStr = `[${process.pid
}
]`
;
const urlStr = `[${this.ctx.request.url
}
]`
return `${timeStr
}
${threadNameStr
}
${urlStr
}
`
;

} setUserId(userId) {
this.userId = userId;

}
}module.exports = CoustomTransport;
複製程式碼

實現CoustomTransport類後,我們就可以初始化logger

//CustomLogger.jsconst Logger = require('egg-logger').Logger;
const CoustomTransport = require('./CoustomTransport.js');
const logger = new Logger();
logger.set('file', new CoustomTransport({
level: 'INFO', file: 'app.log'
}));
module.exports = logger;
複製程式碼

我們通過 logger.info(‘Hello World’)去列印日誌,格式則顯示為我們自定義的格式。

到這,自定義日誌格式解決了,那我們如何獲取每次請求的資訊呢?這裡就要藉助Egg.js框架對Context的擴充套件功能, Context是請求級別的物件,我們在Context的原型上擴充套件方法可以拿到該物件帶有的每次請求的資訊。

//CustomLogger.jsconst Logger = require('egg-logger').Logger;
const CoustomTransport = require('./CoustomTransport.js');
module.exports = function(ctx){
const logger = new Logger();
logger.set('file', new CoustomTransport({
level: 'INFO', file: 'app.log'
}, ctx));
return logger;

};
// app/extend/context.js/** Context物件擴充套件* */const Logger = require('egg-logger').Logger;
const CoustomTransport = require('./CoustomTransport');
const CustomLogger = require('./CustomLogger');
module.exports = {
get swLog() {
return CustomLogger(this);

}
};
複製程式碼

呼叫

// app/controller/home.jsmodule.exports = app =>
{
class HomeController extends app.Controller {
async index() {
this.ctx.swLog.info('Hello World');

}
} return HomeController;

};
複製程式碼

結果

[2018-11-02 19:25:09.665][22896][/] Hello World複製程式碼

到此,我們就能完整的自定義請求級別的日誌了。

來源:https://juejin.im/post/5bdcfd1f518825171b2d820d

相關文章