X-Git-Url: http://git.squeep.com/?a=blobdiff_plain;f=lib%2Flogger.js;h=a040bb8ba3f9e759f3003e3af562c6c0fdc7d386;hb=HEAD;hp=e11810e1d312b5efa36478b418a5bdf25c27a728;hpb=8f067f063e2410dd72bcd51aee69b273b1915c25;p=squeep-logger-json-console diff --git a/lib/logger.js b/lib/logger.js index e11810e..32d9d05 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -3,27 +3,36 @@ 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); @@ -58,6 +67,7 @@ class Logger { /** * All the expected Console levels, but do nothing. * Ordered from highest priority to lowest. + * @returns {object} no-op backend */ static get nullLogger() { return { @@ -73,6 +83,7 @@ class Logger { /** * 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() { @@ -82,19 +93,23 @@ class Logger { /** * 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, replacer); + try { + data = structuredClone(data); + } catch (e) { // eslint-disable-line no-unused-vars + data = JSON.parse(JSON.stringify(data, replacer)); + } this.sanitize(data); } @@ -116,8 +131,8 @@ class Logger { /** * 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)); @@ -126,7 +141,7 @@ class Logger { /** * Mogrify data. - * @param {Object} data + * @param {object} data data */ sanitize(data) { this.dataSanitizers.forEach((sanitizer) => sanitizer(data)); @@ -135,30 +150,31 @@ class Logger { /** * Return a replacer function which does de-cycling, as well as the rest of our replacers. + * @returns {Function} replacer */ getReplacer() { const ancestors = []; const loggerReplacers = this.jsonReplacers; return function cycleReplacer(key, value) { + loggerReplacers.every((replacer) => { + const oldValue = value; + value = replacer(key, value); + return oldValue === value; + }); if (typeof value === 'object' && value !== null) { - // this is object where key/value came from + // 'this' is object where key/value came from 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); } } - loggerReplacers.every((replacer) => { - const oldValue = value; - value = replacer(key, value); - return oldValue === value; - }); return value; - } + }; } }