关于自定义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 相关文章推荐
Javascript中定义方法的另类写法(批量定义js对象的方法)
Feb 25 Javascript
工作需要写的一个js拖拽组件
Jul 28 Javascript
时间戳转换为时间 年月日时间的JS函数
Aug 19 Javascript
SpringMVC restful 注解之@RequestBody进行json与object转换
Dec 10 Javascript
js仿新浪微博消息发布功能
Feb 17 Javascript
jQuery实现Select下拉列表进行状态选择功能
Mar 30 jQuery
jquery.validate表单验证插件使用详解
Jun 21 jQuery
vue首次赋值不触发watch的解决方法
Sep 11 Javascript
vue使用Font Awesome的方法步骤
Feb 26 Javascript
JavaScript多种页面刷新方法小结
Apr 04 Javascript
node.js express捕获全局异常的三种方法实例分析
Dec 27 Javascript
vue中axios封装使用的完整教程
Mar 03 Vue.js
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 miniBB中文乱码问题解决方法
2008/11/25 PHP
PHP获取当前页面完整URL的实现代码
2013/06/10 PHP
php Session存储到Redis的方法
2013/11/04 PHP
PHP实现返回JSON和XML的类分享
2015/01/28 PHP
PHP程序中的文件锁、互斥锁、读写锁使用技巧解析
2016/03/21 PHP
yii 框架实现按天,月,年,自定义时间段统计数据的方法分析
2020/04/04 PHP
javascript 数组排序函数
2009/08/20 Javascript
jQuery事件绑定.on()简要概述及应用
2013/02/07 Javascript
动态创建script标签实现跨域资源访问的方法介绍
2014/02/28 Javascript
javascript面向对象程序设计(一)
2015/01/29 Javascript
Function.prototype.apply()与Function.prototype.call()小结
2016/04/27 Javascript
AngularJS中指令的四种基本形式实例分析
2016/11/22 Javascript
js正则表达式最长匹配(贪婪匹配)和最短匹配(懒惰匹配)用法分析
2016/12/27 Javascript
关于不同页面之间实现参数传递的几种方式讨论
2017/02/13 Javascript
Vue+axios 实现http拦截及路由拦截实例
2017/04/25 Javascript
基于jQuery对象和DOM对象和字符串之间的转化实例
2017/08/08 jQuery
JavaScript实现异步图像上传功能
2018/07/12 Javascript
解决父组件将子组件作为弹窗调用只执行一次created的问题
2020/07/24 Javascript
在antd Table中插入可编辑的单元格实例
2020/10/28 Javascript
在nodejs中创建child process的方法
2021/01/26 NodeJs
自动化Nginx服务器的反向代理的配置方法
2015/06/28 Python
python操作redis方法总结
2018/06/06 Python
pyqt5中QThread在使用时出现重复emit的实例
2019/06/21 Python
通过实例了解python property属性
2019/11/01 Python
python 使用opencv 把视频分割成图片示例
2019/12/12 Python
Python实现读取并写入Excel文件过程解析
2020/05/27 Python
Django model重写save方法及update踩坑详解
2020/07/27 Python
如何在scrapy中捕获并处理各种异常
2020/09/28 Python
python+openCV对视频进行截取的实现
2020/11/27 Python
浅析几个CSS3常用功能的写法
2014/06/05 HTML / CSS
Carter’s官方旗舰店:美国受欢迎的婴童服装品牌
2018/01/21 全球购物
致跳远、跳高运动员广播稿
2014/01/09 职场文书
会计学自我鉴定
2014/02/06 职场文书
竞选文艺委员演讲稿
2014/04/28 职场文书
年底个人总结范文
2015/03/10 职场文书
装修公司工程部经理岗位职责
2015/04/09 职场文书