Skip to content

Commit

Permalink
refactor: improved ignoring endpoints implementation (#1558)
Browse files Browse the repository at this point in the history
ref INSTA-26148
  • Loading branch information
aryamohanan authored Feb 21, 2025
1 parent 97e30f1 commit 2b0e70d
Show file tree
Hide file tree
Showing 9 changed files with 214 additions and 91 deletions.
95 changes: 90 additions & 5 deletions packages/core/src/tracing/cls.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ const tracingUtil = require('./tracingUtil');
const { ENTRY, EXIT, INTERMEDIATE, isExitSpan } = require('./constants');
const hooked = require('./clsHooked');
const tracingMetrics = require('./metrics');
const { applyFilter } = require('../util/spanFilter');

/** @type {import('../core').GenericLogger} */
let logger;
logger = require('../logger').getLogger('tracing/cls', newLogger => {
Expand All @@ -30,6 +32,10 @@ let serviceName;
let processIdentityProvider = null;
/** @type {Boolean} */
let allowRootExitSpan;
/**
* @type {import('../tracing').IgnoreEndpoints}
*/
let ignoreEndpoints;

/*
* Access the Instana namespace in continuation local storage.
Expand All @@ -51,13 +57,23 @@ function init(config, _processIdentityProvider) {
}
processIdentityProvider = _processIdentityProvider;
allowRootExitSpan = config?.tracing?.allowRootExitSpan;
ignoreEndpoints = config?.tracing?.ignoreEndpoints;
}
/**
* @param {import('../util/normalizeConfig').AgentConfig} extraConfig
*/
function activate(extraConfig) {
if (extraConfig?.tracing?.ignoreEndpoints) {
ignoreEndpoints = extraConfig.tracing.ignoreEndpoints;
}
}

class InstanaSpan {
/**
* @param {string} name
* @param {object} [data]
*/
constructor(name) {
constructor(name, data) {
// properties that part of our span model
this.t = undefined;
this.s = undefined;
Expand Down Expand Up @@ -98,7 +114,7 @@ class InstanaSpan {
/** @type {Array.<*>} */
this.stack = [];
/** @type {Object.<string, *>} */
this.data = {};
this.data = data || {};

// Properties for the span that are only used internally but will not be transmitted to the agent/backend,
// therefore defined as non-enumerabled. NOTE: If you add a new property, make sure that it is also defined as
Expand Down Expand Up @@ -256,18 +272,20 @@ class InstanaIgnoredSpan extends InstanaSpan {
* @param {string} [spanAttributes.traceId]
* @param {string} [spanAttributes.parentSpanId]
* @param {import('./w3c_trace_context/W3cTraceContext')} [spanAttributes.w3cTraceContext]
* @param {Object} [spanAttributes.spanData]
* @returns {InstanaSpan}
*/

function startSpan(spanAttributes = {}) {
let { spanName, kind, traceId, parentSpanId, w3cTraceContext } = spanAttributes;
let { spanName, kind, traceId, parentSpanId, w3cTraceContext, spanData } = spanAttributes;

tracingMetrics.incrementOpened();
if (!kind || (kind !== ENTRY && kind !== EXIT && kind !== INTERMEDIATE)) {
logger.warn(`Invalid span (${spanName}) without kind/with invalid kind: ${kind}, assuming EXIT.`);
kind = EXIT;
}
const span = new InstanaSpan(spanName);

const span = new InstanaSpan(spanName, spanData);
span.k = kind;

const parentSpan = getCurrentSpan();
Expand Down Expand Up @@ -313,6 +331,20 @@ function startSpan(spanAttributes = {}) {
span.addCleanup(ns.set(w3cTraceContextKey, w3cTraceContext));
}

const filteredSpan = applySpanFilter(span);

// If the span was filtered out, we do not process it further.
// Instead, we return an 'InstanaIgnoredSpan' instance to explicitly indicate that it was excluded from tracing.
if (!filteredSpan) {
return setIgnoredSpan({
spanName: span.n,
kind: span.k,
traceId: span.t,
parentId: span.p,
data: span.data
});
}

if (span.k === ENTRY) {
// Make the entry span available independently (even if getCurrentSpan would return an intermediate or an exit at
// any given moment). This is used by the instrumentations of web frameworks like Express.js to add path templates
Expand Down Expand Up @@ -367,6 +399,49 @@ function putPseudoSpan(spanName, kind, traceId, spanId) {
return span;
}

/**
* Adds an ignored span to the CLS context, serving as a holder for a trace ID and span ID.
* Customers can access the current span via `instana.currentSpan()`, and we avoid returning a "NoopSpan".
* We need this ignored span instance to ensure the trace ID remains accessible for future cases such as propagating
* the trace ID although suppression is on to reactivate a trace.
* These spans will not be sent to the backend.
* @param {Object} options - The options for the span.
* @param {string} options.spanName
* @param {number} options.kind
* @param {string} options.traceId
* @param {string} options.parentId
* @param {Object} options.data
*/
function setIgnoredSpan({ spanName, kind, traceId, parentId, data = {} }) {
if (!kind || (kind !== ENTRY && kind !== EXIT && kind !== INTERMEDIATE)) {
logger.warn(`Invalid ignored span (${spanName}) without kind/with invalid kind: ${kind}, assuming EXIT.`);
kind = EXIT;
}

const span = new InstanaIgnoredSpan(spanName, data);
span.k = kind;
span.t = traceId;
span.p = parentId;

// Setting the 'parentId' of the span to 'span.s' to ensure trace continuity.
// Although this span doesn't physically exist, we are ignoring it, but retaining its parentId.
// This parentId is propagated downstream.
// The spanId does not need to be retained.
span.s = parentId;

if (span.k === ENTRY) {
// Make the entry span available independently (even if getCurrentSpan would return an intermediate or an exit at
// any given moment). This is used by the instrumentations of web frameworks like Express.js to add path templates
// and error messages to the entry span.
span.addCleanup(ns.set(currentEntrySpanKey, span));
}

// Set the span object as the currently active span in the active CLS context and also add a cleanup hook for when
// this span is transmitted.
span.addCleanup(ns.set(currentSpanKey, span));
return span;
}

/*
* Get the currently active entry span.
*/
Expand Down Expand Up @@ -622,6 +697,15 @@ function skipExitTracing(options) {

return skip;
}
/**
* @param {InstanaSpan | import("../core").InstanaBaseSpan} span
*/
function applySpanFilter(span) {
if (ignoreEndpoints) {
return applyFilter({ span, ignoreEndpoints });
}
return span;
}

module.exports = {
skipExitTracing,
Expand All @@ -648,5 +732,6 @@ module.exports = {
enterAsyncContext,
leaveAsyncContext,
runInAsyncContext,
runPromiseInAsyncContext
runPromiseInAsyncContext,
activate
};
6 changes: 3 additions & 3 deletions packages/core/src/tracing/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ const tracingUtil = require('./tracingUtil');
const spanBuffer = require('./spanBuffer');
const supportedVersion = require('./supportedVersion');
const { otelInstrumentations } = require('./opentelemetry-instrumentations');
const cls = require('./cls');
const coreUtil = require('../util');

let tracingEnabled = false;
let tracingActivated = false;
let instrumenationsInitialized = false;
let automaticTracingEnabled = false;
/** @type {import('./cls')} */
let cls = null;

/** @type {import('../util/normalizeConfig').InstanaConfig} */
let config = null;

Expand Down Expand Up @@ -203,7 +203,6 @@ exports.init = function init(_config, downstreamConnection, _processIdentityProv
tracingHeaders.init(config);
spanBuffer.init(config, downstreamConnection);
opentracing.init(config, automaticTracingEnabled, processIdentityProvider);
cls = require('./cls');
cls.init(config, processIdentityProvider);
sdk.init(cls);

Expand Down Expand Up @@ -259,6 +258,7 @@ function initInstrumenations(_config) {
exports.activate = function activate(extraConfig = {}) {
if (tracingEnabled && !tracingActivated) {
tracingActivated = true;
cls.activate(extraConfig);
spanBuffer.activate(extraConfig);
opentracing.activate();
sdk.activate();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,19 @@ class InstanaAWSDynamoDB extends InstanaAWSProduct {

return cls.ns.runAndReturn(() => {
const self = this;

// Data attributes: operation, table
const spanData = {
[this.spanName]: this.buildSpanData(ctx, originalArgs[0], originalArgs[1])
};

const span = cls.startSpan({
spanName: this.spanName,
kind: EXIT
kind: EXIT,
spanData
});
span.ts = Date.now();
span.stack = tracingUtil.getStackTrace(this.instrumentedMakeRequest, 1);
// Data attribs: op and table
span.data[this.spanName] = this.buildSpanData(ctx, originalArgs[0], originalArgs[1]);

if (typeof originalArgs[2] === 'function') {
// callback case
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,19 @@ class InstanaAWSDynamoDB extends InstanaAWSProduct {
}

const command = smithySendArgs[0];

return cls.ns.runAndReturn(() => {
const self = this;

const spanData = {
[this.spanName]: this.buildSpanData(command.constructor.name, command.input)
};
const span = cls.startSpan({
spanName: this.spanName,
kind: EXIT
kind: EXIT,
spanData
});
span.ts = Date.now();
span.stack = tracingUtil.getStackTrace(this.instrumentedSmithySend, 1);
span.data[this.spanName] = this.buildSpanData(command.constructor.name, command.input);
this.captureRegion(ctx, span);

if (typeof smithySendArgs[1] === 'function') {
Expand Down
39 changes: 22 additions & 17 deletions packages/core/src/tracing/instrumentation/database/ioredis.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,24 +76,26 @@ function instrumentSendCommand(original) {
}

const argsForOriginal = arguments;
const connection = `${client.options.host}:${client.options.port}`;

// TODO: https://jsw.ibm.com/browse/INSTA-14540
// if (client.isCluster) {
// connection = client.startupNodes.map(node => `${node.host}:${node.port}`).join(',');

return cls.ns.runAndReturn(() => {
const spanData = {
redis: {
connection,
operation: command.name.toLowerCase()
}
};
const span = cls.startSpan({
spanName: exports.spanName,
kind: constants.EXIT
kind: constants.EXIT,
spanData
});
span.stack = tracingUtil.getStackTrace(wrappedInternalSendCommand);

const connection = `${client.options.host}:${client.options.port}`;

// TODO: https://jsw.ibm.com/browse/INSTA-14540
// if (client.isCluster) {
// connection = client.startupNodes.map(node => `${node.host}:${node.port}`).join(',');

span.data.redis = {
connection,
operation: command.name.toLowerCase()
};

callback = cls.ns.bind(onResult);
command.promise.then(
// make sure that the first parameter is never truthy
Expand Down Expand Up @@ -156,15 +158,18 @@ function instrumentMultiOrPipelineCommand(commandName, original) {
// create a new cls context parent to track the multi/pipeline child calls
const clsContextForMultiOrPipeline = cls.ns.createContext();
cls.ns.enter(clsContextForMultiOrPipeline);
const spanData = {
redis: {
connection,
operation: commandName
}
};
const span = cls.startSpan({
spanName: exports.spanName,
kind: constants.EXIT
kind: constants.EXIT,
spanData
});
span.stack = tracingUtil.getStackTrace(wrappedInternalMultiOrPipelineCommand);
span.data.redis = {
connection,
operation: commandName
};

const multiOrPipeline = original.apply(this, arguments);
shimmer.wrap(
Expand Down
33 changes: 19 additions & 14 deletions packages/core/src/tracing/instrumentation/database/redis.js
Original file line number Diff line number Diff line change
Expand Up @@ -236,17 +236,19 @@ function instrumentCommand(original, command, address, cbStyle) {
}

return cls.ns.runAndReturn(() => {
const spanData = {
redis: {
connection: address || origCtx.address,
operation: command
}
};
const span = cls.startSpan({
spanName: exports.spanName,
kind: constants.EXIT
kind: constants.EXIT,
spanData
});
span.stack = tracingUtil.getStackTrace(instrumentCommand);

span.data.redis = {
connection: address || origCtx.address,
operation: command
};

let userProvidedCallback;

if (cbStyle) {
Expand Down Expand Up @@ -315,27 +317,30 @@ function instrumentMultiExec(origCtx, origArgs, original, address, isAtomic, cbS

return cls.ns.runAndReturn(() => {
let span;

const spanData = {
redis: {
connection: address,
// pipeline = batch
operation: isAtomic ? 'multi' : 'pipeline'
}
};
if (skipExitResult.allowRootExitSpan) {
span = cls.startSpan({
spanName: exports.spanName,
kind: constants.EXIT
kind: constants.EXIT,
spanData
});
} else {
span = cls.startSpan({
spanName: exports.spanName,
kind: constants.EXIT,
traceId: parentSpan.t,
parentSpanId: parentSpan.s
parentSpanId: parentSpan.s,
spanData
});
}

span.stack = tracingUtil.getStackTrace(instrumentMultiExec);
span.data.redis = {
connection: address,
// pipeline = batch
operation: isAtomic ? 'multi' : 'pipeline'
};

const subCommands = (span.data.redis.subCommands = []);
let legacyMultiMarkerHasBeenSeen = false;
Expand Down
Loading

0 comments on commit 2b0e70d

Please sign in to comment.