关于自定义Egg.js的请求级别日志详解


Posted in Javascript onDecember 12, 2018

Egg.js 是什么?

Egg.js 为企业级框架和应用而生,我们希望由 Egg.js 孕育出更多上层框架,帮助开发团队和开发人员降低开发和维护成本。

注:Egg.js 缩写为 Egg

背景

组织为了更好的对各个业务的请求日志进行统一的分析,制定了统一的日志打印规范,比如:

[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.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去解决。自己实现我们需要考虑一些点,比如:

  • 日志要写到文件中,错误日志单独写一个文件
  • 需要能按天或按小时切割日志
  • IO性能

如果这些都自己实现的话,那就太麻烦了。好在了解到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;

我们通过 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

到此,我们就能完整的自定义请求级别的日志了。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
网站导致浏览器崩溃的原因总结(多款浏览器) 推荐
Apr 15 Javascript
来自qq的javascript面试题
Jul 24 Javascript
JS计算网页停留时间代码
Apr 28 Javascript
JS简单实现动画弹出层效果
May 05 Javascript
Jquery日历插件制作简单日历
Oct 28 Javascript
Bootstrap每天必学之js插件
Nov 30 Javascript
基于JavaScript代码实现随机漂浮图片广告
Jan 05 Javascript
利用原生JS与jQuery实现数字线性变化的动画
Feb 24 Javascript
微信小程序wx.previewImage预览图片实例详解
Dec 07 Javascript
Vue项目中使用jsonp抓取跨域数据的方法
Nov 10 Javascript
解决React在安装antd之后出现的Can't resolve './locale'问题(推荐)
May 03 Javascript
Node.js API详解之 os模块用法实例分析
May 06 Javascript
JS/HTML5游戏常用算法之碰撞检测 像素检测算法实例详解
Dec 12 #Javascript
d3绘制基本的柱形图的实现代码
Dec 12 #Javascript
JS/HTML5游戏常用算法之碰撞检测 地图格子算法实例详解
Dec 12 #Javascript
JS/HTML5游戏常用算法之追踪算法实例详解
Dec 12 #Javascript
js使用swiper实现层叠轮播效果实例代码
Dec 12 #Javascript
如何制作一个Node命令行图像识别工具
Dec 12 #Javascript
JS遍历JSON数组及获取JSON数组长度操作示例【测试可用】
Dec 12 #Javascript
You might like
PHP第一季视频教程(李炎恢+php100 不断更新)
2011/05/29 PHP
浅谈PHP eval()函数定义和用法
2016/06/21 PHP
PHP设计模式之工厂模式定义与用法详解
2018/04/03 PHP
PHP使用XMLWriter读写xml文件操作详解
2018/07/31 PHP
thinkphp5.1框架模板赋值与变量输出示例
2020/05/25 PHP
HTML-CSS群中单选引发的“事件”
2007/03/05 Javascript
在IE中调用javascript打开Excel的代码(downmoon原作)
2007/04/02 Javascript
javascript 打开页面window.location和window.open的区别
2010/03/17 Javascript
多次注册事件会导致一个事件被触发多次的解决方法
2013/08/12 Javascript
vue2中的keep-alive使用总结及注意事项
2017/12/21 Javascript
Javascript实现异步编程的过程
2018/06/18 Javascript
详解vue-router传参的两种方式
2018/09/10 Javascript
vue实现局部刷新的实现示例
2019/04/16 Javascript
JavaScript(js)处理的HTML事件、键盘事件、鼠标事件简单示例
2019/11/19 Javascript
纯js+css实现仿移动端淘宝网站的弹出详情框功能
2019/12/29 Javascript
vue监听键盘事件的相关总结
2021/01/29 Vue.js
[03:49]显微镜下的DOTA2第十五期—VG登基之路完美团
2014/06/24 DOTA
go语言计算两个时间的时间差方法
2015/03/13 Python
Python正则替换字符串函数re.sub用法示例
2017/01/19 Python
Python中的浮点数原理与运算分析
2017/10/12 Python
分享一个简单的python读写文件脚本
2017/11/25 Python
Python实现图片拼接的代码
2018/07/02 Python
python3中函数参数的四种简单用法
2018/07/09 Python
python获取txt文件词向量过程详解
2019/07/05 Python
python通过函数名调用函数的几种场景
2020/09/23 Python
简单几步用纯CSS3实现3D翻转效果
2019/01/17 HTML / CSS
毕业生怎样写好自荐信
2013/11/11 职场文书
老师给学生的表扬信
2014/01/17 职场文书
即将毕业大学生自荐信
2014/01/24 职场文书
核心价值观演讲稿
2014/05/13 职场文书
征用土地赔偿协议书
2014/09/26 职场文书
全国法制宣传日活动总结2014
2014/11/01 职场文书
教师岗位职责
2015/02/03 职场文书
Django cookie和session的应用场景及如何使用
2021/04/29 Python
Python pandas读取CSV文件的注意事项(适合新手)
2021/06/20 Python
Vue router配置与使用分析讲解
2022/12/24 Vue.js