/* eslint max-classes-per-file: ["error", 2] */ /* eslint no-underscore-dangle: ["error", { "allow": ["_getLocationKeys"] }] */ const flatted = require('flatted'); const levels = require('./levels'); class SerDe { constructor() { const deserialise = { __LOG4JS_undefined__: undefined, __LOG4JS_NaN__: Number('abc'), __LOG4JS_Infinity__: 1 / 0, '__LOG4JS_-Infinity__': -1 / 0, }; this.deMap = deserialise; this.serMap = {}; Object.keys(this.deMap).forEach((key) => { const value = this.deMap[key]; this.serMap[value] = key; }); } canSerialise(key) { if (typeof key === 'string') return false; return key in this.serMap; } serialise(key) { if (this.canSerialise(key)) return this.serMap[key]; return key; } canDeserialise(key) { return key in this.deMap; } deserialise(key) { if (this.canDeserialise(key)) return this.deMap[key]; return key; } } const serde = new SerDe(); /** * @name LoggingEvent * @namespace Log4js */ class LoggingEvent { /** * Models a logging event. * @constructor * @param {string} categoryName name of category * @param {Log4js.Level} level level of message * @param {Array} data objects to log * @param {Error} [error] * @author Seth Chisamore */ constructor(categoryName, level, data, context, location, error) { this.startTime = new Date(); this.categoryName = categoryName; this.data = data; this.level = level; this.context = Object.assign({}, context); // eslint-disable-line prefer-object-spread this.pid = process.pid; this.error = error; if (typeof location !== 'undefined') { if (!location || typeof location !== 'object' || Array.isArray(location)) throw new TypeError( 'Invalid location type passed to LoggingEvent constructor' ); this.constructor._getLocationKeys().forEach((key) => { if (typeof location[key] !== 'undefined') this[key] = location[key]; }); } } /** @private */ static _getLocationKeys() { return [ 'fileName', 'lineNumber', 'columnNumber', 'callStack', 'className', 'functionName', 'functionAlias', 'callerName', ]; } serialise() { return flatted.stringify(this, (key, value) => { // JSON.stringify(new Error('test')) returns {}, which is not really useful for us. // The following allows us to serialize errors (semi) correctly. if (value instanceof Error) { // eslint-disable-next-line prefer-object-spread value = Object.assign( { message: value.message, stack: value.stack }, value ); } // JSON.stringify({a: Number('abc'), b: 1/0, c: -1/0}) returns {a: null, b: null, c: null}. // The following allows us to serialize to NaN, Infinity and -Infinity correctly. // JSON.stringify([undefined]) returns [null]. // The following allows us to serialize to undefined correctly. return serde.serialise(value); }); } static deserialise(serialised) { let event; try { const rehydratedEvent = flatted.parse(serialised, (key, value) => { if (value && value.message && value.stack) { const fakeError = new Error(value); Object.keys(value).forEach((k) => { fakeError[k] = value[k]; }); value = fakeError; } return serde.deserialise(value); }); this._getLocationKeys().forEach((key) => { if (typeof rehydratedEvent[key] !== 'undefined') { if (!rehydratedEvent.location) rehydratedEvent.location = {}; rehydratedEvent.location[key] = rehydratedEvent[key]; } }); event = new LoggingEvent( rehydratedEvent.categoryName, levels.getLevel(rehydratedEvent.level.levelStr), rehydratedEvent.data, rehydratedEvent.context, rehydratedEvent.location, rehydratedEvent.error ); event.startTime = new Date(rehydratedEvent.startTime); event.pid = rehydratedEvent.pid; if (rehydratedEvent.cluster) { event.cluster = rehydratedEvent.cluster; } } catch (e) { event = new LoggingEvent('log4js', levels.ERROR, [ 'Unable to parse log:', serialised, 'because: ', e, ]); } return event; } } module.exports = LoggingEvent;