/* eslint max-classes-per-file: ["error", 2] */ 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 (location) { this.fileName = location.fileName; this.lineNumber = location.lineNumber; this.columnNumber = location.columnNumber; this.callStack = location.callStack; this.className = location.className; this.functionName = location.functionName; this.functionAlias = location.functionAlias; this.callerName = location.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); }); if ( rehydratedEvent.fileName || rehydratedEvent.lineNumber || rehydratedEvent.columnNumber || rehydratedEvent.callStack || rehydratedEvent.className || rehydratedEvent.functionName || rehydratedEvent.functionAlias || rehydratedEvent.callerName ) { rehydratedEvent.location = { fileName: rehydratedEvent.fileName, lineNumber: rehydratedEvent.lineNumber, columnNumber: rehydratedEvent.columnNumber, callStack: rehydratedEvent.callStack, className: rehydratedEvent.className, functionName: rehydratedEvent.functionName, functionAlias: rehydratedEvent.functionAlias, callerName: rehydratedEvent.callerName, }; } 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;