acuminous/tripitaka

View on GitHub
lib/Logger.js

Summary

Maintainability
A
25 mins
Test Coverage
A
100%
const Level = require('./Level');
const { context, human, json, timestamp } = require('./processors');
const { stream } = require('./transports');

module.exports = class Logger {
  constructor(params = {}) {
    const {
      processors = [context(), timestamp(), process.env.NODE_ENV === 'production' ? json() : human()],
      transports = [stream()],
      level = Level.INFO,
    } = params;

    this._processors = processors;
    this._transports = transports;
    this._threshold = level;
    this._disabled = false;
    this._pendingTransports = new Set();

    Level.decorate(this);
  }

  enable() {
    this._disabled = false;
  }

  disable() {
    this._disabled = true;
  }

  log(level, ...args) {
    if (this._disabled || !level.satisfies(this._threshold)) return;

    let message;
    let ctx = {};
    if (args[1] !== undefined) {
      [message, ctx] = args;
    } else if (typeof args[0] === 'string') {
      message = args[0];
    } else {
      ctx = args[0] || {};
    }

    const record = this._process({ level, message, ctx });
    this._output({ level, record });
  }

  waitForTransports(timeout) {
    return new Promise((resolve, reject) => {
      const timerId = timeout && setTimeout(() => {
        reject(new Error('Timedout waiting for transports to finish'));
      });
      const waitUntilDrained = () => {
        if (this._pendingTransports.size > 0) return setTimeout(waitUntilDrained, 10).unref();
        clearTimeout(timerId);
        resolve();
      };
      waitUntilDrained();
    });
  }

  _process({ level, message, ctx }) {
    return this._processors.reduce(
      (record, processor) => {
        const result = processor({ level, message, ctx, record });
        return result || record;
      },
      { level: level.name, message },
    );
  }

  _output(level, record) {
    this._transports.forEach((transport) => {
      const result = transport(level, record);
      if (result instanceof Promise) {
        this._pendingTransports.add(result);
        result.then(() => this._pendingTransports.delete(result));
      }
    });
  }
};