UNPKG

@instana/core

Version:
79 lines (73 loc) 3.74 kB
/* * (c) Copyright IBM Corp. 2021 * (c) Copyright Instana Inc. and contributors 2019 */ 'use strict'; 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; } } }