Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
@supertenant/collector / src / logger.js
Size: Mime:
// (c) Copyright 2023 Supertenant Ltd. - all rights reserved.
// See LICENSE file in project root for license terms.

/* eslint-disable dot-notation */

'use strict';

/** @type number */
let threadId = 0;
try {
  threadId = require('worker_threads').threadId;
} catch (ignored) {
  // We are apparently running in a Node.js version that does not have worker threads yet, thus we are on the main
  // thread (0).
}

const consts = require('@supertenant/superconsts');
const bunyan = require('bunyan');
const { logger, superbrainLogger } = require('@supertenant/core');

/** @type {bunyan | import('@supertenant/core/src/logger').GenericLogger} */
let parentLogger = null;
/** @type {Object.<string, (logger: import('@supertenant/core/src/logger').GenericLogger) => *>} */
const registry = {};

/** @type {{[id: number]: number}} */
const bunyanToSuperbrainLogLevel = {};
bunyanToSuperbrainLogLevel[bunyan.TRACE] = consts.BrainLogLevel.Trace;
bunyanToSuperbrainLogLevel[bunyan.DEBUG] = consts.BrainLogLevel.Debug;
bunyanToSuperbrainLogLevel[bunyan.INFO] = consts.BrainLogLevel.Info;
bunyanToSuperbrainLogLevel[bunyan.WARN] = consts.BrainLogLevel.Warn;
bunyanToSuperbrainLogLevel[bunyan.ERROR] = consts.BrainLogLevel.Error;
bunyanToSuperbrainLogLevel[bunyan.FATAL] = consts.BrainLogLevel.Fatal;

/**
 *
 * @param {import('@supertenant/core').superbrainLogger} [sbLogger]
 * @returns {import('bunyan').WriteFn}
 */
function superbrainToBunyanWriteFn(sbLogger) {
  function SuperBrainLogStream() {}
  /**
   * @param {*} [record]
   * @returns {void}
   */
  SuperBrainLogStream.prototype.write = function (record) {
    try {
      if (record.msg) {
        const level = bunyanToSuperbrainLogLevel[record.level] || consts.BrainLogLevel.Info;
        sbLogger(level, JSON.stringify(record));
      }
    } catch (e) {
      // skip this - bunyan probably complained about this record too.
    }
  };
  return new SuperBrainLogStream();
}

/**
 * @param {import('./util/normalizeConfig').CollectorConfig} config
 * @param {function(typeof consts.BrainLogLevel, string): void} sbLogger
 * @param {boolean} [isReInit]
 */
exports.init = function init(config, sbLogger, isReInit) {
  if (config.logger && typeof config.logger.child === 'function') {
    // A bunyan or pino logger has been provided via config. In either case we create a child logger directly under the
    // given logger which serves as the parent for all loggers we create later on.
    parentLogger = config.logger.child({
      module: 'supertenant-nodejs-logger-parent',
      __in: 1
    });
  } else if (config.logger && hasLoggingFunctions(config.logger)) {
    // A custom non-bunyan logger has been provided via config. We use it as is.
    parentLogger = config.logger;
  } else {
    // No custom logger has been provided via config, we create a new bunyan logger as the parent logger for all loggers
    // we create later on.
    parentLogger = bunyan.createLogger({
      name: '@supertenant/collector',
      thread: threadId,
      streams: [{
        level: bunyan.TRACE,
        stream: superbrainToBunyanWriteFn(sbLogger),
        type: 'raw'
      }],
      __in: 1
    });
  }

  if (isBunyan(parentLogger)) {
    // in case we are using a bunyan logger we also forward logs to the agent
    if (process.env['SUPERTENANT_SUPERBRAIN_DEBUG_MODE']) {
      /** @type {bunyan} */ (parentLogger).level('debug');
    } else if (config.level) {
      /** @type {bunyan} */ (parentLogger).level(/** @type {import('bunyan').LogLevel} */ (config.level));
    } else if (process.env['SUPERTENANT_SUPERBRAIN_LOG__LEVEL']) {
      /** @type {bunyan} */ (parentLogger).level(
        /** @type {import('bunyan').LogLevel} */ (process.env['SUPERTENANT_SUPERBRAIN_LOG__LEVEL'].toLowerCase())
      );
    }
  }

  if (isReInit) {
    Object.keys(registry).forEach(loggerName => {
      const reInitFn = registry[loggerName];
      reInitFn(exports.getLogger(loggerName));
    });
    // cascade re-init to @supertenant/core
    logger.init(config);
  }
};

/**
 * @param {string} loggerName
 * @param {(logger: import('@supertenant/core/src/logger').GenericLogger) => *} [reInitFn]
 * @returns {import('@supertenant/core/src/logger').GenericLogger}
 */
exports.getLogger = function getLogger(loggerName, reInitFn) {
  if (!parentLogger) {
    exports.init({}, superbrainLogger);
  }

  let _logger;

  if (typeof parentLogger.child === 'function') {
    // Either bunyan or pino, both support parent-child relationships between loggers.
    _logger = parentLogger.child({
      module: loggerName
    });
  } else {
    // Unknown logger type (neither bunyan nor pino), we simply return the user provided custom logger as-is.
    _logger = parentLogger;
  }

  if (reInitFn) {
    if (registry[loggerName]) {
      throw new Error(`Duplicate logger name: ${loggerName}.`);
    }
    registry[loggerName] = reInitFn;
  }
  return _logger;
};

/**
 * @param {import('bunyan') | *} _logger
 * @returns {boolean}
 */
function isBunyan(_logger) {
  return _logger instanceof bunyan;
}

/**
 * @param {import('@supertenant/core/src/logger').GenericLogger | *} _logger
 * @returns {boolean}
 */
function hasLoggingFunctions(_logger) {
  return (
    typeof _logger.debug === 'function' &&
    typeof _logger.info === 'function' &&
    typeof _logger.warn === 'function' &&
    typeof _logger.error === 'function'
  );
}