Repository URL to install this package:
|
Version:
0.6.4 ▾
|
// (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);
}