Skip to content

Commit 7e1a641

Browse files
Lms24andreiborza
andauthored
feat(core): Add getTraceData function (#13134)
Add a `getTraceData` function to the `@sentry/core` package and re-exports it in none-browser SDKs inheriting from it. --------- Co-authored-by: Andrei <[email protected]>
1 parent 2b7fa21 commit 7e1a641

File tree

16 files changed

+167
-129
lines changed

16 files changed

+167
-129
lines changed

packages/astro/src/index.server.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export {
5555
getSentryRelease,
5656
getSpanDescendants,
5757
getSpanStatusFromHttpCode,
58+
getTraceData,
5859
graphqlIntegration,
5960
hapiIntegration,
6061
httpIntegration,

packages/astro/src/server/meta.ts

Lines changed: 0 additions & 86 deletions
This file was deleted.

packages/astro/src/server/middleware.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import type { Client, Scope, Span, SpanAttributes } from '@sentry/types';
1414
import { addNonEnumerableProperty, objectify, stripUrlQueryAndFragment } from '@sentry/utils';
1515
import type { APIContext, MiddlewareResponseHandler } from 'astro';
1616

17-
import { getTracingMetaTags } from './meta';
17+
import { getTraceData } from '@sentry/node';
1818

1919
type MiddlewareOptions = {
2020
/**
@@ -189,9 +189,17 @@ function addMetaTagToHead(htmlChunk: string, scope: Scope, client: Client, span?
189189
if (typeof htmlChunk !== 'string') {
190190
return htmlChunk;
191191
}
192+
const { 'sentry-trace': sentryTrace, baggage } = getTraceData(span, scope, client);
193+
194+
if (!sentryTrace) {
195+
return htmlChunk;
196+
}
197+
198+
const sentryTraceMeta = `<meta name="sentry-trace" content="${sentryTrace}"/>`;
199+
const baggageMeta = baggage && `<meta name="baggage" content="${baggage}"/>`;
200+
201+
const content = `<head>\n${sentryTraceMeta}`.concat(baggageMeta ? `\n${baggageMeta}` : '', '\n');
192202

193-
const { sentryTrace, baggage } = getTracingMetaTags(span, scope, client);
194-
const content = `<head>\n${sentryTrace}\n${baggage}\n`;
195203
return htmlChunk.replace('<head>', content);
196204
}
197205

packages/astro/test/integration/middleware/index.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { describe, expect, it, vi } from 'vitest';
33
import { onRequest } from '../../../src/integration/middleware';
44

55
vi.mock('../../../src/server/meta', () => ({
6-
getTracingMetaTags: () => ({
6+
getTracingMetaTagValues: () => ({
77
sentryTrace: '<meta name="sentry-trace" content="123">',
88
baggage: '<meta name="baggage" content="abc">',
99
}),

packages/astro/test/server/middleware.test.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core';
2+
import * as SentryCore from '@sentry/core';
23
import * as SentryNode from '@sentry/node';
34
import type { Client, Span } from '@sentry/types';
4-
import { vi } from 'vitest';
5+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
56

67
import { handleRequest, interpolateRouteFromUrlAndParams } from '../../src/server/middleware';
78

89
vi.mock('../../src/server/meta', () => ({
9-
getTracingMetaTags: () => ({
10+
getTracingMetaTagValues: () => ({
1011
sentryTrace: '<meta name="sentry-trace" content="123">',
1112
baggage: '<meta name="baggage" content="abc">',
1213
}),
@@ -28,10 +29,18 @@ describe('sentryMiddleware', () => {
2829
setPropagationContext: vi.fn(),
2930
getSpan: getSpanMock,
3031
setSDKProcessingMetadata: setSDKProcessingMetadataMock,
32+
getPropagationContext: () => ({}),
3133
} as any;
3234
});
3335
vi.spyOn(SentryNode, 'getActiveSpan').mockImplementation(getSpanMock);
3436
vi.spyOn(SentryNode, 'getClient').mockImplementation(() => ({}) as Client);
37+
vi.spyOn(SentryNode, 'getTraceData').mockImplementation(() => ({
38+
'sentry-trace': '123',
39+
baggage: 'abc',
40+
}));
41+
vi.spyOn(SentryCore, 'getDynamicSamplingContextFromSpan').mockImplementation(() => ({
42+
transaction: 'test',
43+
}));
3544
});
3645

3746
const nextResult = Promise.resolve(new Response(null, { status: 200, headers: new Headers() }));

packages/aws-serverless/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export {
2020
getCurrentScope,
2121
getGlobalScope,
2222
getIsolationScope,
23+
getTraceData,
2324
setCurrentClient,
2425
Scope,
2526
SDK_VERSION,

packages/bun/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export {
4040
getCurrentScope,
4141
getGlobalScope,
4242
getIsolationScope,
43+
getTraceData,
4344
setCurrentClient,
4445
Scope,
4546
SDK_VERSION,

packages/cloudflare/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export {
5555
setMeasurement,
5656
getActiveSpan,
5757
getRootSpan,
58+
getTraceData,
5859
startSpan,
5960
startInactiveSpan,
6061
startSpanManual,

packages/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ export {
8282
} from './utils/spanUtils';
8383
export { parseSampleRate } from './utils/parseSampleRate';
8484
export { applySdkMetadata } from './utils/sdkMetadata';
85+
export { getTraceData } from './utils/traceData';
8586
export { DEFAULT_ENVIRONMENT } from './constants';
8687
export { addBreadcrumb } from './breadcrumbs';
8788
export { functionToStringIntegration } from './integrations/functiontostring';

packages/core/src/utils/traceData.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import type { Client, Scope, Span } from '@sentry/types';
2+
import {
3+
TRACEPARENT_REGEXP,
4+
dynamicSamplingContextToSentryBaggageHeader,
5+
generateSentryTraceHeader,
6+
logger,
7+
} from '@sentry/utils';
8+
import { getClient, getCurrentScope } from '../currentScopes';
9+
import { getDynamicSamplingContextFromClient, getDynamicSamplingContextFromSpan } from '../tracing';
10+
import { getActiveSpan, getRootSpan, spanToTraceHeader } from './spanUtils';
11+
12+
type TraceData = {
13+
'sentry-trace'?: string;
14+
baggage?: string;
15+
};
16+
17+
/**
18+
* Extracts trace propagation data from the current span or from the client's scope (via transaction or propagation
19+
* context) and serializes it to `sentry-trace` and `baggage` values to strings. These values can be used to propagate
20+
* a trace via our tracing Http headers or Html `<meta>` tags.
21+
*
22+
* This function also applies some validation to the generated sentry-trace and baggage values to ensure that
23+
* only valid strings are returned.
24+
*
25+
* @param span a span to take the trace data from. By default, the currently active span is used.
26+
* @param scope the scope to take trace data from By default, the active current scope is used.
27+
* @param client the SDK's client to take trace data from. By default, the current client is used.
28+
*
29+
* @returns an object with the tracing data values. The object keys are the name of the tracing key to be used as header
30+
* or meta tag name.
31+
*/
32+
export function getTraceData(span?: Span, scope?: Scope, client?: Client): TraceData {
33+
const clientToUse = client || getClient();
34+
const scopeToUse = scope || getCurrentScope();
35+
const spanToUse = span || getActiveSpan();
36+
37+
const { dsc, sampled, traceId } = scopeToUse.getPropagationContext();
38+
const rootSpan = spanToUse && getRootSpan(spanToUse);
39+
40+
const sentryTrace = spanToUse ? spanToTraceHeader(spanToUse) : generateSentryTraceHeader(traceId, undefined, sampled);
41+
42+
const dynamicSamplingContext = rootSpan
43+
? getDynamicSamplingContextFromSpan(rootSpan)
44+
: dsc
45+
? dsc
46+
: clientToUse
47+
? getDynamicSamplingContextFromClient(traceId, clientToUse)
48+
: undefined;
49+
50+
const baggage = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext);
51+
52+
const isValidSentryTraceHeader = TRACEPARENT_REGEXP.test(sentryTrace);
53+
if (!isValidSentryTraceHeader) {
54+
logger.warn('Invalid sentry-trace data. Cannot generate trace data');
55+
return {};
56+
}
57+
58+
const validBaggage = isValidBaggageString(baggage);
59+
if (!validBaggage) {
60+
logger.warn('Invalid baggage data. Not returning "baggage" value');
61+
}
62+
63+
return {
64+
'sentry-trace': sentryTrace,
65+
...(validBaggage && { baggage }),
66+
};
67+
}
68+
69+
/**
70+
* Tests string against baggage spec as defined in:
71+
*
72+
* - W3C Baggage grammar: https://www.w3.org/TR/baggage/#definition
73+
* - RFC7230 token definition: https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6
74+
*
75+
* exported for testing
76+
*/
77+
export function isValidBaggageString(baggage?: string): boolean {
78+
if (!baggage || !baggage.length) {
79+
return false;
80+
}
81+
const keyRegex = "[-!#$%&'*+.^_`|~A-Za-z0-9]+";
82+
const valueRegex = '[!#-+-./0-9:<=>?@A-Z\\[\\]a-z{-}]+';
83+
const spaces = '\\s*';
84+
// eslint-disable-next-line @sentry-internal/sdk/no-regexp-constructor -- RegExp for readability, no user input
85+
const baggageRegex = new RegExp(
86+
`^${keyRegex}${spaces}=${spaces}${valueRegex}(${spaces},${spaces}${keyRegex}${spaces}=${spaces}${valueRegex})*$`,
87+
);
88+
return baggageRegex.test(baggage);
89+
}

0 commit comments

Comments
 (0)