• Scotland team
  • Author: MaxPan
  • Communication QQ group: 855833773

background

To better analyze the request logs of various services in a unified manner, the organization has developed unified log printing specifications. For example:

[time][processId][traceId][userid] Hello World....
Copy the code

Once the format was unified, the existing business logging tool could not print in a format that met the specification, so we needed to adapt it.

The framework currently used by our front-end Node middle tier is egg.js, so here’s how to customize the request log format in egg.js.

start

There are three types of loggers shipped with egg.js

  • Context Logger
  • App Logger
  • Agent Logger

The Context Logger is used to log requests. Each line of log automatically starts with some information about the current request, such as time, IP, request URL, and so on.

The App Logger is used to record application-level logs, such as program startup logs.

The Agent Logger records logs generated when the Agent runs in multi-process mode.

We want to customize request-level logging, so it’s important to figure out how to do that from the Context Logger. Ideally, the Context Logger itself supports configurable custom formats, which can be customized by passing in the Formatter parameters in the egg.js config file.

//config.default.js
exports.customLogger = {
    log: {
        file: 'appname.log'.formatter: (message) = >{
            return `${message.time}${message.processid}`}}}Copy the code

But soon we realized that the way didn’t work and setting the formatter didn’t work. Context_logger.js: context_logger.js: context_logger.js: context_logger.js: 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;
}
Copy the code

As you can see in the source code, the formatter argument is overridden by a custom formatting function, which is not used in configuration.

This road is blocked, can only try to implement logger to solve. Implement yourself we need to consider some points such as:

  • Logs are written to a file. Error logs are written to a separate file
  • Need to be able to cut logs by day or by hour
  • IO performance

It would be a lot of trouble if it all worked out by itself. The good news is that Egg’s loggers are based on egg-Loggers and egg-Logrotators, so we can stand on the shoulders of giants.

Context Logger is based on the egg-Logger FileTransport class for file landing, and FileTransport is also configured with egg-Logrotator log splitting by default. So, we just need to inherit the FileTransport class and implement the interface, the code is as follows:

//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 = ` [The ${this.ctx.request.url}] `
        return `${timeStr}${threadNameStr}${urlStr}`;
    }

    setUserId(userId) {
        this.userId = userId; }}module.exports = CoustomTransport;
Copy the code

Once we implement the CoustomTransport class, we can initialize 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;
Copy the code

We print the log via logger.info(‘Hello World’) and the format is displayed as our custom.

Now that the custom log format is out of the way, how do we get the information for each request? Context is a request-level object. We extend methods on the prototype of the Context to get information about each request that the object contains.

//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 object extends * */
const Logger = require('egg-logger').Logger;
const CoustomTransport = require('./CoustomTransport');
const CustomLogger = require('./CustomLogger');
module.exports = {
    get swLog() {
        return CustomLogger(this); }};Copy the code

call

// app/controller/home.js
module.exports = app= > {
    class HomeController extends app.Controller {
        async index() {
            this.ctx.swLog.info('Hello World'); }}return HomeController;
};
Copy the code

The results of

[the 2018-11-02 19:25:09. 665] [22896] [/] Hello WorldCopy the code

At this point, we can fully customize request-level logging.