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

  • 蘇格團隊
  • 做者:MaxPan
  • 交流QQ羣:855833773

背景

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

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

統一格式以後,業務現有業務的日誌工具打印出來的格式是沒法知足該規範的,因此咱們須要對此進行改造。前端

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

開始動手

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

  • Context Logger
  • App Logger
  • Agent Logger

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去解決。本身實現咱們須要考慮一些點,好比:

  • 日誌要寫到文件中,錯誤日誌單獨寫一個文件
  • 須要能按天或按小時切割日誌
  • IO性能

若是這些都本身實現的話,那就太麻煩了。好在瞭解到Egg的這幾個logger都是基於egg-loggeregg-logrotator去實現的,因此咱們能夠站在巨人的肩膀上搞事情。

Context Logger是基於egg-loggerFileTransport類去進行文件落地的,同時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
複製代碼

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

相關文章
相關標籤/搜索