const jsonReplacers = require('./json-replacers');
const dataSanitizers = require('./data-sanitizers');
-const nop = () => { /**/ };
+const nop = () => undefined;
class Logger {
/**
- * @typedef {Object} ConsoleLike
- * @property {Function(...):void} error
- * @property {Function(...):void} warn
- * @property {Function(...):void} info
- * @property {Function(...):void} log
- * @property {Function(...):void} debug
+ * @callback LogFn
+ * @param {any} ...args values to log
+ * @returns {void} nothing
*/
/**
- * @typedef {Object} LoggerOptions
- * @property {String} ignoreBelowLevel - minimum level to log, e.g. 'info'
+ * @typedef {object} ConsoleLike
+ * @property {LogFn} error error level
+ * @property {LogFn} warn warn level
+ * @property {LogFn} info info level
+ * @property {LogFn} log log level
+ * @property {LogFn} debug debug level
+ */
+ /**
+ * @typedef AsyncLocalStorage
+ * @property {Function} getStore local storage
+ */
+ /**
+ * @typedef {object} LoggerOptions
+ * @property {string} ignoreBelowLevel minimum level to log, e.g. 'info'
*/
/**
* 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
+ * @param {LoggerOptions} options 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);
/**
* All the expected Console levels, but do nothing.
* Ordered from highest priority to lowest.
+ * @returns {object} no-op backend
*/
static get nullLogger() {
return {
/**
* Default of empty object when no asyncLocalStorage is defined.
* Overridden on instance when asyncLocalStorage is defined.
+ * @returns {object} empty
*/
// eslint-disable-next-line class-methods-use-this
get asyncLogObject() {
/**
* Structure all expected log data into JSON string.
- * @param {String} level
- * @param {String} scope
- * @param {String} message
- * @param {Object} data
- * @param {...any} other
- * @returns {String} JSON string
+ * @param {string} level log level
+ * @param {string} scope scope
+ * @param {string} message message
+ * @param {object} data data
+ * @param {...any} other more data merged into payload
+ * @returns {string} JSON string
*/
payload(level, scope, message, data, ...other) {
const replacer = this.getReplacer();
if (this.sanitizationNeeded(data)) {
// Create copy of data so we are not changing anything important.
- data = structuredClone(data);
+ try {
+ data = structuredClone(data);
+ } catch (e) { // eslint-disable-line no-unused-vars
+ data = JSON.parse(JSON.stringify(data, replacer));
+ }
this.sanitize(data);
}
/**
* Determine if data needs sanitizing.
- * @param {Object} data
- * @returns {Boolean}
+ * @param {object} data data
+ * @returns {boolean} needs sanitization
*/
sanitizationNeeded(data) {
return this.dataSanitizers.some((sanitizer) => sanitizer(data, false));
/**
* Mogrify data.
- * @param {Object} data
+ * @param {object} data data
*/
sanitize(data) {
this.dataSanitizers.forEach((sanitizer) => sanitizer(data));
/**
* Return a replacer function which does de-cycling, as well as the rest of our replacers.
+ * @returns {Function} replacer
*/
getReplacer() {
const ancestors = [];
while (ancestors.length > 0 && ancestors.at(-1) !== this) {
ancestors.pop();
}
- if (ancestors.includes(value)) { // eslint-disable-line security/detect-object-injection
+ if (ancestors.includes(value)) {
return '[Circular]';
} else {
ancestors.push(value);
}
return value;
- }
+ };
}
}