Egg.js 自定义请求级日志实现指南

作者:百宝袋

时间:2026-10-22

浏览:501

本文详细介绍如何在 Egg.js 框架中自定义请求级别的日志格式。通过继承 egg-logger 的 FileTransport 类,绕过原生 Context Logger 对 formatter 参数的限制,实现包含时间、进程ID、TraceId 等字段的统一日志规范。

文章目录

Egg.js 简介与背景

Egg.js 是什么?

Egg.js 是专为构建企业级应用而设计的框架,旨在通过其生态孕育更多上层框架,从而降低开发团队和个人的开发与维护成本。

注:Egg.js 缩写为 Egg

背景

为了统一分析各业务的请求日志,组织制定了标准化的日志打印规范,例如:

[time][processId][traceId][userid] Hello World....

统一格式后,现有业务使用的日志工具无法直接满足该规范,因此需要进行改造。

前端 Node 中间层目前采用 Egg.js 框架,下文将阐述如何在 Egg.js 中自定义请求日志格式。

Egg.js 内置 Logger 分析

开始动手

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}` 
  }
 }
}

自定义 Logger 实现方案

但随后发现此路不通,设置 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

至此,我们便能完整实现请求级别的日志自定义。

总结

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

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章