Home Manual Reference Source Test Repository

src/config/logger.js

// Import the neccesary modules.
import ExpressWinston from "express-winston";
import fs from "fs";
import path from "path";
import sprintf from "sprintf";
import Winston from "winston";

import { tempDir } from "./constants";
import { name } from "../../package.json";

/** Class for configuring logging. */
export default class Logger {

  /**
   * Create a logger object.
   * @param {?Boolean} [verbose] - Debug mode for no output.
   * @param {?Boolean} [debug] - Debug mode for extra output.
   */
  constructor(pretty, verbose) {
    /**
     * Pretty mode.
     * @type {Boolean}
     */
    Logger._pretty = pretty;

    /**
     * Verbose mode.
     * @type {Boolean}
     */
    Logger._verbose = verbose;

     // Create the temp directory if it does not exists.
    if (!fs.existsSync(tempDir)) fs.mkdirSync(tempDir);

    /**
     * The log levels Winston will be using.
     * @type {Object}
     */
    Logger._levels = {
      error: 0,
      warn: 1,
      info: 2,
      debug: 3
    };

    if (Logger._pretty) {
      /**
       * The Winston instance.
       * @external {Winston} https://github.com/winstonjs/winston
       */
      Logger.logger = new Winston.Logger({
        transports: [
          new Winston.transports.Console({
            name,
            levels: Logger._levels,
            formatter: Logger._consoleFormatter,
            handleExceptions: true,
            prettyPrint: true
          }),
          new Winston.transports.File({
            filename: path.join(tempDir, `${name}.log`),
            json: false,
            level: "warn",
            formatter: Logger._fileFormatter,
            maxsize: 5242880,
            handleExceptions: true
          })
        ],
        exitOnError: false
      });

      /**
       * The Express Winston instance.
       * @external {ExpressWinston} http://bithavoc.io/express-winston/
       */
      Logger.expressLogger = new ExpressWinston.logger({
        winstonInstance: Logger.logger,
        expressFormat: true
      });
    }

    // Create the logger object.
    Logger._createLogger();
  }

  /**
   * Check if the message is empty and replace it with the meta.
   * @param {Object} args - Arguments passed by Winston.
   * @returns {Object} - Formatter arguments passed by Winston.
   */
  static _checkEmptyMessage(args) {
    if (args.message === "" && Object.keys(args.meta).length !== 0)
      args.message = JSON.stringify(args.meta);

    return args;
  }

  /**
   * Get the color of the output based on the log level.
   * @param {String} level - The log level.
   * @returns {String} - A color based on the log level.
   */
  static _getLevelColor(level) {
    switch (level) {
    case "error":
      return "\x1b[31m";
    case "warn":
      return "\x1b[33m";
    case "info":
      return "\x1b[36m";
    case "debug":
      return "\x1b[34m";
    default:
      return "\x1b[36m";
    }
  }

  /**
   * Formatter function which formats the output to the console.
   * @param {Object} args - Arguments passed by Winston.
   * @returns {String} - The formatted message.
   */
  static _consoleFormatter(args) {
    args = Logger._checkEmptyMessage(args);
    const color = Logger._getLevelColor(args.level);

    return sprintf(`\x1b[0m[%s] ${color}%5s:\x1b[0m %2s/%d: \x1b[36m%s\x1b[0m`,
      new Date().toISOString(), args.level.toUpperCase(), name,
      process.pid, args.message);
  }

  /**
   * Formatter function which formate the output to the log file.
   * @param {Object} args - Arguments passed by Winston.
   * @returns {String} - The formatted message.
   */
  static _fileFormatter(args) {
    args = Logger._checkEmptyMessage(args);
    return JSON.stringify({
      name,
      pid: process.pid,
      level: args.level,
      msg: args.message,
      time: new Date().toISOString()
    });
  }

  /**
   * Function to create a global logger object based on the properties of the Logger class.
   */
  static _createLogger() {
    if (!global.logger) global.logger = {};

    Object.keys(Logger._levels).map(level => {
      if (Logger._pretty) {
        global.logger[level] = msg => {
          if (!Logger._verbose) Logger.logger[level](msg);
        };
      } else {
        global.logger[level] = msg => {
          if (!Logger._verbose) console[level](msg);
        };
      }
    });
  }

}