Skip to content

Commit 19f0bdf

Browse files
committed
add tracestate <meta> tag handling
1 parent 1a77fb6 commit 19f0bdf

File tree

2 files changed

+60
-39
lines changed

2 files changed

+60
-39
lines changed

packages/tracing/src/browser/browsertracing.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { getGlobalObject, logger } from '@sentry/utils';
55
import { startIdleTransaction } from '../hubextensions';
66
import { DEFAULT_IDLE_TIMEOUT, IdleTransaction } from '../idletransaction';
77
import { SpanStatus } from '../spanstatus';
8-
import { extractSentrytraceData, secToMs } from '../utils';
8+
import { extractSentrytraceData, extractTracestateData, secToMs } from '../utils';
99
import { registerBackgroundTabDetection } from './backgroundtab';
1010
import { MetricsInstrumentation } from './metrics';
1111
import {
@@ -191,7 +191,7 @@ export class BrowserTracing implements Integration {
191191
// eslint-disable-next-line @typescript-eslint/unbound-method
192192
const { beforeNavigate, idleTimeout, maxTransactionDuration } = this.options;
193193

194-
const parentContextFromHeader = context.op === 'pageload' ? getHeaderContext() : undefined;
194+
const parentContextFromHeader = context.op === 'pageload' ? extractTraceDataFromMetaTags() : undefined;
195195

196196
const expandedContext = {
197197
...context,
@@ -230,14 +230,22 @@ export class BrowserTracing implements Integration {
230230
}
231231

232232
/**
233-
* Gets transaction context from a sentry-trace meta.
233+
* Gets transaction context data from `sentry-trace` and `tracestate` <meta> tags.
234234
*
235-
* @returns Transaction context data from the header or undefined if there's no header or the header is malformed
235+
* @returns Transaction context data or undefined neither tag exists or has valid data
236236
*/
237-
export function getHeaderContext(): Partial<TransactionContext> | undefined {
238-
const header = getMetaContent('sentry-trace');
239-
if (header) {
240-
return extractSentrytraceData(header);
237+
export function extractTraceDataFromMetaTags(): Partial<TransactionContext> | undefined {
238+
const sentrytraceValue = getMetaContent('sentry-trace');
239+
const tracestateValue = getMetaContent('tracestate');
240+
241+
const sentrytraceData = sentrytraceValue ? extractSentrytraceData(sentrytraceValue) : undefined;
242+
const tracestateData = tracestateValue ? extractTracestateData(tracestateValue) : undefined;
243+
244+
if (sentrytraceData || tracestateData?.sentry || tracestateData?.thirdparty) {
245+
return {
246+
...sentrytraceData,
247+
...(tracestateData && { metadata: { tracestate: tracestateData } }),
248+
};
241249
}
242250

243251
return undefined;

packages/tracing/test/browser/browsertracing.test.ts

Lines changed: 44 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
BrowserTracing,
99
BrowserTracingOptions,
1010
DEFAULT_MAX_TRANSACTION_DURATION_SECONDS,
11-
getHeaderContext,
11+
extractTraceDataFromMetaTags,
1212
getMetaContent,
1313
} from '../../src/browser/browsertracing';
1414
import { defaultRequestInstrumentationOptions } from '../../src/browser/request';
@@ -209,20 +209,23 @@ describe('BrowserTracing', () => {
209209
});
210210
});
211211

212-
it('sets transaction context from sentry-trace header', () => {
213-
const name = 'sentry-trace';
214-
const content = '126de09502ae4e0fb26c6967190756a4-b6e54397b12a2a0f-1';
215-
document.head.innerHTML = `<meta name="${name}" content="${content}">`;
212+
it('sets transaction context from <meta> tag data', () => {
213+
document.head.innerHTML =
214+
`<meta name="sentry-trace" content="12312012123120121231201212312012-1121201211212012-0"> ` +
215+
`<meta name="tracestate" content="sentry=doGsaREgReaT,maisey=silly,charlie=goofy">`;
216+
216217
const startIdleTransaction = jest.spyOn(hubExtensions, 'startIdleTransaction');
217218

218219
createBrowserTracing(true, { routingInstrumentation: customRoutingInstrumentation });
219220

220221
expect(startIdleTransaction).toHaveBeenCalledWith(
222+
// because `startIdleTransaction` uses positional arguments, we have to include placeholders for all of them
221223
expect.any(Object),
222224
expect.objectContaining({
223-
traceId: '126de09502ae4e0fb26c6967190756a4',
224-
parentSpanId: 'b6e54397b12a2a0f',
225-
parentSampled: true,
225+
traceId: '12312012123120121231201212312012',
226+
parentSpanId: '1121201211212012',
227+
parentSampled: false,
228+
metadata: { tracestate: { sentry: 'sentry=doGsaREgReaT', thirdparty: 'maisey=silly,charlie=goofy' } },
226229
}),
227230
expect.any(Number),
228231
expect.any(Boolean),
@@ -354,7 +357,7 @@ describe('BrowserTracing', () => {
354357
});
355358
});
356359

357-
describe('sentry-trace <meta> element', () => {
360+
describe('sentry-trace/tracestate <meta> tags', () => {
358361
describe('getMetaContent', () => {
359362
it('finds the specified tag and extracts the value', () => {
360363
const name = 'sentry-trace';
@@ -384,39 +387,37 @@ describe('BrowserTracing', () => {
384387
});
385388
});
386389

387-
describe('getHeaderContext', () => {
388-
it('correctly parses a valid sentry-trace meta header', () => {
389-
document.head.innerHTML = `<meta name="sentry-trace" content="12312012123120121231201212312012-1121201211212012-0">`;
390-
391-
const headerContext = getHeaderContext();
392-
393-
expect(headerContext).toBeDefined();
394-
expect(headerContext!.traceId).toEqual('12312012123120121231201212312012');
395-
expect(headerContext!.parentSpanId).toEqual('1121201211212012');
396-
expect(headerContext!.parentSampled).toEqual(false);
397-
});
398-
399-
it('returns undefined if the header is malformed', () => {
400-
document.head.innerHTML = `<meta name="sentry-trace" content="12312012-112120121-0">`;
390+
describe('extractTraceDataFromMetaTags', () => {
391+
it('correctly captures both sentry-trace and tracestate data', () => {
392+
document.head.innerHTML =
393+
`<meta name="sentry-trace" content="12312012123120121231201212312012-1121201211212012-0"> ` +
394+
`<meta name="tracestate" content="sentry=doGsaREgReaT,maisey=silly,charlie=goofy">`;
401395

402-
const headerContext = getHeaderContext();
396+
const headerContext = extractTraceDataFromMetaTags();
403397

404-
expect(headerContext).toBeUndefined();
398+
expect(headerContext).toEqual({
399+
traceId: '12312012123120121231201212312012',
400+
parentSpanId: '1121201211212012',
401+
parentSampled: false,
402+
metadata: { tracestate: { sentry: 'sentry=doGsaREgReaT', thirdparty: 'maisey=silly,charlie=goofy' } },
403+
});
405404
});
406405

407-
it("returns undefined if the header isn't there", () => {
408-
document.head.innerHTML = `<meta name="dogs" content="12312012123120121231201212312012-1121201211212012-0">`;
406+
it('returns undefined if neither tag is there', () => {
407+
document.head.innerHTML = `<meta name="cory" content="loyal"> <meta name="bodhi" content="floppy">`;
409408

410-
const headerContext = getHeaderContext();
409+
const headerContext = extractTraceDataFromMetaTags();
411410

412411
expect(headerContext).toBeUndefined();
413412
});
414413
});
415414

416-
describe('using the data', () => {
415+
describe('using <meta> tag data', () => {
417416
it('uses the data for pageload transactions', () => {
418417
// make sampled false here, so we can see that it's being used rather than the tracesSampleRate-dictated one
419-
document.head.innerHTML = `<meta name="sentry-trace" content="12312012123120121231201212312012-1121201211212012-0">`;
418+
document.head.innerHTML =
419+
`<meta name="sentry-trace" content="12312012123120121231201212312012-1121201211212012-0"> ` +
420+
`<meta name="tracestate" content="sentry=doGsaREgReaT,maisey=silly,charlie=goofy">`;
420421

421422
// pageload transactions are created as part of the BrowserTracing integration's initialization
422423
createBrowserTracing(true);
@@ -427,11 +428,20 @@ describe('BrowserTracing', () => {
427428
expect(transaction.traceId).toEqual('12312012123120121231201212312012');
428429
expect(transaction.parentSpanId).toEqual('1121201211212012');
429430
expect(transaction.sampled).toBe(false);
431+
expect(transaction.metadata?.tracestate).toEqual({
432+
sentry: 'sentry=doGsaREgReaT',
433+
thirdparty: 'maisey=silly,charlie=goofy',
434+
});
430435
});
431436

437+
// navigation transactions happen on the same page as generated a pageload transaction (with <metat> tags still
438+
// possibly present) but they start their own trace and therefore shouldn't contain the pageload transaction's
439+
// trace data
432440
it('ignores the data for navigation transactions', () => {
433441
mockChangeHistory = () => undefined;
434-
document.head.innerHTML = `<meta name="sentry-trace" content="12312012123120121231201212312012-1121201211212012-0">`;
442+
document.head.innerHTML =
443+
`<meta name="sentry-trace" content="12312012123120121231201212312012-1121201211212012-0"> ` +
444+
`<meta name="tracestate" content="sentry=doGsaREgReaT,maisey=silly,charlie=goofy">`;
435445

436446
createBrowserTracing(true);
437447

@@ -442,6 +452,9 @@ describe('BrowserTracing', () => {
442452
expect(transaction.op).toBe('navigation');
443453
expect(transaction.traceId).not.toEqual('12312012123120121231201212312012');
444454
expect(transaction.parentSpanId).toBeUndefined();
455+
expect(transaction.sampled).toBe(true);
456+
expect(transaction.metadata?.tracestate?.sentry).not.toEqual('sentry=doGsaREgReaT');
457+
expect(transaction.metadata?.tracestate?.thirdparty).toBeUndefined();
445458
});
446459
});
447460
});

0 commit comments

Comments
 (0)