Skip to content

Commit

Permalink
ref(nextjs): Set propagation context for tracing (#8426)
Browse files Browse the repository at this point in the history
  • Loading branch information
AbhiPrasad authored Jun 30, 2023
1 parent e087afe commit cf8c072
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 65 deletions.
36 changes: 15 additions & 21 deletions packages/nextjs/src/client/performance.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import { getCurrentHub } from '@sentry/core';
import { WINDOW } from '@sentry/react';
import type { Primitive, TraceparentData, Transaction, TransactionContext, TransactionSource } from '@sentry/types';
import {
baggageHeaderToDynamicSamplingContext,
extractTraceparentData,
logger,
stripUrlQueryAndFragment,
} from '@sentry/utils';
import type { Primitive, Transaction, TransactionContext, TransactionSource } from '@sentry/types';
import { logger, stripUrlQueryAndFragment, tracingContextFromHeaders } from '@sentry/utils';
import type { NEXT_DATA as NextData } from 'next/dist/next-server/lib/utils';
import { default as Router } from 'next/router';
import type { ParsedUrlQuery } from 'querystring';
Expand Down Expand Up @@ -37,9 +32,9 @@ interface SentryEnhancedNextData extends NextData {

interface NextDataTagInfo {
route?: string;
traceParentData?: TraceparentData;
baggage?: string;
params?: ParsedUrlQuery;
sentryTrace?: string;
baggage?: string;
}

/**
Expand Down Expand Up @@ -83,13 +78,8 @@ function extractNextDataTagInformation(): NextDataTagInfo {
nextDataTagInfo.params = query;

if (props && props.pageProps) {
if (props.pageProps._sentryBaggage) {
nextDataTagInfo.baggage = props.pageProps._sentryBaggage;
}

if (props.pageProps._sentryTraceData) {
nextDataTagInfo.traceParentData = extractTraceparentData(props.pageProps._sentryTraceData);
}
nextDataTagInfo.sentryTrace = props.pageProps._sentryTraceData;
nextDataTagInfo.baggage = props.pageProps._sentryBaggage;
}

return nextDataTagInfo;
Expand Down Expand Up @@ -121,22 +111,26 @@ export function nextRouterInstrumentation(
startTransactionOnPageLoad: boolean = true,
startTransactionOnLocationChange: boolean = true,
): void {
const { route, traceParentData, baggage, params } = extractNextDataTagInformation();
const { route, params, sentryTrace, baggage } = extractNextDataTagInformation();
const { traceparentData, dynamicSamplingContext, propagationContext } = tracingContextFromHeaders(
sentryTrace,
baggage,
);

getCurrentHub().getScope().setPropagationContext(propagationContext);
prevLocationName = route || globalObject.location.pathname;

if (startTransactionOnPageLoad) {
const source = route ? 'route' : 'url';
const dynamicSamplingContext = baggageHeaderToDynamicSamplingContext(baggage);

activeTransaction = startTransactionCb({
name: prevLocationName,
op: 'pageload',
tags: DEFAULT_TAGS,
...(params && client && client.getOptions().sendDefaultPii && { data: params }),
...traceParentData,
...traceparentData,
metadata: {
source,
dynamicSamplingContext: traceParentData && !dynamicSamplingContext ? {} : dynamicSamplingContext,
dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext,
},
});
}
Expand Down
26 changes: 10 additions & 16 deletions packages/nextjs/src/edge/utils/edgeWrapperUtils.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
import { captureException, getCurrentHub, hasTracingEnabled, startTransaction } from '@sentry/core';
import type { Span } from '@sentry/types';
import {
addExceptionMechanism,
baggageHeaderToDynamicSamplingContext,
extractTraceparentData,
logger,
objectify,
} from '@sentry/utils';
import { addExceptionMechanism, logger, objectify, tracingContextFromHeaders } from '@sentry/utils';

import type { EdgeRouteHandler } from '../types';
import { flush } from './flush';
Expand All @@ -32,17 +26,17 @@ export function withEdgeWrapping<H extends EdgeRouteHandler>(
op: options.spanOp,
});
} else if (req instanceof Request) {
// If there is a trace header set, extract the data from it (parentSpanId, traceId, and sampling decision)
let traceparentData;

const sentryTraceHeader = req.headers.get('sentry-trace');
if (sentryTraceHeader) {
traceparentData = extractTraceparentData(sentryTraceHeader);
__DEBUG_BUILD__ && logger.log(`[Tracing] Continuing trace ${traceparentData?.traceId}.`);
const sentryTrace = req.headers.get('sentry-trace') || '';
const baggage = req.headers.get('baggage');
const { traceparentData, dynamicSamplingContext, propagationContext } = tracingContextFromHeaders(
sentryTrace,
baggage,
);
currentScope.setPropagationContext(propagationContext);
if (traceparentData) {
__DEBUG_BUILD__ && logger.log(`[Tracing] Continuing trace ${traceparentData.traceId}.`);
}

const dynamicSamplingContext = baggageHeaderToDynamicSamplingContext(req.headers.get('baggage'));

span = startTransaction({
name: options.spanDescription,
op: options.spanOp,
Expand Down
18 changes: 11 additions & 7 deletions packages/nextjs/src/server/utils/wrapperUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
startTransaction,
} from '@sentry/core';
import type { Span, Transaction } from '@sentry/types';
import { baggageHeaderToDynamicSamplingContext, extractTraceparentData } from '@sentry/utils';
import { isString, tracingContextFromHeaders } from '@sentry/utils';
import type { IncomingMessage, ServerResponse } from 'http';

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

const sentryTraceHeader = req.headers['sentry-trace'];
const rawBaggageString = req.headers && req.headers.baggage;
const traceparentData =
typeof sentryTraceHeader === 'string' ? extractTraceparentData(sentryTraceHeader) : undefined;

const dynamicSamplingContext = baggageHeaderToDynamicSamplingContext(rawBaggageString);
const sentryTrace =
req.headers && isString(req.headers['sentry-trace']) ? req.headers['sentry-trace'] : undefined;
const baggage = req.headers?.baggage;
const { traceparentData, dynamicSamplingContext, propagationContext } = tracingContextFromHeaders(
sentryTrace,
baggage,
);
scope.setPropagationContext(propagationContext);

if (platformSupportsStreaming()) {
let spanToContinue: Span;
Expand Down
22 changes: 12 additions & 10 deletions packages/nextjs/src/server/wrapApiHandlerWithSentry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ import { captureException, startTransaction } from '@sentry/node';
import type { Transaction } from '@sentry/types';
import {
addExceptionMechanism,
baggageHeaderToDynamicSamplingContext,
extractTraceparentData,
isString,
logger,
objectify,
stripUrlQueryAndFragment,
tracingContextFromHeaders,
} from '@sentry/utils';

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

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

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

// prefer the parameterized route, if we have it (which we will if we've auto-wrapped the route handler)
let reqPath = parameterizedRoute;
Expand Down
15 changes: 8 additions & 7 deletions packages/nextjs/src/server/wrapServerComponentWithSentry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import {
runWithAsyncContext,
startTransaction,
} from '@sentry/core';
import { baggageHeaderToDynamicSamplingContext, extractTraceparentData } from '@sentry/utils';
import { tracingContextFromHeaders } from '@sentry/utils';

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

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

let maybePromiseResult;

const traceparentData = context.sentryTraceHeader
? extractTraceparentData(context.sentryTraceHeader)
: undefined;

const dynamicSamplingContext = baggageHeaderToDynamicSamplingContext(context.baggageHeader);
const { traceparentData, dynamicSamplingContext, propagationContext } = tracingContextFromHeaders(
context.sentryTraceHeader,
context.baggageHeader,
);
currentScope.setPropagationContext(propagationContext);

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

const currentScope = hub.getScope();
if (currentScope) {
currentScope.setSpan(transaction);
}
Expand Down
10 changes: 6 additions & 4 deletions packages/utils/src/tracing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ export const TRACEPARENT_REGEXP = new RegExp(
*
* @returns Object containing data from the header, or undefined if traceparent string is malformed
*/
export function extractTraceparentData(traceparent: string): TraceparentData | undefined {
const matches = traceparent.match(TRACEPARENT_REGEXP);
export function extractTraceparentData(traceparent?: string): TraceparentData | undefined {
if (!traceparent) {
return undefined;
}

if (!traceparent || !matches) {
// empty string or no matches is invalid traceparent data
const matches = traceparent.match(TRACEPARENT_REGEXP);
if (!matches) {
return undefined;
}

Expand Down

0 comments on commit cf8c072

Please sign in to comment.