Source: lib/util/Logger.js

'use strict';

const msg = require('../message');
const deprecated = require('./deprecated');

/**
 * A Logger to store log notes when running the app.
 *
 * @alias util.Logger
 */
class Logger {
  /**
   * Creates a Logger instance for the given EntityManager
   * @param {EntityManager} entityManager
   * @return {util.Logger} The created logger instance
   */
  static create(entityManager) {
    const proto = this.prototype;

    // eslint-disable-next-line no-shadow
    function Logger() {
      proto.log.apply(Logger, arguments);
    }

    Object.getOwnPropertyNames(proto).forEach((key) => {
      Object.defineProperty(Logger, key, Object.getOwnPropertyDescriptor(proto, key));
    });

    Logger.init(entityManager);

    return Logger;
  }

  /**
   * The log level which will be logged
   *
   * The log level can be one of 'trace', 'debug', 'info', 'warn', 'error'
   * @type string
   */
  get level() {
    return Logger.LEVELS[this.levelIndex];
  }

  /**
   * Sets the log level which will be logged
   * @param {string} value
   */
  set level(value) {
    const index = Logger.LEVELS.indexOf(value);
    if (index === -1) {
      throw new Error('Unknown logging level ' + value);
    }

    this.levelIndex = index;
  }

  /**
   * Logs a message in the default level 'info'
   * @param {string} message The message to log, the message string can be interpolated like the node util.format method
   * @param {...*} args The arguments used to interpolated the message string. The last param can be object which will
   * be included in the log entry
   * @return {void}
   *
   * @name log
   * @memberOf util.Logger.prototype
   * @function
   * @see https://nodejs.org/api/util.html#util_util_format_format
   */

  /**
   * Logs a message in the default level 'info'
   * @param {string} message The message to log, the message string can be interpolated like the node util.format method
   * @param {Object<string, *>} [data=null] An optional object which will be included in the log entry
   * @return {void}
   *
   * @name log
   * @memberOf util.Logger.prototype
   * @function
   * @see https://nodejs.org/api/util.html#util_util_format_format
   */

  /**
   * Logs a message with the given log level
   * @param {string} level The level used to log the message
   * @param {string} message The message to log, the message string can be interpolated like the node util.format method
   * @param {...*} args The arguments used to interpolated the message string. The last param can be object which will
   * be included in the log entry
   * @return {void}
   * @name log
   * @memberOf util.Logger.prototype
   * @function
   *
   * @see https://nodejs.org/api/util.html#util_util_format_format
   */

  /**
   * Logs a message with the given log level
   * @param {string} level The level used to log the message
   * @param {string} message The message to log, the message string can be interpolated like the node util.format method
   * @param {Object<string, *>} [data=null] An optional object which will be included in the log entry
   * @return {Promise<*>|null} A promise which resolves when the log messages was logged, or null if the log level has
   * skipped the message
   *
   * @see https://nodejs.org/api/util.html#util_util_format_format
   */
  log(/* level, message, data */) {
    const args = Array.prototype.slice.call(arguments);
    const level = Logger.LEVELS.indexOf(args[0]) === -1 ? 'info' : args.shift();

    if (this.levelIndex > Logger.LEVELS.indexOf(level)) {
      return null;
    }

    let message = typeof args[0] === 'string' ? this.format(args.shift(), args) : '[no message]';

    let data = null;
    if (args.length) {
      data = args.pop();
      if (typeof data !== 'object' || Array.isArray(data)) {
        data = { data };
      }
      if (data instanceof Error) {
        // errors aren't loggable by default, since they do not have any visible property
        data = {
          name: data.name,
          message: data.message,
          stack: data.stack,
          status: data.status,
          data: data.data,
        };
      }
    }

    if (args.length) {
      message += ', ' + args.join(', ');
    }

    return this.logJSON({
      date: new Date(),
      message,
      level,
      user: this.entityManager.me && this.entityManager.me.id,
      data,
    });
  }

  format(message, args) {
    if (args.length === 0) {
      return message;
    }

    const str = String(message).replace(Logger.FORMAT_REGEXP, (x) => {
      if (x === '%%') {
        return '%';
      }
      if (!args.length) {
        return x;
      }
      switch (x) {
        case '%s':
          return String(args.shift());
        case '%d':
          return Number(args.shift());
        case '%j':
          try {
            return JSON.stringify(args.shift());
          } catch (_) {
            return '[Circular]';
          }
        default:
          return x;
      }
    });

    return str;
  }

  init(entityManager) {
    /** @type EntityManager */
    this.entityManager = entityManager;
    this.levelIndex = 2;

    Logger.LEVELS.forEach((level) => {
      this[level] = this.log.bind(this, level);
    });
  }

  logJSON(json) {
    if (!this.entityManager.isReady) {
      return this.entityManager.ready(this.logJSON.bind(this, json));
    }

    return this.entityManager.send(new msg.CreateObject('logs.AppLog', json));
  }
}

/**
 * Log message at trace level
 * @param {string} message The message to log, the message string can be interpolated like the node util.format method
 * @param {...*} args The arguments used to interpolated the message string. The last param can be object which will
 * be included in the log entry
 * @return {Promise<*>|null} A promise which resolves when the log messages was logged, or null if the log level has
 * skipped the message
 * @function trace
 * @memberOf util.Logger.prototype
 *
 * @see https://nodejs.org/api/util.html#util_util_format_format
 */

/**
 * Log message at trace level
 * @param {string} message The message to log, the message string can be interpolated like the node util.format method
 * @param {Object<string, *>} [data=null] An optional object which will be included in the log entry
 * @return {Promise<*>|null} A promise which resolves when the log messages was logged, or null if the log level has
 * skipped the message
 * @function trace
 * @memberOf util.Logger.prototype
 *
 * @see https://nodejs.org/api/util.html#util_util_format_format
 */

/**
 * Log message at debug level
 * @param {string} message The message to log, the message string can be interpolated like the node util.format method
 * @param {...*} args The arguments used to interpolated the message string. The last param can be object which will
 * be included in the log entry
 * @return {Promise<*>|null} A promise which resolves when the log messages was logged, or null if the log level has
 * skipped the message
 * @function debug
 * @memberOf util.Logger.prototype
 *
 * @see https://nodejs.org/api/util.html#util_util_format_format
 */

/**
 * Log message at debug level
 * @param {string} message The message to log, the message string can be interpolated like the node util.format method
 * @param {Object<string, *>} [data=null] An optional object which will be included in the log entry
 * @return {Promise<*>|null} A promise which resolves when the log messages was logged, or null if the log level has
 * skipped the message
 * @function debug
 * @memberOf util.Logger.prototype
 *
 * @see https://nodejs.org/api/util.html#util_util_format_format
 */

/**
 * Log message at info level
 * @param {string} message The message to log, the message string can be interpolated like the node util.format method
 * @param {...*} args The arguments used to interpolated the message string. The last param can be object which will
 * be included in the log entry
 * @return {Promise<*>|null} A promise which resolves when the log messages was logged, or null if the log level has
 * skipped the message
 * @function info
 * @memberOf util.Logger.prototype
 *
 * @see https://nodejs.org/api/util.html#util_util_format_format
 */

/**
 * Log message at info level
 * @param {string} message The message to log, the message string can be interpolated like the node util.format method
 * @param {Object<string, *>} [data=null] An optional object which will be included in the log entry
 * @return {Promise<*>|null} A promise which resolves when the log messages was logged, or null if the log level has
 * skipped the message
 * @function info
 * @memberOf util.Logger.prototype
 *
 * @see https://nodejs.org/api/util.html#util_util_format_format
 */

/**
 * Log message at warn level
 * @param {string} message The message to log, the message string can be interpolated like the node util.format method
 * @param {...*} args The arguments used to interpolated the message string. The last param can be object which will
 * be included in the log entry
 * @return {Promise<*>|null} A promise which resolves when the log messages was logged, or null if the log level has
 * skipped the message
 * @function warn
 * @memberOf util.Logger.prototype
 *
 * @see https://nodejs.org/api/util.html#util_util_format_format
 */

/**
 * Log message at warn level
 * @param {string} message The message to log, the message string can be interpolated like the node util.format method
 * @param {Object<string, *>} [data=null] An optional object which will be included in the log entry
 * @return {Promise<*>|null} A promise which resolves when the log messages was logged, or null if the log level has
 * skipped the message
 * @function warn
 * @memberOf util.Logger.prototype
 *
 * @see https://nodejs.org/api/util.html#util_util_format_format
 */

/**
 * Log message at error level
 * @param {string} message The message to log, the message string can be interpolated like the node util.format method
 * @param {...*} args The arguments used to interpolated the message string. The last param can be object which will
 * be included in the log entry
 * @return {Promise<*>|null} A promise which resolves when the log messages was logged, or null if the log level has
 * skipped the message
 * @function error
 * @memberOf util.Logger.prototype
 *
 * @see https://nodejs.org/api/util.html#util_util_format_format
 */

/**
 * Log message at error level
 * @param {string} message The message to log, the message string can be interpolated like the node util.format method
 * @param {Object<string, *>} [data=null] An optional object which will be included in the log entry
 * @return {Promise<*>|null} A promise which resolves when the log messages was logged, or null if the log level has
 * skipped the message
 * @function error
 * @memberOf util.Logger.prototype
 *
 * @see https://nodejs.org/api/util.html#util_util_format_format
 */

Object.assign(Logger, {
  LEVELS: ['trace', 'debug', 'info', 'warn', 'error'],
  FORMAT_REGEXP: /%[sdj%]/g,
});

deprecated(Logger.prototype, '_init', 'init');
deprecated(Logger.prototype, '_format', 'format');
deprecated(Logger.prototype, '_log', 'logJSON');

module.exports = Logger;