/** @module */
'use strict';
require('../../core-upgrade.js');
/**
* Consolidates logging data into a single flattened
* object (flatLogObject) and exposes various methods
* that can be used by backends to generate message
* strings (e.g., stack trace).
*
* @class
* @param {string} logType Type of log being generated.
* @param {Object} logObject Data being logged.
*/
var LogData = function(logType, logObject) {
this.logType = logType;
this.logObject = logObject;
this._error = new Error();
// Cache log information if previously constructed.
this._cache = {};
};
/**
* Generate a full message string consisting of a message and stack trace.
*/
LogData.prototype.fullMsg = function() {
if (this._cache.fullMsg === undefined) {
var messageString = this.msg();
// Stack traces only for error & fatal
// FIXME: This should be configurable later on.
if (/^(error|fatal)(\/|$)?/.test(this.logType) && this.stack()) {
messageString += '\n' + this.stack();
}
this._cache.fullMsg = messageString;
}
return this._cache.fullMsg;
};
/**
* Generate a message string that combines all of the
* logObject's message fields (if an originally an object)
* or strings (if originally an array of strings).
*/
LogData.prototype.msg = function() {
if (this._cache.msg === undefined) {
this._cache.msg = this.flatLogObject().msg;
}
return this._cache.msg;
};
LogData.prototype._getStack = function() {
// Save original Error.prepareStackTrace
var origPrepareStackTrace = Error.prepareStackTrace;
// Override with function that just returns `stack`
Error.prepareStackTrace = function(_, s) { return s; };
// Remove superfluous function calls on stack
var stack = this._error.stack;
for (var i = 0; i < stack.length - 1; i++) {
if (/\.log \(/.test(stack[i])) {
stack = stack.slice(i + 1);
break;
}
}
// Restore original `Error.prepareStackTrace`
Error.prepareStackTrace = origPrepareStackTrace;
return "Stack:\n " + stack.join('\n ');
};
/**
* Generates a message string with a stack trace. Uses the
* flattened logObject's stack trace if it exists; otherwise,
* creates a new stack trace.
*/
LogData.prototype.stack = function() {
if (this._cache.stack === undefined) {
this._cache.stack = this.flatLogObject().stack === undefined
? this._getStack() : this.flatLogObject().stack;
}
return this._cache.stack;
};
/**
* Flattens the logObject array into a single object for access
* by backends.
*/
LogData.prototype.flatLogObject = function() {
if (this._cache.flatLogObject === undefined) {
this._cache.flatLogObject = this._flatten(this.logObject, 'top level');
}
return this._cache.flatLogObject;
};
/**
* Returns a flattened object with an arbitrary number of fields,
* including "msg" (combining all "msg" fields and strings from
* underlying objects) and "stack" (a stack trace, if any).
*
* @param {Object} o Object to flatten.
* @param {string} topLevel Separate top-level from recursive calls.
* @return {Object} Flattened Object.
* @return {string} [return.msg] All "msg" fields, combined with spaces.
* @return {string} [return.longMsg] All "msg" fields, combined with newlines.
* @return {string} [return.stack] A stack trace (if any).
*/
LogData.prototype._flatten = function(o, topLevel) {
var f, msg, longMsg;
if (typeof (o) === 'undefined' || o === null) {
return { msg: '' };
} else if (Array.isArray(o) && topLevel) {
// flatten components, but no longer in a top-level context.
f = o.map(oo => this._flatten(oo));
// join all the messages with spaces or newlines between them.
var tobool = (x => !!x);
msg = f.map(oo => oo.msg).filter(tobool).join(' ');
longMsg = f.map(oo => oo.msg).filter(tobool).join('\n');
// merge all custom fields
f = f.reduce((prev, oo) => Object.assign(prev, oo), {});
return Object.assign(f, {
msg: msg,
longMsg: longMsg,
});
} else if (o instanceof Error) {
f = {
msg: o.message,
// In some cases, we wish to suppress stacks when logging,
// as indicated by `suppressLoggingStack`.
// (E.g. see DoesNotExistError in mediawikiApiRequest.js).
// We return a defined value to avoid generating a stack above.
stack: o.suppressLoggingStack ? "" : o.stack,
};
if (o.httpStatus) {
f.httpStatus = o.httpStatus;
}
return f;
} else if (typeof (o) === 'function') {
return this._flatten(o());
} else if (typeof (o) === 'object' && o.hasOwnProperty('msg')) {
return o;
} else if (typeof (o) === 'string') {
return { msg: o };
} else {
return { msg: JSON.stringify(o) };
}
};
if (typeof module === "object") {
module.exports.LogData = LogData;
}