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/core / src / logger.js
Size: Mime:
// (c) Copyright 2023 Supertenant Ltd. - all rights reserved.
// See LICENSE file in project root for license terms.

'use strict';

const superbrain = require('@supertenant/superbrain');
const consts = require('@supertenant/superconsts');

/** @type {*} */
let parentLogger = null;
/** @type {*} */
const registry = {};
/** @type {*} */
const dedupKeys = {};
/** @type {*} */
let dedupErrorLogger = null;
const DedupLogPeriod = 60*60*1000; // every 1 hour

/**
 * @typedef {Object} GenericLogger
 * @property {(...args: *) => void} [trace]
 * @property {(...args: *) => void} debug
 * @property {(...args: *) => void} info
 * @property {(...args: *) => void} warn
 * @property {(...args: *) => void} error
 * @property {*} [child]
 */

/** @type {GenericLogger} */
const consoleLogger = {
  /* eslint-disable no-console */
  debug: function(...args) {
    console.log(...args);
    // TODO supertenant: find stack traces and print them
    superbrain.log(consts.BrainLogLevel.Debug, args.join(' '));
  },
  info: function(...args) {
    console.log(...args);
    // TODO supertenant: find stack traces and print them
    superbrain.log(consts.BrainLogLevel.Info, args.join(' '));
  },
  warn: function(...args) {
    console.warn(...args);
    // TODO supertenant: find stack traces and print them
    superbrain.log(consts.BrainLogLevel.Warn, args.join(' '));
  },
  error: function(...args) {
    console.error(...args);
    // TODO supertenant: find stack traces and print them
    superbrain.log(consts.BrainLogLevel.Error, args.join(' '));
  }
  /* eslint-enable no-console */
};

/**
 * @typedef {Object} LoggerConfig
 * @property {*} [logger]
 */

/**
 * @param {LoggerConfig} config
 */
exports.init = function init(config = {}) {
  if (
    config.logger &&
    typeof config.logger.child === 'function' &&
    config.logger.fields &&
    config.logger.fields.__in === 1
  ) {
    // A logger has been provided via config and it has been created by another module (@supertenant/collector).
    // We use it as is.
    parentLogger = config.logger;
  } else 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/core',
      __in: 1
    });
  } else if (config.logger && hasLoggingFunctions(config.logger)) {
    // A custom logger which is neither bunyan nor pino 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 minimal logger.
    parentLogger = consoleLogger;
  }

  Object.keys(registry).forEach(loggerName => {
    const reInitFn = registry[loggerName];
    reInitFn(exports.getLogger(loggerName));
  });
};

/**
 * @param {string} loggerName
 * @param {(arg: *) => *} [reInitFn]
 * @returns {GenericLogger}
 */
exports.getLogger = function getLogger(loggerName, reInitFn) {
  if (!parentLogger) {
    exports.init({});
  }

  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 pino nor bunyan), 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 {typeof consts.BrainLogLevel} level 
 * @param {string} msg 
 */
exports.superbrainLogger = function superbrainLogger(level, msg) {
  superbrain.log(level, msg);
}

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


// FIXME supertenant - these logs don't go to console for some reason (CU #860q6qeuv).
/**
 * 
 * @param {*} dedupKey 
 * @param  {...any} args 
 */
exports.dedupReportError = function(dedupKey, ...args) {
  let now = Date.now();
  const ts = dedupKeys[dedupKey];
  if (ts !== null && ts !== undefined) {
    if (now - DedupLogPeriod < ts) {
      return;
    }
  }
  if (dedupErrorLogger === null) {
    dedupErrorLogger = exports.getLogger('DedupError');
  }
  dedupKeys[dedupKey] = now;
  let stack = new Error().stack;
  dedupErrorLogger.error({dedupKey: dedupKey, stack: stack}, ...args);
}