diff --git a/packages/aws-lambda/test/specification_compliance/test.js b/packages/aws-lambda/test/specification_compliance/test.js index e0637f3b39..aec3dee4fe 100644 --- a/packages/aws-lambda/test/specification_compliance/test.js +++ b/packages/aws-lambda/test/specification_compliance/test.js @@ -148,7 +148,7 @@ function registerSuite(w3cTraceCorrelationDisabled) { const responseBody = response.body; verifyHttpHeadersOnDownstreamRequest(testDefinition, valuesForPlaceholders, responseBody); - const suppressed = testDefinition['X-INSTANA-L in'] === '0'; + const suppressed = testDefinition.suppressed === true; if (suppressed) { await delay(500); const spans = await control.getSpans(); diff --git a/packages/collector/test/integration/misc/specification_compliance/test_base.js b/packages/collector/test/integration/misc/specification_compliance/test_base.js index f4e89394d2..ecaa9e3fb2 100644 --- a/packages/collector/test/integration/misc/specification_compliance/test_base.js +++ b/packages/collector/test/integration/misc/specification_compliance/test_base.js @@ -138,7 +138,7 @@ module.exports = function () { headers = { ...headers, ...parseHeaderList(testDefinition['request headers in']) }; - const suppressed = testDefinition['X-INSTANA-L in'] === '0'; + const suppressed = testDefinition.suppressed === true; const basePath = '/start'; const query = testDefinition['query in']; diff --git a/packages/collector/test/integration/misc/specification_compliance/tracer_compliance_test_cases.json b/packages/collector/test/integration/misc/specification_compliance/tracer_compliance_test_cases.json index c675b231e2..08b15bbe6d 100644 --- a/packages/collector/test/integration/misc/specification_compliance/tracer_compliance_test_cases.json +++ b/packages/collector/test/integration/misc/specification_compliance/tracer_compliance_test_cases.json @@ -231,26 +231,29 @@ }, { "Scenario": "only X-INSTANA-L=0", - "What to do?": "Don’t create spans, send X-INSTANA-L=0 and spec headers downstream with sampled=0", + "What to do?": "Don't create spans, send X-INSTANA-L=0 and spec headers downstream with sampled=0", "X-INSTANA-L in": "0", + "suppressed": true, "X-INSTANA-L out": "0", "traceparent out": "00-0000000000000000$new_64_bit_trace_id-$new_span_id-02", "index": 12 }, { "Scenario": "X-INSTANA-L=0 plus -T and -S", - "What to do?": "Don’t create spans, send X-INSTANA-L=0 and spec headers downstream with sampled=0", + "What to do?": "Don't create spans, send X-INSTANA-L=0 and spec headers downstream with sampled=0", "X-INSTANA-T in": "fa2375d711a4ca0f", "X-INSTANA-S in": "37cb2d6e9b1c078a", "X-INSTANA-L in": "0", + "suppressed": true, "X-INSTANA-L out": "0", "traceparent out": "00-0000000000000000$new_64_bit_trace_id-$new_span_id-02", "index": 13 }, { "Scenario": "X-INSTANA-L=0 plus traceparent", - "What to do?": "Don’t create spans, send X-INSTANA-L=0 and spec headers downstream with sampled=0", + "What to do?": "Don't create spans, send X-INSTANA-L=0 and spec headers downstream with sampled=0", "X-INSTANA-L in": "0", + "suppressed": true, "traceparent in": "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01", "X-INSTANA-L out": "0", "traceparent out": "00-0af7651916cd43dd8448eb211c80319c-$new_span_id-00", @@ -258,8 +261,9 @@ }, { "Scenario": "X-INSTANA-L=0 plus traceparent and tracestate", - "What to do?": "Don’t create spans, send X-INSTANA-L=0 and spec headers downstream with sampled=0", + "What to do?": "Don't create spans, send X-INSTANA-L=0 and spec headers downstream with sampled=0", "X-INSTANA-L in": "0", + "suppressed": true, "traceparent in": "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01", "tracestate in": "congo=ucfJifl5GOE,rojo=00f067aa0ba902b7", "X-INSTANA-L out": "0", @@ -515,29 +519,32 @@ }, { "Scenario": "w3c off, only X-INSTANA-L=0", - "What to do?": "Don’t create spans, send X-INSTANA-L=0 and spec headers downstream with sampled=0", + "What to do?": "Don't create spans, send X-INSTANA-L=0 and spec headers downstream with sampled=0", "INSTANA_DISABLE_W3C_TRACE_CORRELATION": "yes_please", "X-INSTANA-L in": "0", + "suppressed": true, "X-INSTANA-L out": "0", "traceparent out": "00-0000000000000000$new_64_bit_trace_id-$new_span_id-02", "index": 28 }, { "Scenario": "w3c off, X-INSTANA-L=0 plus -T and -S", - "What to do?": "Don’t create spans, send X-INSTANA-L=0 and spec headers downstream with sampled=0", + "What to do?": "Don't create spans, send X-INSTANA-L=0 and spec headers downstream with sampled=0", "INSTANA_DISABLE_W3C_TRACE_CORRELATION": "w3c_trace_correlation_stinks", "X-INSTANA-T in": "fa2375d711a4ca0f", "X-INSTANA-S in": "37cb2d6e9b1c078a", "X-INSTANA-L in": "0", + "suppressed": true, "X-INSTANA-L out": "0", "traceparent out": "00-0000000000000000$new_64_bit_trace_id-$new_span_id-02", "index": 29 }, { "Scenario": "w3c off, X-INSTANA-L=0 plus traceparent", - "What to do?": "Don’t create spans, send X-INSTANA-L=0 and spec headers downstream with sampled=0", + "What to do?": "Don't create spans, send X-INSTANA-L=0 and spec headers downstream with sampled=0", "INSTANA_DISABLE_W3C_TRACE_CORRELATION": "true", "X-INSTANA-L in": "0", + "suppressed": true, "traceparent in": "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01", "X-INSTANA-L out": "0", "traceparent out": "00-0af7651916cd43dd8448eb211c80319c-$new_span_id-00", @@ -545,9 +552,10 @@ }, { "Scenario": "w3c off, X-INSTANA-L=0 plus traceparent and tracestate", - "What to do?": "Don’t create spans, send X-INSTANA-L=0 and spec headers downstream with sampled=0", + "What to do?": "Don't create spans, send X-INSTANA-L=0 and spec headers downstream with sampled=0", "INSTANA_DISABLE_W3C_TRACE_CORRELATION": "yes", "X-INSTANA-L in": "0", + "suppressed": true, "traceparent in": "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01", "tracestate in": "congo=ucfJifl5GOE,rojo=00f067aa0ba902b7", "X-INSTANA-L out": "0", @@ -591,22 +599,11 @@ }, { "Scenario": "traceparent but no tracestate, sampled flag is 0", - "What to do?": "Ignore the sampled flag, continue the trace from traceparent, set span.lt and span.tp, but not span.ia", + "What to do?": "Respect the sampled=0 flag, suppress tracing, pass sampled=0 downstream", "traceparent in": "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-00", - "Server-Timing": "intid;desc=8448eb211c80319c", - "entrySpan.t": "8448eb211c80319c", - "entrySpan.p": "b9c7c989f97918e1", - "entrySpan.s": "$new_span_id_1", - "entrySpan.tp": true, - "entrySpan.lt": "0af7651916cd43dd8448eb211c80319c", - "exitSpan.t": "8448eb211c80319c", - "exitSpan.p": "$new_span_id_1", - "exitSpan.s": "$new_span_id_2", - "X-INSTANA-T out": "8448eb211c80319c", - "X-INSTANA-S out": "$new_span_id_2", - "X-INSTANA-L out": "1", - "traceparent out": "00-0af7651916cd43dd8448eb211c80319c-$new_span_id_2-01", - "tracestate out": "in=8448eb211c80319c;$new_span_id_2", + "suppressed": true, + "X-INSTANA-L out": "0", + "traceparent out": "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-00", "index": 34 }, { @@ -774,4 +771,4 @@ "tracestate out": "in=fa2375d711a4ca0f;$new_span_id_2", "index": 42 } -] \ No newline at end of file +] diff --git a/packages/collector/test/integration/misc/w3c_trace_context/test_base.js b/packages/collector/test/integration/misc/w3c_trace_context/test_base.js index 89e082e93b..de90250c39 100644 --- a/packages/collector/test/integration/misc/w3c_trace_context/test_base.js +++ b/packages/collector/test/integration/misc/w3c_trace_context/test_base.js @@ -169,21 +169,73 @@ module.exports = function () { }) ); - // First request to Instana already has spec headers with sampled=0, simulating a (spec) trace in progress where - // the most recent upstream service did not record tracing data. We still expect Instana to continue the W3C - // trace with the W3C trace ID from traceparent (the parent element has not been recorded, but other ancestor - // elements might have been recorded). We also expect the correct spec headers to be passed downstream by the - // Instana service. In particular, it should keep the same trace ID it received when passing down spec headers. - // Furthermore, the random trace ID flag should be set since it was set in the incoming header. - it('Instana continues a spec trace with sampled=0 (upstream sets the random trace ID flag)', () => + // W3C sampled=0 (flag 02: sampled=0, random-trace-id=1), no X-INSTANA-L + it('Instana respects W3C sampled=0 flag (with random-trace-id flag)', () => startRequest({ app: instanaAppControls, depth: 1, withSpecHeaders: 'valid-not-sampled-with-random-trace-id' + }) + .then(response => { + response = response && response.body ? JSON.parse(response.body) : response; + expect(response.instanaHeaders).to.be.an('object'); + expect(response.instanaHeaders.t).to.not.exist; + expect(response.instanaHeaders.s).to.not.exist; + expect(response.instanaHeaders.l).to.equal('0'); + const traceparent = response.w3cTraceContext.receivedHeaders.traceparent; + const tracestate = response.w3cTraceContext.receivedHeaders.tracestate; + // sampled=0 is passed down, random-trace-id flag is preserved (flag 02) + expect(traceparent).to.match(new RegExp(`00-${foreignTraceId}-${foreignParentId}-02`)); + expect(tracestate).to.equal('thing=foo,bar=baz'); + }) + .then(() => delay(500)) + .then(() => + agentControls.getSpans().then(spans => { + expect(spans).to.have.lengthOf(0); + }) + )); + + // W3C sampled=0 (flag 00: sampled=0, random-trace-id=0), no X-INSTANA-L + it('Instana respects W3C sampled=0 flag (without random-trace-id flag)', () => + startRequest({ + app: instanaAppControls, + depth: 1, + withSpecHeaders: 'valid-not-sampled-no-random-trace-id' + }) + .then(response => { + response = response && response.body ? JSON.parse(response.body) : response; + expect(response.instanaHeaders).to.be.an('object'); + expect(response.instanaHeaders.t).to.not.exist; + expect(response.instanaHeaders.s).to.not.exist; + // X-INSTANA-L: 0 is passed down (derived from W3C sampled=0) + expect(response.instanaHeaders.l).to.equal('0'); + expect(response.w3cTraceContext).to.be.an('object'); + expect(response.w3cTraceContext.receivedHeaders).to.be.an('object'); + const traceparent = response.w3cTraceContext.receivedHeaders.traceparent; + const tracestate = response.w3cTraceContext.receivedHeaders.tracestate; + // sampled=0 is passed down (flag stays 00) + expect(traceparent).to.match(new RegExp(`00-${foreignTraceId}-${foreignParentId}-00`)); + expect(tracestate).to.equal('thing=foo,bar=baz'); + }) + // give spans a chance to come in + .then(() => delay(500)) + .then(() => + // verify there are no spans + agentControls.getSpans().then(spans => { + expect(spans).to.have.lengthOf(0); + }) + )); + + // W3C sampled=1, no X-INSTANA-L: Should trace normally + it('Instana respects incoming W3C sampled=1 flag and traces (no X-INSTANA-L)', () => + startRequest({ + app: instanaAppControls, + depth: 1, + withSpecHeaders: 'valid-sampled-no-random-trace-id' }).then(response => { const { traceparent, tracestate } = getSpecHeadersFromFinalHttpRequest(response); return retryUntilSpansMatch(agentControls, spans => { - const instanaHttpEntryRoot = verifyHttpEntry({ + const instanaHttpEntry = verifyHttpEntry({ spans, instanaAppControls, parentSpan: { @@ -194,44 +246,117 @@ module.exports = function () { usedTraceParent: true, longTraceId: foreignTraceId }); - const instanaHttpExit = verifyHttpExit(spans, instanaHttpEntryRoot, '/end'); + const instanaHttpExit = verifyHttpExit(spans, instanaHttpEntry, '/end'); + // W3C sampled=1 is respected: spans created, sampled=1 passed downstream const instanaExitSpanId = instanaHttpExit.s; - expect(traceparent).to.match(new RegExp(`00-${foreignTraceId}-${instanaExitSpanId}-03`)); + expect(traceparent).to.match(new RegExp(`00-${foreignTraceId}-${instanaExitSpanId}-01`)); expect(tracestate).to.match(new RegExp(`in=${foreignTraceIdRightHalf};${instanaExitSpanId}`)); }); })); - // First request to Instana already has spec headers with sampled=0, simulating a (spec) trace in progress where - // the most recent upstream service did not record tracing data. We still expect Instana to continue the W3C - // trace with the W3C trace ID from traceparent (the parent element has not been recorded, but other ancestor - // elements might have been recorded). We also expect the correct spec headers to be passed downstream by the - // Instana service. In particular, it should keep the same trace ID it received when passing down spec headers. - // Furthermore, the random trace ID flag should be unset since it was unset in the incoming header. - it('Instana continues a spec trace with sampled=0 (upstream does not set the random trace ID flag)', () => + // W3C sampled=0 + X-INSTANA-L: 0: Both say suppress, should suppress + it('W3C sampled=0 + X-INSTANA-L=0: suppresses tracing (both agree)', () => startRequest({ app: instanaAppControls, depth: 1, - withSpecHeaders: 'valid-not-sampled-no-random-trace-id' + withSpecHeaders: 'valid-not-sampled-no-random-trace-id', + withInstanaHeaders: 'suppress' + }) + .then(response => { + response = response && response.body ? JSON.parse(response.body) : response; + expect(response.instanaHeaders.t).to.not.exist; + expect(response.instanaHeaders.s).to.not.exist; + expect(response.instanaHeaders.l).to.equal('0'); + const traceparent = response.w3cTraceContext.receivedHeaders.traceparent; + expect(traceparent).to.match(new RegExp(`00-${foreignTraceId}-${foreignParentId}-00`)); + }) + .then(() => delay(500)) + .then(() => + agentControls.getSpans().then(spans => { + expect(spans).to.have.lengthOf(0); + }) + )); + + // W3C sampled=0 + X-INSTANA-L: 1: X-INSTANA-L wins, should trace + it('W3C sampled=0 + X-INSTANA-L=1: traces anyway (X-INSTANA-L has priority)', () => + startRequest({ + app: instanaAppControls, + depth: 1, + withSpecHeaders: 'valid-not-sampled-no-random-trace-id', + withInstanaHeaders: 'trace-in-progress' }).then(response => { const { traceparent, tracestate } = getSpecHeadersFromFinalHttpRequest(response); return retryUntilSpansMatch(agentControls, spans => { - const instanaHttpEntryRoot = verifyHttpEntry({ + const instanaHttpEntry = verifyHttpEntry({ + instanaAppControls, spans, + parentSpan: { + t: upstreamInstanaTraceId, + s: upstreamInstanaParentId + }, + url: '/start' + }); + const instanaHttpExit = verifyHttpExit(spans, instanaHttpEntry, '/end'); + + // X-INSTANA-L: 1 overrides W3C sampled=0, so sampled=1 is passed down + const instanaExitSpanId = instanaHttpExit.s; + expect(traceparent).to.match(new RegExp(`00-${foreignTraceId}-${instanaExitSpanId}-01`)); + expect(tracestate).to.match(new RegExp(`in=${upstreamInstanaTraceId};${instanaExitSpanId}`)); + }); + })); + + // W3C sampled=1 + X-INSTANA-L: 0: X-INSTANA-L wins, should suppress + it('W3C sampled=1 + X-INSTANA-L=0: suppresses tracing (X-INSTANA-L has priority)', () => + startRequest({ + app: instanaAppControls, + depth: 1, + withSpecHeaders: 'valid-sampled-no-random-trace-id', + withInstanaHeaders: 'suppress' + }) + .then(response => { + response = response && response.body ? JSON.parse(response.body) : response; + expect(response.instanaHeaders.t).to.not.exist; + expect(response.instanaHeaders.s).to.not.exist; + expect(response.instanaHeaders.l).to.equal('0'); + const traceparent = response.w3cTraceContext.receivedHeaders.traceparent; + // X-INSTANA-L: 0 overrides W3C sampled=1, so sampled=0 is passed down + const traceParentMatch = new RegExp(`00-${foreignTraceId}-([0-9a-f]{16})-00`).exec(traceparent); + expect(traceParentMatch).to.exist; + expect(traceParentMatch[1]).to.not.equal(foreignParentId); + }) + .then(() => delay(500)) + .then(() => + agentControls.getSpans().then(spans => { + expect(spans).to.have.lengthOf(0); + }) + )); + + // W3C sampled=1 + X-INSTANA-L: 1: Both say trace, should trace + it('W3C sampled=1 + X-INSTANA-L=1: traces (both agree)', () => + startRequest({ + app: instanaAppControls, + depth: 1, + withSpecHeaders: 'valid-sampled-no-random-trace-id', + withInstanaHeaders: 'trace-in-progress' + }).then(response => { + const { traceparent, tracestate } = getSpecHeadersFromFinalHttpRequest(response); + return retryUntilSpansMatch(agentControls, spans => { + const instanaHttpEntry = verifyHttpEntry({ instanaAppControls, + spans, parentSpan: { - t: foreignTraceIdRightHalf, - s: foreignParentId + t: upstreamInstanaTraceId, + s: upstreamInstanaParentId }, - url: '/start', - usedTraceParent: true, - longTraceId: foreignTraceId + url: '/start' }); - const instanaHttpExit = verifyHttpExit(spans, instanaHttpEntryRoot, '/end'); + const instanaHttpExit = verifyHttpExit(spans, instanaHttpEntry, '/end'); + // Both agree on tracing, sampled=1 is passed down const instanaExitSpanId = instanaHttpExit.s; expect(traceparent).to.match(new RegExp(`00-${foreignTraceId}-${instanaExitSpanId}-01`)); - expect(tracestate).to.match(new RegExp(`in=${foreignTraceIdRightHalf};${instanaExitSpanId}`)); + expect(tracestate).to.match(new RegExp(`in=${upstreamInstanaTraceId};${instanaExitSpanId}`)); }); })); diff --git a/packages/core/src/tracing/tracingHeaders.js b/packages/core/src/tracing/tracingHeaders.js index f28e367bbf..faeb849586 100644 --- a/packages/core/src/tracing/tracingHeaders.js +++ b/packages/core/src/tracing/tracingHeaders.js @@ -171,18 +171,25 @@ exports.fromHeaders = function fromHeaders(headers) { // If w3cTraceContext has no instanaTraceId/instanaParentId yet, it will get one as soon as we start a span and // upate it. In case we received X-INSTANA-L: 0 we will not start a span, but we will make sure to toggle the // sampled flag in traceparent off. + + // CASE: Respect W3C sampled flag (only if X-INSTANA-L header is not present, as Instana headers have priority) + const hasInstanaLevelHeader = level != null; + const w3cSampledFlagSuppressed = w3cTraceContext.sampled === false && !hasInstanaLevelHeader; + const effectiveLevel = w3cSampledFlagSuppressed ? '0' : level; + const shouldSuppress = isSuppressed(effectiveLevel); + let instanaAncestor; - if (traceStateHasInstanaKeyValuePair(w3cTraceContext) && !isSuppressed(level)) { + if (traceStateHasInstanaKeyValuePair(w3cTraceContext) && !shouldSuppress) { instanaAncestor = { t: w3cTraceContext.instanaTraceId, p: w3cTraceContext.instanaParentId }; } return exports.limitTraceId({ - traceId: !isSuppressed(level) ? w3cTraceContext.traceParentTraceId : null, - parentId: !isSuppressed(level) ? w3cTraceContext.traceParentParentId : null, - usedTraceParent: !isSuppressed(level), - level, + traceId: !shouldSuppress ? w3cTraceContext.traceParentTraceId : null, + parentId: !shouldSuppress ? w3cTraceContext.traceParentParentId : null, + usedTraceParent: !shouldSuppress, + level: effectiveLevel, correlationType, correlationId, synthetic,