const jsonReplacers = require('./json-replacers');
const dataSanitizers = require('./data-sanitizers');
-/**
- * Log as JSON to stdout/stderr.
- */
-
const nop = () => { /**/ };
-
class Logger {
/**
- * Wrap backend calls with payload normalization.
- * @param {Object} options
- * @param {String} options.nodeId - unique identifier for running instance, usually a uuid
- * @param {Object} options.logger
- * @param {String} options.logger.ignoreBelowLevel - minimum level to log
+ * @typedef {Object} ConsoleLike
+ * @property {Function(...):void} error
+ * @property {Function(...):void} warn
+ * @property {Function(...):void} info
+ * @property {Function(...):void} log
+ * @property {Function(...):void} debug
+ */
+ /**
+ * @typedef {Object} LoggerOptions
+ * @property {String} ignoreBelowLevel - minimum level to log, e.g. 'info'
*/
- constructor(options) {
+ /**
+ * Wrap backend calls with payload normalization and metadata.
+ * @param {LoggerOptions} options
+ * @param {Object} commonObject - object to be merged into logged json
+ * @param {AsyncLocalStorage} asyncLocalStorage - async storage for an object to be merged into logged json
+ * @param {ConsoleLike} backend - default is console
+ */
+ constructor(options, commonObject, asyncLocalStorage, backend) {
const logLevels = Object.keys(Logger.nullLogger);
- const ignoreBelowLevel = options && options.logger && options.logger.ignoreBelowLevel || 'debug';
- this.backend = console;
- this.nodeId = options.nodeId;
+ const ignoreBelowLevel = options?.ignoreBelowLevel || 'debug';
+ this.backend = backend || console;
+ this.commonObject = commonObject || {};
this.jsonReplacers = Object.values(jsonReplacers);
this.dataSanitizers = Object.values(dataSanitizers);
+ if (asyncLocalStorage) {
+ // Override the class getter.
+ Object.defineProperty(this, 'asyncLogObject', {
+ enumerable: true,
+ get: asyncLocalStorage.getStore.bind(asyncLocalStorage),
+ });
+ }
+
if (!logLevels.includes(ignoreBelowLevel)) {
throw new RangeError(`unrecognized minimum log level '${ignoreBelowLevel}'`);
}
const ignoreLevelIdx = logLevels.indexOf(ignoreBelowLevel);
logLevels.forEach((level) => {
+ const includeBackendLevel = (logLevels.indexOf(level) > ignoreLevelIdx) && this.backend[level]; // eslint-disable-line security/detect-object-injection
// eslint-disable-next-line security/detect-object-injection
- this[level] = (logLevels.indexOf(level) > ignoreLevelIdx) ?
+ this[level] = (includeBackendLevel) ?
nop :
(...args) => this.backend[level](this.payload(level, ...args)); // eslint-disable-line security/detect-object-injection
});
}
+ /**
+ * Default of empty object when no asyncLocalStorage is defined.
+ * Overridden on instance when asyncLocalStorage is defined.
+ */
+ // eslint-disable-next-line class-methods-use-this
+ get asyncLogObject() {
+ return {};
+ }
+
+
/**
* Structure all expected log data into JSON string.
* @param {String} level
const now = new Date();
return JSON.stringify({
- nodeId: this.nodeId,
+ ...this.commonObject,
timestamp: now.toISOString(),
timestampMs: now.getTime(),
level: level,
message: message || '',
data: data || {},
...(other.length && { other }),
+ ...this.asyncLogObject,
}, this.jsonReplacer.bind(this));
}