Agent.prototype.captureError = function()

in lib/agent.js [595:769]


Agent.prototype.captureError = function (err, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts;
    opts = EMPTY_OPTS;
  } else if (!opts) {
    opts = EMPTY_OPTS;
  }

  const id = errors.generateErrorId();

  if (!this.isStarted()) {
    if (cb) {
      cb(new Error('cannot capture error before agent is started'), id);
    }
    return;
  }

  // Avoid unneeded error/stack processing if only propagating trace-context.
  if (this._conf.contextPropagationOnly) {
    if (cb) {
      process.nextTick(cb, null, id);
    }
    return;
  }

  const agent = this;
  let callSiteLoc = null;
  const errIsError = isError(err);
  const handled = opts.handled !== false; // default true
  const shouldCaptureAttributes = opts.captureAttributes !== false; // default true
  const skipOutcome = Boolean(opts.skipOutcome);
  const timestampUs = opts.timestamp
    ? Math.floor(opts.timestamp * 1000)
    : Date.now() * 1000;

  // Determine transaction/span context to associate with this error.
  let parent;
  let span;
  let trans;
  if (opts.parent === undefined) {
    parent =
      this._instrumentation.currSpan() ||
      this._instrumentation.currTransaction();
  } else if (opts.parent === null) {
    parent = null;
  } else {
    parent = opts.parent;
  }
  if (parent instanceof Transaction) {
    span = null;
    trans = parent;
  } else if (parent instanceof Span) {
    span = parent;
    trans = parent.transaction;
  }
  const traceContext = (span || trans || {})._context;
  const req =
    opts.request instanceof IncomingMessage ? opts.request : trans && trans.req;
  const res =
    opts.response instanceof ServerResponse
      ? opts.response
      : trans && trans.res;

  // As an added feature, for *some* cases, we capture a stacktrace at the point
  // this `captureError` was called. This is added to `error.log.stacktrace`.
  if (
    handled &&
    (agent._conf.captureErrorLogStackTraces ===
      CAPTURE_ERROR_LOG_STACK_TRACES_ALWAYS ||
      (!errIsError &&
        agent._conf.captureErrorLogStackTraces ===
          CAPTURE_ERROR_LOG_STACK_TRACES_MESSAGES))
  ) {
    callSiteLoc = {};
    Error.captureStackTrace(callSiteLoc, Agent.prototype.captureError);
  }

  if (span && !skipOutcome) {
    span._setOutcomeFromErrorCapture(constants.OUTCOME_FAILURE);
  }

  // Note this error as an "inflight" event. See Agent#flush().
  const inflightEvents = this._inflightEvents;
  inflightEvents.add(id);

  // Move the remaining captureError processing to a later tick because:
  // 1. This allows the calling code to continue processing. For example, for
  //    Express instrumentation this can significantly improve latency in
  //    the app's endpoints because the response does not proceed until the
  //    error handlers return.
  // 2. Gathering `error.context.response` in the same tick results in data
  //    for a response that hasn't yet completed (no headers, unset status_code,
  //    etc.).
  setImmediate(() => {
    // Gather `error.context.*`.
    const errorContext = {
      user: Object.assign(
        {},
        req && parsers.getUserContextFromRequest(req),
        trans && trans._user,
        opts.user,
      ),
      tags: Object.assign({}, trans && trans._labels, opts.tags, opts.labels),
      custom: Object.assign({}, trans && trans._custom, opts.custom),
    };
    if (req) {
      errorContext.request = parsers.getContextFromRequest(
        req,
        agent._conf,
        'errors',
      );
    }
    if (res) {
      errorContext.response = parsers.getContextFromResponse(
        res,
        agent._conf,
        true,
      );
    }

    errors.createAPMError(
      {
        log: agent.logger,
        id,
        exception: errIsError ? err : null,
        logMessage: errIsError ? null : err,
        shouldCaptureAttributes,
        timestampUs,
        handled,
        callSiteLoc,
        message: opts.message,
        sourceLinesAppFrames: agent._conf.sourceLinesErrorAppFrames,
        sourceLinesLibraryFrames: agent._conf.sourceLinesErrorLibraryFrames,
        trans,
        traceContext,
        errorContext,
        exceptionType: opts.exceptionType,
      },
      function filterAndSendError(_err, apmError) {
        // _err is always null from createAPMError.

        apmError = agent._errorFilters.process(apmError);
        if (!apmError) {
          agent.logger.debug('error ignored by filter %o', { id });
          inflightEvents.delete(id);
          if (cb) {
            cb(null, id);
          }
          return;
        }

        if (agent._apmClient) {
          agent.logger.debug('Sending error to Elastic APM: %o', { id });
          agent._apmClient.sendError(apmError);
          inflightEvents.delete(id);
          if (!handled || cb) {
            // Immediately flush *unhandled* errors -- those from
            // `uncaughtException` -- on the assumption that the process may
            // soon crash. Also flush when a `cb` is provided.
            agent.flush(function (flushErr) {
              if (cb) {
                cb(flushErr, id);
              }
            });
          }
        } else {
          inflightEvents.delete(id);
          if (cb) {
            cb(new Error('cannot send error: missing transport'), id);
          }
        }
      },
    );
  });
};