Home Manual Reference Source Test

src/lib/util/Logger.js

import readline from 'readline';
import { Console } from 'console';
import gulplog from 'gulplog';
import chalk from 'chalk';
import tildify from 'tildify';

const logConsole = new Console(process.stdout, process.stderr);

/**
 * Formats strings to be used in the {@link Logger}.
 */
export class LogFormat {
  /**
   * Formats a string to represent a path.
   * @param {string} path The path to format.
   * @return {string} The formatted string.
   */
  static path(path) {
    return chalk.magenta(tildify(path));
  }

  /**
   * Formats a string to represent a command.
   * @param {string} command The command to format.
   * @return {string} The formatted string.
   */
  static command(command) {
    return chalk.bold(command);
  }

  /**
   * Formats a string to represent a value. Use this format for files, module names, etc.
   * @param {string} value The value to format.
   * @return {string} The formatted string.
   */
  static value(value) {
    return chalk.cyan(value);
  }

  /**
   * Formats a string to represent a number. Use this format for times, counts, etc.
   * @param {string} number The value to format.
   * @return {string} The formatted string.
   */
  static number(number) {
    return chalk.magenta(number);
  }
}

/**
 * A logger used in all console outputs.
 * **Should never be instantiated.**
 * Log levels, `--silent`-flags etc. are handled automatically by {@link gulplog}.
 */
export default class Logger {
  /**
   * An instance of {@link chalk}.
   * @type {chalk}
   */
  static get colors() {
    return chalk;
  }

  /**
   * An instance of {@link LogFormat}.
   * @type {LogFormat}
   */
  static get format() {
    return LogFormat;
  }

  /**
   * The prefix added to each log. Should always equal fancy-log's prefix.
   * @type {string}
   * @see {@link gulplog}
   */
  static get prefix() {
    function pad(val) {
      return `00${val}`.slice(-2);
    }

    const now = new Date();
    const timestamp = [pad(now.getHours()), pad(now.getMinutes()), pad(now.getSeconds())].join(':');

    return `[${chalk.gray(timestamp)}]`;
  }

  /**
   * Print debug messages.
   * @param {...string} message The message(s) to print.
   */
  static debug(...message) {
    gulplog.debug(...message);
  }

  /**
   * Print regular logs.
   * @param {...string} message The message(s) to print.
   */
  static info(...message) {
    gulplog.info(...message);
  }

  /**
   * Print warnings.
   * @param {...string} message The message(s) to print.
   */
  static warn(...message) {
    gulplog.warn(...message);
  }

  /**
   * Print error messages.
   * @param {...string} message The message(s) to print.
   */
  static error(...message) {
    gulplog.error(...message);
  }

  /**
   * The log types available.
   * @type {string[]}
   */
  static get types() {
    return ['error', 'warn', 'info', 'debug'];
  }

  /**
   * Apply options to the logger.
   * **Should only be called once.**.
   * @param {Object} options Options passed to {@link gulplog}.
   */
  static applyOptions(options) {
    if (options.tasksSimple || options.silent || options.logLevel === 0) {
      gulplog.on('error', () => {});
      return;
    }

    /**
     * The log types handled.
     * @type {Map<String, Boolean>}
     */
    this._handled = {};

    this.types
      .filter((item, i) => {
        const handle = i < options.logLevel;

        this._handled[item] = handle;

        return handle;
      })
      .forEach((level) =>
        gulplog.on(level, (...args) => {
          logConsole[level === 'error' ? 'error' : 'info'](...[this.prefix].concat(args));
        })
      );
  }

  /**
   * Pipes a readable stream and logs the last line of each chunk processed.
   * @param {node.stream.Readable} stream The stream to pipe.
   */
  static pipeLastLine(stream) {
    let loggedBefore = false;

    stream
      .on('data', (d) => {
        const lines = d
          .toString()
          .split('\n')
          .filter((l) => l.trim() !== '');

        if (loggedBefore && this._handled.info) {
          readline.moveCursor(process.stdout, 0, -1);
          readline.clearLine(process.stdout);
        }

        Logger.info(lines[lines.length - 1]);
        loggedBefore = true;
      })
      .on('end', () => {
        if (loggedBefore && this._handled.info) {
          readline.moveCursor(process.stdout, 0, -1);
          readline.clearLine(process.stdout);
          readline.cursorTo(process.stdout, 0);
        }
      });
  }
}