Source: logging/NewRelicLoggingService.js

/**
 * NewRelic will not log an error if it is too long.
 *
 * @ignore
 */
export const MAX_ERROR_LENGTH = 4000;

function fixErrorLength(error) {
  if (error.message && error.message.length > MAX_ERROR_LENGTH) {
    const processedError = Object.create(error);
    processedError.message = processedError.message.substring(0, MAX_ERROR_LENGTH);
    return processedError;
  }
  if (typeof error === 'string' && error.length > MAX_ERROR_LENGTH) {
    return error.substring(0, MAX_ERROR_LENGTH);
  }
  return error;
}

/* Constants used as New Relic page action names. */
const pageActionNameInfo = 'INFO';
const pageActionNameIgnoredError = 'IGNORED_ERROR';

function sendPageAction(actionName, message, customAttributes) {
  if (process.env.NODE_ENV === 'development') {
    console.log(actionName, message, customAttributes); // eslint-disable-line
  }
  if (window && typeof window.newrelic !== 'undefined') {
    // https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/addpageaction/
    window.newrelic.addPageAction(actionName, { message, ...customAttributes });
  }
}

function sendError(error, customAttributes) {
  if (process.env.NODE_ENV === 'development') {
    console.error(error, customAttributes); // eslint-disable-line
  }
  if (window && typeof window.newrelic !== 'undefined') {
    // https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/noticeerror/
    window.newrelic.noticeError(fixErrorLength(error), customAttributes);
  }
}

function setCustomAttribute(name, value) {
  if (process.env.NODE_ENV === 'development') {
    console.log(name, value); // eslint-disable-line
  }
  if (window && typeof window.newrelic !== 'undefined') {
    // https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/setcustomattribute/
    window.newrelic.setCustomAttribute(name, value);
  }
}

/**
 * The NewRelicLoggingService is a concrete implementation of the logging service interface that
 * sends messages to NewRelic that can be seen in NewRelic Browser and NewRelic Insights. When in
 * development mode, all messages will instead be sent to the console.
 *
 * When you use `logError`, your errors will be checked to see if they're ignored *or* not.
 * Not-ignored errors will appear under "JS errors" for your Browser application.
 *
 * ```
 * SELECT * from JavaScriptError WHERE errorStatus is not null SINCE 10 days ago
 * ```
 *
 * Ignored errors will appear in New Relic Insights as page actions, which can be queried:
 *
 * ```
 * SELECT * from PageAction WHERE actionName = 'IGNORED_ERROR' SINCE 1 hour ago
 * ```
 *
 * When using `logInfo`, these only appear in New Relic Insights when querying for page actions as
 * follows:
 *
 * ```
 * SELECT * from PageAction WHERE actionName = 'INFO' SINCE 1 hour ago
 * ```
 *
 * You can also add your own custom metrics as an additional argument, or see the code to find
 * other standard custom attributes. By default, userId is added (via setCustomAttribute) for logged
 * in users via the auth service (AuthAxiosJwtService).
 *
 * Requires the NewRelic Browser JavaScript snippet.
 *
 * @implements {LoggingService}
 * @memberof module:Logging
 */
export default class NewRelicLoggingService {
  constructor(options) {
    const config = options ? options.config : undefined;
    /*
        String which is an explicit error message regex. If an error message matches the regex, the error
        is considered an *ignored* error and submitted to New Relic as a page action - not an error.

        Ignored error regexes are configured per frontend application (MFE).

        The regex for all ignored errors are represented in the .env files as a single string. If you need to
        ignore multiple errors, use the standard `|` regex syntax.

        For example, here's a .env line which ignores two specific errors:

        IGNORED_ERROR_REGEX='^\\[frontend-auth\\] Unimportant Error|Specific non-critical error #[\\d]+'

        This example would ignore errors with the following messages:

        [frontend-app-generic] - Specific non-critical error #45678 happened.
        [frontend-app-generic] - Specific non-critical error #93475 happened.
        [frontend-auth] Unimportant Error: Browser strangeness occurred.

        To test your regex additions, use a JS CLI environment (such as node) and run code like this:

        x = new RegExp('^\\[frontend-auth\\] Unimportant Error|Specific non-critical error #[\\d]+');
        '[frontend-app-generic] - Specific non-critical error #45678 happened.'.match(x);
        '[frontend-auth] Unimportant Error: Browser strangeness occurred.'.match(x);
        'This error should not match anything!'.match(x);

        For edx.org, add new error message regexes in edx-internal YAML as needed.
    */
    this.ignoredErrorRegexes = config ? config.IGNORED_ERROR_REGEX : undefined;
  }

  /**
   *
   *
   * @param {*} infoStringOrErrorObject
   * @param {*} [customAttributes={}]
   * @memberof NewRelicLoggingService
   */
  logInfo(infoStringOrErrorObject, customAttributes = {}) {
    let message = infoStringOrErrorObject;
    let customAttrs = customAttributes;
    if (typeof infoStringOrErrorObject === 'object' && 'message' in infoStringOrErrorObject) {
      /* Caller has passed in an error object to be logged as a page action. */
      /* Extract the attributes and the message. */
      const infoCustomAttributes = infoStringOrErrorObject.customAttributes || {};
      customAttrs = { ...infoCustomAttributes, ...customAttributes };
      message = infoStringOrErrorObject.message;
    }
    sendPageAction(pageActionNameInfo, message, customAttrs);
  }

  /**
   *
   *
   * @param {*} errorStringOrObject
   * @param {*} [customAttributes={}]
   * @memberof NewRelicLoggingService
   */
  logError(errorStringOrObject, customAttributes = {}) {
    const errorCustomAttributes = errorStringOrObject.customAttributes || {};
    let allCustomAttributes = { ...errorCustomAttributes, ...customAttributes };
    if (Object.keys(allCustomAttributes).length === 0) {
      // noticeError expects undefined if there are no custom attributes.
      allCustomAttributes = undefined;
    }

    /*
        Separate the errors into ignored errors and other errors.
        Ignored errors are logged via adding a page action.
        Other errors are logged via noticeError and count as "JS Errors" for the application.
    */
    const errorMessage = errorStringOrObject.message || (typeof errorStringOrObject === 'string' ? errorStringOrObject : '');
    if (this.ignoredErrorRegexes && errorMessage.match(this.ignoredErrorRegexes)) {
      /* ignored error */
      sendPageAction(pageActionNameIgnoredError, errorMessage, allCustomAttributes);
    } else {
      /*  error! */
      sendError(errorStringOrObject, allCustomAttributes);
    }
  }

  /**
   * Sets a custom attribute that will be included with all subsequent log messages.
   *
   * @param {string} name
   * @param {string|number|null} value
   */
  setCustomAttribute(name, value) {
    setCustomAttribute(name, value);
  }
}