Skip to content

Commit 938c8bf

Browse files
committed
feat: added support for w3c sampled flag
refs https://jsw.ibm.com/browse/INSTA-86266
1 parent ab90a13 commit 938c8bf

2 files changed

Lines changed: 162 additions & 31 deletions

File tree

packages/collector/test/integration/misc/w3c_trace_context/test_base.js

Lines changed: 151 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -169,21 +169,73 @@ module.exports = function () {
169169
})
170170
);
171171

172-
// First request to Instana already has spec headers with sampled=0, simulating a (spec) trace in progress where
173-
// the most recent upstream service did not record tracing data. We still expect Instana to continue the W3C
174-
// trace with the W3C trace ID from traceparent (the parent element has not been recorded, but other ancestor
175-
// elements might have been recorded). We also expect the correct spec headers to be passed downstream by the
176-
// Instana service. In particular, it should keep the same trace ID it received when passing down spec headers.
177-
// Furthermore, the random trace ID flag should be set since it was set in the incoming header.
178-
it('Instana continues a spec trace with sampled=0 (upstream sets the random trace ID flag)', () =>
172+
// W3C sampled=0 (flag 02: sampled=0, random-trace-id=1), no X-INSTANA-L
173+
it('Instana respects W3C sampled=0 flag (with random-trace-id flag)', () =>
179174
startRequest({
180175
app: instanaAppControls,
181176
depth: 1,
182177
withSpecHeaders: 'valid-not-sampled-with-random-trace-id'
178+
})
179+
.then(response => {
180+
response = response && response.body ? JSON.parse(response.body) : response;
181+
expect(response.instanaHeaders).to.be.an('object');
182+
expect(response.instanaHeaders.t).to.not.exist;
183+
expect(response.instanaHeaders.s).to.not.exist;
184+
expect(response.instanaHeaders.l).to.equal('0');
185+
const traceparent = response.w3cTraceContext.receivedHeaders.traceparent;
186+
const tracestate = response.w3cTraceContext.receivedHeaders.tracestate;
187+
// sampled=0 is passed down, random-trace-id flag is preserved (flag 02)
188+
expect(traceparent).to.match(new RegExp(`00-${foreignTraceId}-${foreignParentId}-02`));
189+
expect(tracestate).to.equal('thing=foo,bar=baz');
190+
})
191+
.then(() => delay(500))
192+
.then(() =>
193+
agentControls.getSpans().then(spans => {
194+
expect(spans).to.have.lengthOf(0);
195+
})
196+
));
197+
198+
// W3C sampled=0 (flag 00: sampled=0, random-trace-id=0), no X-INSTANA-L
199+
it('Instana respects W3C sampled=0 flag (without random-trace-id flag)', () =>
200+
startRequest({
201+
app: instanaAppControls,
202+
depth: 1,
203+
withSpecHeaders: 'valid-not-sampled-no-random-trace-id'
204+
})
205+
.then(response => {
206+
response = response && response.body ? JSON.parse(response.body) : response;
207+
expect(response.instanaHeaders).to.be.an('object');
208+
expect(response.instanaHeaders.t).to.not.exist;
209+
expect(response.instanaHeaders.s).to.not.exist;
210+
// X-INSTANA-L: 0 is passed down (derived from W3C sampled=0)
211+
expect(response.instanaHeaders.l).to.equal('0');
212+
expect(response.w3cTraceContext).to.be.an('object');
213+
expect(response.w3cTraceContext.receivedHeaders).to.be.an('object');
214+
const traceparent = response.w3cTraceContext.receivedHeaders.traceparent;
215+
const tracestate = response.w3cTraceContext.receivedHeaders.tracestate;
216+
// sampled=0 is passed down (flag stays 00)
217+
expect(traceparent).to.match(new RegExp(`00-${foreignTraceId}-${foreignParentId}-00`));
218+
expect(tracestate).to.equal('thing=foo,bar=baz');
219+
})
220+
// give spans a chance to come in
221+
.then(() => delay(500))
222+
.then(() =>
223+
// verify there are no spans
224+
agentControls.getSpans().then(spans => {
225+
expect(spans).to.have.lengthOf(0);
226+
})
227+
));
228+
229+
// W3C sampled=1, no X-INSTANA-L: Should trace normally
230+
it('Instana respects incoming W3C sampled=1 flag and traces (no X-INSTANA-L)', () =>
231+
startRequest({
232+
app: instanaAppControls,
233+
depth: 1,
234+
withSpecHeaders: 'valid-sampled-no-random-trace-id'
183235
}).then(response => {
184236
const { traceparent, tracestate } = getSpecHeadersFromFinalHttpRequest(response);
185237
return retryUntilSpansMatch(agentControls, spans => {
186-
const instanaHttpEntryRoot = verifyHttpEntry({
238+
const instanaHttpEntry = verifyHttpEntry({
187239
spans,
188240
instanaAppControls,
189241
parentSpan: {
@@ -194,44 +246,117 @@ module.exports = function () {
194246
usedTraceParent: true,
195247
longTraceId: foreignTraceId
196248
});
197-
const instanaHttpExit = verifyHttpExit(spans, instanaHttpEntryRoot, '/end');
249+
const instanaHttpExit = verifyHttpExit(spans, instanaHttpEntry, '/end');
198250

251+
// W3C sampled=1 is respected: spans created, sampled=1 passed downstream
199252
const instanaExitSpanId = instanaHttpExit.s;
200-
expect(traceparent).to.match(new RegExp(`00-${foreignTraceId}-${instanaExitSpanId}-03`));
253+
expect(traceparent).to.match(new RegExp(`00-${foreignTraceId}-${instanaExitSpanId}-01`));
201254
expect(tracestate).to.match(new RegExp(`in=${foreignTraceIdRightHalf};${instanaExitSpanId}`));
202255
});
203256
}));
204257

205-
// First request to Instana already has spec headers with sampled=0, simulating a (spec) trace in progress where
206-
// the most recent upstream service did not record tracing data. We still expect Instana to continue the W3C
207-
// trace with the W3C trace ID from traceparent (the parent element has not been recorded, but other ancestor
208-
// elements might have been recorded). We also expect the correct spec headers to be passed downstream by the
209-
// Instana service. In particular, it should keep the same trace ID it received when passing down spec headers.
210-
// Furthermore, the random trace ID flag should be unset since it was unset in the incoming header.
211-
it('Instana continues a spec trace with sampled=0 (upstream does not set the random trace ID flag)', () =>
258+
// W3C sampled=0 + X-INSTANA-L: 0: Both say suppress, should suppress
259+
it('W3C sampled=0 + X-INSTANA-L=0: suppresses tracing (both agree)', () =>
212260
startRequest({
213261
app: instanaAppControls,
214262
depth: 1,
215-
withSpecHeaders: 'valid-not-sampled-no-random-trace-id'
263+
withSpecHeaders: 'valid-not-sampled-no-random-trace-id',
264+
withInstanaHeaders: 'suppress'
265+
})
266+
.then(response => {
267+
response = response && response.body ? JSON.parse(response.body) : response;
268+
expect(response.instanaHeaders.t).to.not.exist;
269+
expect(response.instanaHeaders.s).to.not.exist;
270+
expect(response.instanaHeaders.l).to.equal('0');
271+
const traceparent = response.w3cTraceContext.receivedHeaders.traceparent;
272+
expect(traceparent).to.match(new RegExp(`00-${foreignTraceId}-${foreignParentId}-00`));
273+
})
274+
.then(() => delay(500))
275+
.then(() =>
276+
agentControls.getSpans().then(spans => {
277+
expect(spans).to.have.lengthOf(0);
278+
})
279+
));
280+
281+
// W3C sampled=0 + X-INSTANA-L: 1: X-INSTANA-L wins, should trace
282+
it('W3C sampled=0 + X-INSTANA-L=1: traces anyway (X-INSTANA-L has priority)', () =>
283+
startRequest({
284+
app: instanaAppControls,
285+
depth: 1,
286+
withSpecHeaders: 'valid-not-sampled-no-random-trace-id',
287+
withInstanaHeaders: 'trace-in-progress'
216288
}).then(response => {
217289
const { traceparent, tracestate } = getSpecHeadersFromFinalHttpRequest(response);
218290
return retryUntilSpansMatch(agentControls, spans => {
219-
const instanaHttpEntryRoot = verifyHttpEntry({
291+
const instanaHttpEntry = verifyHttpEntry({
292+
instanaAppControls,
220293
spans,
294+
parentSpan: {
295+
t: upstreamInstanaTraceId,
296+
s: upstreamInstanaParentId
297+
},
298+
url: '/start'
299+
});
300+
const instanaHttpExit = verifyHttpExit(spans, instanaHttpEntry, '/end');
301+
302+
// X-INSTANA-L: 1 overrides W3C sampled=0, so sampled=1 is passed down
303+
const instanaExitSpanId = instanaHttpExit.s;
304+
expect(traceparent).to.match(new RegExp(`00-${foreignTraceId}-${instanaExitSpanId}-01`));
305+
expect(tracestate).to.match(new RegExp(`in=${upstreamInstanaTraceId};${instanaExitSpanId}`));
306+
});
307+
}));
308+
309+
// W3C sampled=1 + X-INSTANA-L: 0: X-INSTANA-L wins, should suppress
310+
it('W3C sampled=1 + X-INSTANA-L=0: suppresses tracing (X-INSTANA-L has priority)', () =>
311+
startRequest({
312+
app: instanaAppControls,
313+
depth: 1,
314+
withSpecHeaders: 'valid-sampled-no-random-trace-id',
315+
withInstanaHeaders: 'suppress'
316+
})
317+
.then(response => {
318+
response = response && response.body ? JSON.parse(response.body) : response;
319+
expect(response.instanaHeaders.t).to.not.exist;
320+
expect(response.instanaHeaders.s).to.not.exist;
321+
expect(response.instanaHeaders.l).to.equal('0');
322+
const traceparent = response.w3cTraceContext.receivedHeaders.traceparent;
323+
// X-INSTANA-L: 0 overrides W3C sampled=1, so sampled=0 is passed down
324+
const traceParentMatch = new RegExp(`00-${foreignTraceId}-([0-9a-f]{16})-00`).exec(traceparent);
325+
expect(traceParentMatch).to.exist;
326+
expect(traceParentMatch[1]).to.not.equal(foreignParentId);
327+
})
328+
.then(() => delay(500))
329+
.then(() =>
330+
agentControls.getSpans().then(spans => {
331+
expect(spans).to.have.lengthOf(0);
332+
})
333+
));
334+
335+
// W3C sampled=1 + X-INSTANA-L: 1: Both say trace, should trace
336+
it('W3C sampled=1 + X-INSTANA-L=1: traces (both agree)', () =>
337+
startRequest({
338+
app: instanaAppControls,
339+
depth: 1,
340+
withSpecHeaders: 'valid-sampled-no-random-trace-id',
341+
withInstanaHeaders: 'trace-in-progress'
342+
}).then(response => {
343+
const { traceparent, tracestate } = getSpecHeadersFromFinalHttpRequest(response);
344+
return retryUntilSpansMatch(agentControls, spans => {
345+
const instanaHttpEntry = verifyHttpEntry({
221346
instanaAppControls,
347+
spans,
222348
parentSpan: {
223-
t: foreignTraceIdRightHalf,
224-
s: foreignParentId
349+
t: upstreamInstanaTraceId,
350+
s: upstreamInstanaParentId
225351
},
226-
url: '/start',
227-
usedTraceParent: true,
228-
longTraceId: foreignTraceId
352+
url: '/start'
229353
});
230-
const instanaHttpExit = verifyHttpExit(spans, instanaHttpEntryRoot, '/end');
354+
const instanaHttpExit = verifyHttpExit(spans, instanaHttpEntry, '/end');
231355

356+
// Both agree on tracing, sampled=1 is passed down
232357
const instanaExitSpanId = instanaHttpExit.s;
233358
expect(traceparent).to.match(new RegExp(`00-${foreignTraceId}-${instanaExitSpanId}-01`));
234-
expect(tracestate).to.match(new RegExp(`in=${foreignTraceIdRightHalf};${instanaExitSpanId}`));
359+
expect(tracestate).to.match(new RegExp(`in=${upstreamInstanaTraceId};${instanaExitSpanId}`));
235360
});
236361
}));
237362

packages/core/src/tracing/tracingHeaders.js

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -171,18 +171,24 @@ exports.fromHeaders = function fromHeaders(headers) {
171171
// If w3cTraceContext has no instanaTraceId/instanaParentId yet, it will get one as soon as we start a span and
172172
// upate it. In case we received X-INSTANA-L: 0 we will not start a span, but we will make sure to toggle the
173173
// sampled flag in traceparent off.
174+
175+
// Respect W3C sampled flag: If sampled=0 and no X-INSTANA-L header is present, treat it as suppressed
176+
const w3cSampledFlagSuppressed = w3cTraceContext.sampled === false && !level;
177+
const effectiveLevel = w3cSampledFlagSuppressed ? '0' : level;
178+
const shouldSuppress = isSuppressed(effectiveLevel);
179+
174180
let instanaAncestor;
175-
if (traceStateHasInstanaKeyValuePair(w3cTraceContext) && !isSuppressed(level)) {
181+
if (traceStateHasInstanaKeyValuePair(w3cTraceContext) && !shouldSuppress) {
176182
instanaAncestor = {
177183
t: w3cTraceContext.instanaTraceId,
178184
p: w3cTraceContext.instanaParentId
179185
};
180186
}
181187
return exports.limitTraceId({
182-
traceId: !isSuppressed(level) ? w3cTraceContext.traceParentTraceId : null,
183-
parentId: !isSuppressed(level) ? w3cTraceContext.traceParentParentId : null,
184-
usedTraceParent: !isSuppressed(level),
185-
level,
188+
traceId: !shouldSuppress ? w3cTraceContext.traceParentTraceId : null,
189+
parentId: !shouldSuppress ? w3cTraceContext.traceParentParentId : null,
190+
usedTraceParent: !shouldSuppress,
191+
level: effectiveLevel,
186192
correlationType,
187193
correlationId,
188194
synthetic,

0 commit comments

Comments
 (0)