- 苏格团队
- 做者:MaxPan
- 交流QQ群:855833773
组织为了更好的对各个业务的请求日志进行统一的分析,制定了统一的日志打印规范,好比:html
[time][processId][traceId][userid] Hello World....
复制代码
统一格式以后,业务现有业务的日志工具打印出来的格式是没法知足该规范的,因此咱们须要对此进行改造。前端
咱们前端目前Node中间层使用的框架是Egg.js,因此下文讲述下如何在Egg.js上自定义请求日志格式。git
Egg.js中自带了三种logger,分别是github
Context Logger主要是用来记录请求相关的日志。每行日志都会在开头自动的记录当前请求的一些信息,好比时间、ip、请求url等等。bash
App Logger用于记录应用级别的日志,好比程序启动日志。app
Agent Logger用于记录多进程模式运行下的日志。框架
咱们想自定义请求级别的日志,那重点就要从Context Logger
去研究怎么作。最理想的方案就是,Context Logger
自己支持配置化的自定义格式,经过在egg.js的config配置文件中,经过传入formatter的参数就能自定义。async
//config.default.js
exports.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去解决。本身实现咱们须要考虑一些点,好比:
若是这些都本身实现的话,那就太麻烦了。好在了解到Egg的这几个logger都是基于egg-logger
和egg-logrotator
去实现的,因此咱们能够站在巨人的肩膀上搞事情。
Context Logger
是基于egg-logger
的FileTransport
类去进行文件落地的,同时FileTransport
也默认配置了egg-logrotator
的日志拆分。因此,咱们只须要继承FileTransport
类,实现接口就能够了,代码以下:
//CoustomTransport.js
const 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.js
const 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.js
const 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.js
module.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
复制代码
到此,咱们就能完整的自定义请求级别的日志了。