From ad332eea67ea14796959bc96cf21e50103e55e5f Mon Sep 17 00:00:00 2001 From: louiszawadzki Date: Mon, 6 Nov 2023 16:59:37 +0100 Subject: [PATCH 1/3] Add tracestate header for w3c headers --- .../distributedTracingHeaders.ts | 45 ++++++++++++++----- .../XHRProxy/__tests__/XHRProxy.test.ts | 12 +++-- 2 files changed, 44 insertions(+), 13 deletions(-) diff --git a/packages/core/src/rum/instrumentation/resourceTracking/distributedTracing/distributedTracingHeaders.ts b/packages/core/src/rum/instrumentation/resourceTracking/distributedTracing/distributedTracingHeaders.ts index 988f41475..32b409d2f 100644 --- a/packages/core/src/rum/instrumentation/resourceTracking/distributedTracing/distributedTracingHeaders.ts +++ b/packages/core/src/rum/instrumentation/resourceTracking/distributedTracing/distributedTracingHeaders.ts @@ -24,6 +24,7 @@ export const PARENT_ID_HEADER_KEY = 'x-datadog-parent-id'; * OTel headers */ export const TRACECONTEXT_HEADER_KEY = 'traceparent'; +export const TRACESTATE_HEADER_KEY = 'tracestate'; export const B3_HEADER_KEY = 'b3'; export const B3_MULTI_TRACE_ID_HEADER_KEY = 'X-B3-TraceId'; export const B3_MULTI_SPAN_ID_HEADER_KEY = 'X-B3-SpanId'; @@ -60,16 +61,26 @@ export const getTracingHeaders = ( break; } case PropagatorType.TRACECONTEXT: { - headers.push({ - header: TRACECONTEXT_HEADER_KEY, - value: generateTraceContextHeader({ - version: '00', - traceId: tracingAttributes.traceId, - parentId: tracingAttributes.spanId, - isSampled: - tracingAttributes.samplingPriorityHeader === '1' - }) - }); + headers.push( + { + header: TRACECONTEXT_HEADER_KEY, + value: generateTraceContextHeader({ + version: '00', + traceId: tracingAttributes.traceId, + parentId: tracingAttributes.spanId, + isSampled: + tracingAttributes.samplingPriorityHeader === '1' + }) + }, + { + header: TRACESTATE_HEADER_KEY, + value: generateTraceStateHeader({ + parentId: tracingAttributes.spanId, + isSampled: + tracingAttributes.samplingPriorityHeader === '1' + }) + } + ); break; } case PropagatorType.B3: { @@ -124,6 +135,20 @@ const generateTraceContextHeader = ({ )}-${parentId.toPaddedString(16, 16)}-${flags}`; }; +const generateTraceStateHeader = ({ + parentId, + isSampled +}: { + parentId: SpanId; + isSampled: boolean; +}) => { + const sampled = `s:${isSampled ? '1' : '0'}`; + const origin = 'o:rum'; + const parent = `p:${parentId.toPaddedString(16, 16)}`; + + return `dd=${sampled};${origin};${parent}`; +}; + const generateB3Header = ({ traceId, spanId, diff --git a/packages/core/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/__tests__/XHRProxy.test.ts b/packages/core/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/__tests__/XHRProxy.test.ts index 3b39f242a..60e304edf 100644 --- a/packages/core/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/__tests__/XHRProxy.test.ts +++ b/packages/core/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/__tests__/XHRProxy.test.ts @@ -22,7 +22,8 @@ import { B3_MULTI_SPAN_ID_HEADER_KEY, B3_MULTI_SAMPLED_HEADER_KEY, ORIGIN_RUM, - ORIGIN_HEADER_KEY + ORIGIN_HEADER_KEY, + TRACESTATE_HEADER_KEY } from '../../../distributedTracing/distributedTracingHeaders'; import { firstPartyHostsRegexMapBuilder } from '../../../distributedTracing/firstPartyHosts'; import { @@ -444,8 +445,13 @@ describe('XHRProxy', () => { await flushPromises(); // THEN - const headerValue = xhr.requestHeaders[TRACECONTEXT_HEADER_KEY]; - expect(headerValue).toMatch(/^00-[0-9a-f]{32}-[0-9a-f]{16}-01$/); + const contextHeader = xhr.requestHeaders[TRACECONTEXT_HEADER_KEY]; + expect(contextHeader).toMatch(/^00-[0-9a-f]{32}-[0-9a-f]{16}-01$/); + + // Parent value of the context header is the 3rd part of it + const parentValue = contextHeader.split('-')[2]; + const stateHeader = xhr.requestHeaders[TRACESTATE_HEADER_KEY]; + expect(stateHeader).toBe(`dd=s:1;o:rum;p:${parentValue}`); }); it('adds tracecontext request headers when the host is instrumented with tracecontext and request is sampled', async () => { From f05b1a208f57ee663c01f3721837bbf6c2d4876d Mon Sep 17 00:00:00 2001 From: louiszawadzki Date: Mon, 6 Nov 2023 17:05:17 +0100 Subject: [PATCH 2/3] Add W3C to default first party hosts headers --- .../src/DdSdkReactNativeConfiguration.tsx | 5 ++++- .../src/__tests__/DdSdkReactNative.test.tsx | 20 +++++++++++++++---- .../__tests__/initializationModes.test.tsx | 7 ++++++- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/packages/core/src/DdSdkReactNativeConfiguration.tsx b/packages/core/src/DdSdkReactNativeConfiguration.tsx index a9c8e61c3..02f297ae5 100644 --- a/packages/core/src/DdSdkReactNativeConfiguration.tsx +++ b/packages/core/src/DdSdkReactNativeConfiguration.tsx @@ -75,7 +75,10 @@ export const formatFirstPartyHosts = ( if (isLegacyFirstPartyHost(host)) { return { match: host, - propagatorTypes: [PropagatorType.DATADOG] + propagatorTypes: [ + PropagatorType.DATADOG, + PropagatorType.TRACECONTEXT + ] }; } return host; diff --git a/packages/core/src/__tests__/DdSdkReactNative.test.tsx b/packages/core/src/__tests__/DdSdkReactNative.test.tsx index 097c9e96a..d3ed50f2c 100644 --- a/packages/core/src/__tests__/DdSdkReactNative.test.tsx +++ b/packages/core/src/__tests__/DdSdkReactNative.test.tsx @@ -514,8 +514,14 @@ describe('DdSdkReactNative', () => { '_dd.native_view_tracking': false, '_dd.native_interaction_tracking': false, '_dd.first_party_hosts': [ - { match: 'api.example.com', propagatorTypes: ['datadog'] }, - { match: 'something.fr', propagatorTypes: ['datadog'] } + { + match: 'api.example.com', + propagatorTypes: ['datadog', 'tracecontext'] + }, + { + match: 'something.fr', + propagatorTypes: ['datadog'] + } ] }); expect(DdRumResourceTracking.startTracking).toHaveBeenCalledTimes( @@ -524,8 +530,14 @@ describe('DdSdkReactNative', () => { expect(DdRumResourceTracking.startTracking).toHaveBeenCalledWith({ tracingSamplingRate: 42, firstPartyHosts: [ - { match: 'api.example.com', propagatorTypes: ['datadog'] }, - { match: 'something.fr', propagatorTypes: ['datadog'] } + { + match: 'api.example.com', + propagatorTypes: ['datadog', 'tracecontext'] + }, + { + match: 'something.fr', + propagatorTypes: ['datadog'] + } ] }); }); diff --git a/packages/core/src/sdk/DatadogProvider/__tests__/initializationModes.test.tsx b/packages/core/src/sdk/DatadogProvider/__tests__/initializationModes.test.tsx index 7d5eef4ab..031090d6f 100644 --- a/packages/core/src/sdk/DatadogProvider/__tests__/initializationModes.test.tsx +++ b/packages/core/src/sdk/DatadogProvider/__tests__/initializationModes.test.tsx @@ -171,7 +171,12 @@ describe('DatadogProvider', () => { expect( NativeModules.DdSdk.initialize.mock.calls[0][0] .additionalConfig['_dd.first_party_hosts'] - ).toEqual([{ match: 'api.com', propagatorTypes: ['datadog'] }]); + ).toEqual([ + { + match: 'api.com', + propagatorTypes: ['datadog', 'tracecontext'] + } + ]); expect(NativeModules.DdRum.addAction).toHaveBeenCalledTimes(1); }); }); From 660897567d89ee60205d6ba84adb3ba05b994295 Mon Sep 17 00:00:00 2001 From: louiszawadzki Date: Tue, 7 Nov 2023 10:39:22 +0100 Subject: [PATCH 3/3] Test all headers values match --- .../XHRProxy/__tests__/XHRProxy.test.ts | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/packages/core/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/__tests__/XHRProxy.test.ts b/packages/core/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/__tests__/XHRProxy.test.ts index 60e304edf..4bbfa0de7 100644 --- a/packages/core/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/__tests__/XHRProxy.test.ts +++ b/packages/core/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/__tests__/XHRProxy.test.ts @@ -55,6 +55,10 @@ const flushPromises = () => new Promise(jest.requireActual('timers').setImmediate); let xhrProxy; +const hexToDecimal = (hex: string): string => { + return BigInt(`0x${hex}`).toString(10); +}; + beforeEach(() => { DdNativeRum.startResource.mockClear(); DdNativeRum.stopResource.mockClear(); @@ -454,6 +458,67 @@ describe('XHRProxy', () => { expect(stateHeader).toBe(`dd=s:1;o:rum;p:${parentValue}`); }); + it('adds tracing headers with matching value when all headers are added', async () => { + // GIVEN + const method = 'GET'; + const url = 'https://api.example.com:443/v2/user'; + xhrProxy.onTrackingStart({ + tracingSamplingRate: 100, + firstPartyHostsRegexMap: firstPartyHostsRegexMapBuilder([ + { + match: 'example.com', + propagatorTypes: [PropagatorType.DATADOG] + }, + { + match: 'example.com', + propagatorTypes: [PropagatorType.TRACECONTEXT] + }, + { + match: 'example.com', + propagatorTypes: [PropagatorType.B3] + }, + { + match: 'example.com', + propagatorTypes: [PropagatorType.B3MULTI] + } + ]) + }); + + // WHEN + const xhr = new XMLHttpRequestMock(); + xhr.open(method, url); + xhr.send(); + xhr.notifyResponseArrived(); + xhr.complete(200, 'ok'); + await flushPromises(); + + // THEN + const datadogTraceValue = xhr.requestHeaders[TRACE_ID_HEADER_KEY]; + const datadogParentValue = xhr.requestHeaders[PARENT_ID_HEADER_KEY]; + + const contextHeader = xhr.requestHeaders[TRACECONTEXT_HEADER_KEY]; + const traceContextValue = contextHeader.split('-')[1]; + const parentContextValue = contextHeader.split('-')[2]; + + const b3MultiTraceHeader = + xhr.requestHeaders[B3_MULTI_TRACE_ID_HEADER_KEY]; + const b3MultiParentHeader = + xhr.requestHeaders[B3_MULTI_SPAN_ID_HEADER_KEY]; + + const b3Header = xhr.requestHeaders[B3_HEADER_KEY]; + const traceB3Value = b3Header.split('-')[0]; + const parentB3Value = b3Header.split('-')[1]; + + expect(hexToDecimal(traceContextValue)).toBe(datadogTraceValue); + expect(hexToDecimal(parentContextValue)).toBe(datadogParentValue); + + expect(hexToDecimal(b3MultiTraceHeader)).toBe(datadogTraceValue); + expect(hexToDecimal(b3MultiParentHeader)).toBe(datadogParentValue); + + expect(hexToDecimal(traceB3Value)).toBe(datadogTraceValue); + expect(hexToDecimal(parentB3Value)).toBe(datadogParentValue); + }); + it('adds tracecontext request headers when the host is instrumented with tracecontext and request is sampled', async () => { // GIVEN const method = 'GET';