/* 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;