Skip to content

Commit cf8c072

Browse files
authored
ref(nextjs): Set propagation context for tracing (#8426)
1 parent e087afe commit cf8c072

File tree

6 files changed

+62
-65
lines changed

6 files changed

+62
-65
lines changed

packages/nextjs/src/client/performance.ts

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
11
import { getCurrentHub } from '@sentry/core';
22
import { WINDOW } from '@sentry/react';
3-
import type { Primitive, TraceparentData, Transaction, TransactionContext, TransactionSource } from '@sentry/types';
4-
import {
5-
baggageHeaderToDynamicSamplingContext,
6-
extractTraceparentData,
7-
logger,
8-
stripUrlQueryAndFragment,
9-
} from '@sentry/utils';
3+
import type { Primitive, Transaction, TransactionContext, TransactionSource } from '@sentry/types';
4+
import { logger, stripUrlQueryAndFragment, tracingContextFromHeaders } from '@sentry/utils';
105
import type { NEXT_DATA as NextData } from 'next/dist/next-server/lib/utils';
116
import { default as Router } from 'next/router';
127
import type { ParsedUrlQuery } from 'querystring';
@@ -37,9 +32,9 @@ interface SentryEnhancedNextData extends NextData {
3732

3833
interface NextDataTagInfo {
3934
route?: string;
40-
traceParentData?: TraceparentData;
41-
baggage?: string;
4235
params?: ParsedUrlQuery;
36+
sentryTrace?: string;
37+
baggage?: string;
4338
}
4439

4540
/**
@@ -83,13 +78,8 @@ function extractNextDataTagInformation(): NextDataTagInfo {
8378
nextDataTagInfo.params = query;
8479

8580
if (props && props.pageProps) {
86-
if (props.pageProps._sentryBaggage) {
87-
nextDataTagInfo.baggage = props.pageProps._sentryBaggage;
88-
}
89-
90-
if (props.pageProps._sentryTraceData) {
91-
nextDataTagInfo.traceParentData = extractTraceparentData(props.pageProps._sentryTraceData);
92-
}
81+
nextDataTagInfo.sentryTrace = props.pageProps._sentryTraceData;
82+
nextDataTagInfo.baggage = props.pageProps._sentryBaggage;
9383
}
9484

9585
return nextDataTagInfo;
@@ -121,22 +111,26 @@ export function nextRouterInstrumentation(
121111
startTransactionOnPageLoad: boolean = true,
122112
startTransactionOnLocationChange: boolean = true,
123113
): void {
124-
const { route, traceParentData, baggage, params } = extractNextDataTagInformation();
114+
const { route, params, sentryTrace, baggage } = extractNextDataTagInformation();
115+
const { traceparentData, dynamicSamplingContext, propagationContext } = tracingContextFromHeaders(
116+
sentryTrace,
117+
baggage,
118+
);
119+
120+
getCurrentHub().getScope().setPropagationContext(propagationContext);
125121
prevLocationName = route || globalObject.location.pathname;
126122

127123
if (startTransactionOnPageLoad) {
128124
const source = route ? 'route' : 'url';
129-
const dynamicSamplingContext = baggageHeaderToDynamicSamplingContext(baggage);
130-
131125
activeTransaction = startTransactionCb({
132126
name: prevLocationName,
133127
op: 'pageload',
134128
tags: DEFAULT_TAGS,
135129
...(params && client && client.getOptions().sendDefaultPii && { data: params }),
136-
...traceParentData,
130+
...traceparentData,
137131
metadata: {
138132
source,
139-
dynamicSamplingContext: traceParentData && !dynamicSamplingContext ? {} : dynamicSamplingContext,
133+
dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext,
140134
},
141135
});
142136
}

packages/nextjs/src/edge/utils/edgeWrapperUtils.ts

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
import { captureException, getCurrentHub, hasTracingEnabled, startTransaction } from '@sentry/core';
22
import type { Span } from '@sentry/types';
3-
import {
4-
addExceptionMechanism,
5-
baggageHeaderToDynamicSamplingContext,
6-
extractTraceparentData,
7-
logger,
8-
objectify,
9-
} from '@sentry/utils';
3+
import { addExceptionMechanism, logger, objectify, tracingContextFromHeaders } from '@sentry/utils';
104

115
import type { EdgeRouteHandler } from '../types';
126
import { flush } from './flush';
@@ -32,17 +26,17 @@ export function withEdgeWrapping<H extends EdgeRouteHandler>(
3226
op: options.spanOp,
3327
});
3428
} else if (req instanceof Request) {
35-
// If there is a trace header set, extract the data from it (parentSpanId, traceId, and sampling decision)
36-
let traceparentData;
37-
38-
const sentryTraceHeader = req.headers.get('sentry-trace');
39-
if (sentryTraceHeader) {
40-
traceparentData = extractTraceparentData(sentryTraceHeader);
41-
__DEBUG_BUILD__ && logger.log(`[Tracing] Continuing trace ${traceparentData?.traceId}.`);
29+
const sentryTrace = req.headers.get('sentry-trace') || '';
30+
const baggage = req.headers.get('baggage');
31+
const { traceparentData, dynamicSamplingContext, propagationContext } = tracingContextFromHeaders(
32+
sentryTrace,
33+
baggage,
34+
);
35+
currentScope.setPropagationContext(propagationContext);
36+
if (traceparentData) {
37+
__DEBUG_BUILD__ && logger.log(`[Tracing] Continuing trace ${traceparentData.traceId}.`);
4238
}
4339

44-
const dynamicSamplingContext = baggageHeaderToDynamicSamplingContext(req.headers.get('baggage'));
45-
4640
span = startTransaction({
4741
name: options.spanDescription,
4842
op: options.spanOp,

packages/nextjs/src/server/utils/wrapperUtils.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
startTransaction,
77
} from '@sentry/core';
88
import type { Span, Transaction } from '@sentry/types';
9-
import { baggageHeaderToDynamicSamplingContext, extractTraceparentData } from '@sentry/utils';
9+
import { isString, tracingContextFromHeaders } from '@sentry/utils';
1010
import type { IncomingMessage, ServerResponse } from 'http';
1111

1212
import { platformSupportsStreaming } from './platformSupportsStreaming';
@@ -38,6 +38,7 @@ function setTransactionOnRequest(transaction: Transaction, req: IncomingMessage)
3838
*
3939
* Note: This function turns the wrapped function into an asynchronous one.
4040
*/
41+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
4142
export function withErrorInstrumentation<F extends (...args: any[]) => any>(
4243
origFunction: F,
4344
): (...params: Parameters<F>) => Promise<ReturnType<F>> {
@@ -66,6 +67,7 @@ export function withErrorInstrumentation<F extends (...args: any[]) => any>(
6667
* @param options Options providing details for the created transaction and span.
6768
* @returns what the data fetching method call returned.
6869
*/
70+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
6971
export function withTracedServerSideDataFetcher<F extends (...args: any[]) => Promise<any> | any>(
7072
origDataFetcher: F,
7173
req: IncomingMessage,
@@ -86,12 +88,14 @@ export function withTracedServerSideDataFetcher<F extends (...args: any[]) => Pr
8688
const previousSpan: Span | undefined = getTransactionFromRequest(req) ?? scope.getSpan();
8789
let dataFetcherSpan;
8890

89-
const sentryTraceHeader = req.headers['sentry-trace'];
90-
const rawBaggageString = req.headers && req.headers.baggage;
91-
const traceparentData =
92-
typeof sentryTraceHeader === 'string' ? extractTraceparentData(sentryTraceHeader) : undefined;
93-
94-
const dynamicSamplingContext = baggageHeaderToDynamicSamplingContext(rawBaggageString);
91+
const sentryTrace =
92+
req.headers && isString(req.headers['sentry-trace']) ? req.headers['sentry-trace'] : undefined;
93+
const baggage = req.headers?.baggage;
94+
const { traceparentData, dynamicSamplingContext, propagationContext } = tracingContextFromHeaders(
95+
sentryTrace,
96+
baggage,
97+
);
98+
scope.setPropagationContext(propagationContext);
9599

96100
if (platformSupportsStreaming()) {
97101
let spanToContinue: Span;

packages/nextjs/src/server/wrapApiHandlerWithSentry.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,11 @@ import { captureException, startTransaction } from '@sentry/node';
33
import type { Transaction } from '@sentry/types';
44
import {
55
addExceptionMechanism,
6-
baggageHeaderToDynamicSamplingContext,
7-
extractTraceparentData,
86
isString,
97
logger,
108
objectify,
119
stripUrlQueryAndFragment,
10+
tracingContextFromHeaders,
1211
} from '@sentry/utils';
1312

1413
import type { AugmentedNextApiRequest, AugmentedNextApiResponse, NextApiHandler } from './types';
@@ -73,15 +72,18 @@ export function withSentry(apiHandler: NextApiHandler, parameterizedRoute?: stri
7372
currentScope.setSDKProcessingMetadata({ request: req });
7473

7574
if (hasTracingEnabled(options) && options?.instrumenter === 'sentry') {
76-
// If there is a trace header set, extract the data from it (parentSpanId, traceId, and sampling decision)
77-
let traceparentData;
78-
if (req.headers && isString(req.headers['sentry-trace'])) {
79-
traceparentData = extractTraceparentData(req.headers['sentry-trace']);
80-
__DEBUG_BUILD__ && logger.log(`[Tracing] Continuing trace ${traceparentData?.traceId}.`);
81-
}
75+
const sentryTrace =
76+
req.headers && isString(req.headers['sentry-trace']) ? req.headers['sentry-trace'] : undefined;
77+
const baggage = req.headers?.baggage;
78+
const { traceparentData, dynamicSamplingContext, propagationContext } = tracingContextFromHeaders(
79+
sentryTrace,
80+
baggage,
81+
);
82+
hub.getScope().setPropagationContext(propagationContext);
8283

83-
const baggageHeader = req.headers && req.headers.baggage;
84-
const dynamicSamplingContext = baggageHeaderToDynamicSamplingContext(baggageHeader);
84+
if (__DEBUG_BUILD__ && traceparentData) {
85+
logger.log(`[Tracing] Continuing trace ${traceparentData.traceId}.`);
86+
}
8587

8688
// prefer the parameterized route, if we have it (which we will if we've auto-wrapped the route handler)
8789
let reqPath = parameterizedRoute;

packages/nextjs/src/server/wrapServerComponentWithSentry.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@ import {
55
runWithAsyncContext,
66
startTransaction,
77
} from '@sentry/core';
8-
import { baggageHeaderToDynamicSamplingContext, extractTraceparentData } from '@sentry/utils';
8+
import { tracingContextFromHeaders } from '@sentry/utils';
99

1010
import { isNotFoundNavigationError, isRedirectNavigationError } from '../common/nextNavigationErrorUtils';
1111
import type { ServerComponentContext } from '../common/types';
1212

1313
/**
1414
* Wraps an `app` directory server component with Sentry error instrumentation.
1515
*/
16+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
1617
export function wrapServerComponentWithSentry<F extends (...args: any[]) => any>(
1718
appDirComponent: F,
1819
context: ServerComponentContext,
@@ -28,14 +29,15 @@ export function wrapServerComponentWithSentry<F extends (...args: any[]) => any>
2829
apply: (originalFunction, thisArg, args) => {
2930
return runWithAsyncContext(() => {
3031
const hub = getCurrentHub();
32+
const currentScope = hub.getScope();
3133

3234
let maybePromiseResult;
3335

34-
const traceparentData = context.sentryTraceHeader
35-
? extractTraceparentData(context.sentryTraceHeader)
36-
: undefined;
37-
38-
const dynamicSamplingContext = baggageHeaderToDynamicSamplingContext(context.baggageHeader);
36+
const { traceparentData, dynamicSamplingContext, propagationContext } = tracingContextFromHeaders(
37+
context.sentryTraceHeader,
38+
context.baggageHeader,
39+
);
40+
currentScope.setPropagationContext(propagationContext);
3941

4042
const transaction = startTransaction({
4143
op: 'function.nextjs',
@@ -48,7 +50,6 @@ export function wrapServerComponentWithSentry<F extends (...args: any[]) => any>
4850
},
4951
});
5052

51-
const currentScope = hub.getScope();
5253
if (currentScope) {
5354
currentScope.setSpan(transaction);
5455
}

packages/utils/src/tracing.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@ export const TRACEPARENT_REGEXP = new RegExp(
1818
*
1919
* @returns Object containing data from the header, or undefined if traceparent string is malformed
2020
*/
21-
export function extractTraceparentData(traceparent: string): TraceparentData | undefined {
22-
const matches = traceparent.match(TRACEPARENT_REGEXP);
21+
export function extractTraceparentData(traceparent?: string): TraceparentData | undefined {
22+
if (!traceparent) {
23+
return undefined;
24+
}
2325

24-
if (!traceparent || !matches) {
25-
// empty string or no matches is invalid traceparent data
26+
const matches = traceparent.match(TRACEPARENT_REGEXP);
27+
if (!matches) {
2628
return undefined;
2729
}
2830

0 commit comments

Comments
 (0)