@instana/core
Version:
Core library for Instana's Node.js packages
79 lines (73 loc) • 3.74 kB
JavaScript
/*
* (c) Copyright IBM Corp. 2021
* (c) Copyright Instana Inc. and contributors 2019
*/
;
const currentSpanKey = 'com.instana.span';
const reducedSpanKey = 'com.instana.reduced';
/**
* @param {import('./context').InstanaCLSContext} context
* @param {string} key
* @param {*} value
*/
module.exports = function unset(context, key, value) {
if (context[key] === value) {
storeReducedSpan(context, key, value);
delete context[key];
}
};
/**
* Check if the provided object is an Instana span and if so, store a reduced version of the span under a special key.
* The full span is reduced to a smaller set of attributes necessary to keep trace continuity. Potentially large fields
* like data, stackTrace, etc. are not stored.
* @param {import('./context').InstanaCLSContext} context
* @param {string} key
* @param {import('../../core').InstanaBaseSpan} span
*/
function storeReducedSpan(context, key, span) {
// We keep a reduced record of spans after having sent them to the agent. This serves two purposes:
//
// 1) In some special cases, async continuity can break due to userland queueing or deferred exits.
//
// One example are GraphQL subscription updates. The processing of the HTTP entry that triggered the subscription
// update has already finished by the time the subscription update is processed.
//
// Another example are deferred exit calls. For example, when processing an incoming HTTP request, the application
// under monitoring could send a response back to the client early and trigger additional outgoing calls in an
// asynchronous post-processing step (after sending back the response). Without keeping a reduced span, we would miss
// these deferred exits - the exit instrumentation would see that there is no active entry span and would not record
// the call as an exit span at all. To support deferred exit calls, instrumentations can fall back to the reduced span
// if there is no active entry span. At the moment, only HTTP client instrumentations (httpClient, http2Client and
// nativeFetch) and GraphQL subscription updates do this. We could potentially extend this to _all_ exit
// instrumentations.
//
// In summary: By keeping the reduced span around, we can still provide trace continuity in these edge cases.
//
// Note: The reduced span will automatically be removed when its async context is finally destroyed. This is important
// for two reasons, (a) keeping the reduced span does not create a memory leak and (b) we do not accidentally capture
// exit spans under completely unrelated entry spans.
//
// 2) If there is an async leak in the Node.js runtime, that is, missing destroy calls for async_hook resources for
// which an init call has been received, the memory used by clsHooked will grow, because context objects will be kept
// around forever. By keeping the reduced span we can at least see (in a heap dump) for which type of spans the
// destroy call is missing, aiding in troubleshooting the Node.js runtime bug.
//
// Exception:
// SDK spans are different. Customers use "startEntrySpan" or "startExitSpan" manually inside their code base.
// We cannot remember the reduced spans because it can mislead to a wrong functionality such as
// tracing exit spans without entry spans - see sdkApp1.js.
if (key === currentSpanKey && span != null && span.n !== 'sdk') {
context[reducedSpanKey] = {
n: span.n,
t: span.t,
s: span.s,
p: span.p,
k: span.k
};
// Also keep captured GraphQL destination (host & port) for subscription updates, if present.
const gqd = span.gqd;
if (gqd) {
context[reducedSpanKey].gqd = gqd;
}
}
}