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);
}
}
},
);
});
};