Source: appender/standard.js

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