/* globals document */
/**
* Recommended default log appender. Adds property attributes below to the logs. Depending
* on the browser some values may be default logged as '?' when the value cannot
* be determined.
*
* @memberof Canadarm.Appender
* @function standardLogAppender
*
* @property logDate {string} - The UTC time the browser threw the error.
* @property language {string} - The language set on the page.
* @property characterSet {string} - Encoding used to read the page.
* @property type {string} - Specifies the type of the log as 'jserror'.
* @property columnNumber {integer} - The column number of where the error took place.
* @property lineNumber {integer} - The line number of where the error took place.
* @property msg {string} - The message for the error. (e.g. [ERROR]: blah blah)
* @property pageURL {string} - The URL location of the page the error occurred on.
* @property url {string} - The URL location of the script that produced the error.
* @property stack {object} - Stacktrace of the exception that occurred.
*
* @param {string} level - Log level of the exception. (e.g. ERROR, FATAL)
* @param {error} exception - JavaScript Error object.
* @param {string} message - Message of the error.
* @param {object} data - A no nesting key/value set of extra data for a log.
*/
function standardLogAppender(level, exception, message, data) {
// Attempt to get the Error stack if it isn't passed in. Without the stack
// the traceback is not as useful. For more information on Error stack see:
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/stack
var stack = exception ? exception.stack : new Error().stack || null,
scriptURL = Canadarm.constant.UNKNOWN_LOG,
errorMessage = message || exception.message || Canadarm.constant.UNKNOWN_LOG,
// Want an ISO string because local time sent as a log is not helpful
currentDate = new Date(),
pageURI = window.location.href,
lineNumber = Canadarm.constant.UNKNOWN_LOG,
columnNumber = Canadarm.constant.UNKNOWN_LOG,
language = window.navigator.language || Canadarm.constant.UNKNOWN_LOG,
characterSet = window.document.characterSet ||
window.document.charset ||
window.document.defaultCharset ||
Canadarm.constant.UNKNOWN_LOG,
logAttributes,
dataKey,
stackData,
dateTime,
// first position is the URL of the script,
// second position is the line number,
// third position is the column number,
// last position is used to gobble down so we can get the data in all browsers.
STACK_SCRIPT_COLUMN_LINE_FINDER = /(http\:\/\/.*\/.*\.js)\:(\d+)\:(\d+)(.*)$/;
// Generates the url, lineNumber, and Column number of the error.
function findStackData(stack) {
// If the stack is not in the error we cannot get any information and
// should return immediately.
if (stack === undefined || stack === null) {
return {
'url': Canadarm.constant.UNKNOWN_LOG,
'lineNumber': Canadarm.constant.UNKNOWN_LOG,
'columnNumber': Canadarm.constant.UNKNOWN_LOG
};
}
// Remove the newlines from all browsers so we can regex this easier
var stackBits = stack.replace(/(\r\n|\n|\r)/gm,'').match(STACK_SCRIPT_COLUMN_LINE_FINDER),
newStack, stackData = [],
stackHasBits = (stackBits !== null && stackBits !== undefined);
while (stackHasBits) {
stackBits = stackBits[1].match(STACK_SCRIPT_COLUMN_LINE_FINDER);
newStack = stackBits !== null ? stackBits[1] : null;
stackData = stackBits !== null ? stackBits : stackData;
stackHasBits = (stackBits !== null && stackBits !== undefined);
}
return {
'url': stackData.length >= 1 ? stackData[1] : Canadarm.constant.UNKNOWN_LOG,
'lineNumber': stackData.length >= 1 ? stackData[2] : Canadarm.constant.UNKNOWN_LOG,
'columnNumber': stackData.length >= 1 ? stackData[3] : Canadarm.constant.UNKNOWN_LOG
};
}
// Use an internal polyfill for Date.toISOString
// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString#Polyfill
// Not attaching it to prototype so we do not add implementation to consumers.
function pad(number) {
if (number < 10) {
return '0' + number;
}
return number;
}
if (!currentDate.toISOString) {
dateTime = (function() {
return currentDate.getUTCFullYear() +
'-' + pad(currentDate.getUTCMonth() + 1) +
'-' + pad(currentDate.getUTCDate()) +
'T' + pad(currentDate.getUTCHours()) +
':' + pad(currentDate.getUTCMinutes()) +
':' + pad(currentDate.getUTCSeconds()) +
'.' + (currentDate.getUTCMilliseconds() / 1000).toFixed(3).slice(2, 5) +
'Z';
}());
} else {
dateTime = currentDate.toISOString();
}
// If stack is not defined we are in a browser that does not fully support our logging.
// Or one of our custom loggers was called, e.g. Logger.debug('message').
if (exception === undefined || exception === null || exception.stack === null) {
if (typeof window.document.getElementsByTagName === 'function') {
var scripts = window.document.getElementsByTagName('script');
scriptURL = (window.document.currentScript || scripts[scripts.length - 1]).src;
} else {
// Probably not in a browser, return unknown log.
scriptURL = Canadarm.constant.UNKNOWN_LOG;
}
} else {
stackData = findStackData(stack);
scriptURL = stackData.url;
lineNumber = stackData.lineNumber;
columnNumber = stackData.columnNumber;
}
// Set base values for log attributes.
logAttributes = {
'characterSet' : characterSet,
'columnNumber' : columnNumber,
'language' : language,
'lineNumber' : lineNumber,
'logDate' : dateTime,
'msg' : '[' + level + ']: ' + errorMessage,
'pageURL' : pageURI,
'stack' : stack || Canadarm.constant.UNKNOWN_LOG,
'type' : 'jserror',
'scriptURL' : scriptURL
};
// Gather the data and add it to our standard logger.
for (dataKey in data) {
if (!data.hasOwnProperty(dataKey)){
continue;
}
// Only set value for legitimate data.
if (data[dataKey] !== null && data[dataKey] !== undefined) {
logAttributes[dataKey] = data[dataKey];
}
}
return logAttributes;
}
Canadarm.Appender.standardLogAppender = standardLogAppender;