Skip to content

Commit 84d9bd7

Browse files
author
Luca Forstner
committed
logic
1 parent d4aa8f1 commit 84d9bd7

File tree

6 files changed

+39
-25
lines changed

6 files changed

+39
-25
lines changed

dev-packages/e2e-tests/test-applications/nextjs-15/app/pageload-tracing/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export default async function Page() {
66
}
77

88
export async function generateMetadata() {
9-
(await fetch('http://example.com/')).text();
9+
(await fetch('http://example.com/', { cache: 'no-store' })).text();
1010

1111
return {
1212
title: 'my title',

dev-packages/e2e-tests/test-applications/nextjs-15/tests/pageload-tracing.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { expect, test } from '@playwright/test';
22
import { waitForTransaction } from '@sentry-internal/test-utils';
33

4-
test('all server component transactions should be attached to the pageload request span', async ({ page }) => {
4+
test('App router transactions should be attached to the pageload request span', async ({ page }) => {
55
const serverTransactionPromise = waitForTransaction('nextjs-15', async transactionEvent => {
66
return transactionEvent?.transaction === 'GET /pageload-tracing';
77
});

dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/client-app-routing-instrumentation.test.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,6 @@ test('Creates a navigation transaction for app router routes', async ({ page })
3838
});
3939

4040
const serverComponentTransactionPromise = waitForTransaction('nextjs-app-dir', async transactionEvent => {
41-
console.log({
42-
t: transactionEvent.transaction,
43-
tid: transactionEvent.contexts?.trace?.trace_id,
44-
ctid: (await clientNavigationTransactionPromise).contexts?.trace?.trace_id,
45-
});
4641
return (
4742
transactionEvent?.transaction === 'GET /server-component/parameter/foo/bar/baz' &&
4843
(await clientNavigationTransactionPromise).contexts?.trace?.trace_id ===

packages/nextjs/src/common/wrapGenerationFunctionWithSentry.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ import {
1515
withScope,
1616
} from '@sentry/core';
1717
import type { WebFetchHeaders } from '@sentry/types';
18-
import { winterCGHeadersToDict } from '@sentry/utils';
18+
import { propagationContextFromHeaders, uuid4, winterCGHeadersToDict } from '@sentry/utils';
1919

2020
import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core';
2121
import type { GenerationFunctionContext } from '../common/types';
2222
import { isNotFoundNavigationError, isRedirectNavigationError } from './nextNavigationErrorUtils';
23-
import { commonObjectToIsolationScope } from './utils/tracingUtils';
23+
import { commonObjectToIsolationScope, commonObjectToPropagationContext } from './utils/tracingUtils';
2424

2525
/**
2626
* Wraps a generation function (e.g. generateMetadata) with Sentry error and performance instrumentation.
@@ -33,6 +33,7 @@ export function wrapGenerationFunctionWithSentry<F extends (...args: any[]) => a
3333
const { requestAsyncStorage, componentRoute, componentType, generationFunctionIdentifier } = context;
3434
return new Proxy(generationFunction, {
3535
apply: (originalFunction, thisArg, args) => {
36+
const requestTraceId = getActiveSpan()?.spanContext().traceId;
3637
let headers: WebFetchHeaders | undefined = undefined;
3738
// We try-catch here just in case anything goes wrong with the async storage here goes wrong since it is Next.js internal API
3839
try {
@@ -74,6 +75,17 @@ export function wrapGenerationFunctionWithSentry<F extends (...args: any[]) => a
7475
},
7576
});
7677

78+
const propagationContext = commonObjectToPropagationContext(
79+
headers,
80+
headersDict?.['sentry-trace']
81+
? propagationContextFromHeaders(headersDict['sentry-trace'], headersDict['baggage'])
82+
: {
83+
traceId: requestTraceId || uuid4(),
84+
spanId: uuid4().substring(16),
85+
},
86+
);
87+
scope.setPropagationContext(propagationContext);
88+
7789
scope.setExtra('route_data', data);
7890

7991
return startSpanManual(

packages/nextjs/src/common/wrapServerComponentWithSentry.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ import {
1313
withIsolationScope,
1414
withScope,
1515
} from '@sentry/core';
16-
import { winterCGHeadersToDict } from '@sentry/utils';
16+
import { propagationContextFromHeaders, uuid4, winterCGHeadersToDict } from '@sentry/utils';
1717

1818
import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core';
1919
import { isNotFoundNavigationError, isRedirectNavigationError } from '../common/nextNavigationErrorUtils';
2020
import type { ServerComponentContext } from '../common/types';
2121
import { flushSafelyWithTimeout } from './utils/responseEnd';
22-
import { commonObjectToIsolationScope } from './utils/tracingUtils';
22+
import { commonObjectToIsolationScope, commonObjectToPropagationContext } from './utils/tracingUtils';
2323
import { vercelWaitUntil } from './utils/vercelWaitUntil';
2424

2525
/**
@@ -36,6 +36,7 @@ export function wrapServerComponentWithSentry<F extends (...args: any[]) => any>
3636
// hook. 🤯
3737
return new Proxy(appDirComponent, {
3838
apply: (originalFunction, thisArg, args) => {
39+
const requestTraceId = getActiveSpan()?.spanContext().traceId;
3940
const isolationScope = commonObjectToIsolationScope(context.headers);
4041

4142
const activeSpan = getActiveSpan();
@@ -59,6 +60,21 @@ export function wrapServerComponentWithSentry<F extends (...args: any[]) => any>
5960
return withIsolationScope(isolationScope, () => {
6061
return withScope(scope => {
6162
scope.setTransactionName(`${componentType} Server Component (${componentRoute})`);
63+
64+
if (process.env.NEXT_RUNTIME === 'edge') {
65+
const propagationContext = commonObjectToPropagationContext(
66+
context.headers,
67+
headersDict?.['sentry-trace']
68+
? propagationContextFromHeaders(headersDict['sentry-trace'], headersDict['baggage'])
69+
: {
70+
traceId: requestTraceId || uuid4(),
71+
spanId: uuid4().substring(16),
72+
},
73+
);
74+
75+
scope.setPropagationContext(propagationContext);
76+
}
77+
6278
return startSpanManual(
6379
{
6480
op: 'function.nextjs',

packages/nextjs/src/server/index.ts

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { applySdkMetadata, getClient, getGlobalScope } from '@sentry/core';
1+
import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, applySdkMetadata, getClient, getGlobalScope } from '@sentry/core';
22
import { getDefaultIntegrations, init as nodeInit } from '@sentry/node';
33
import type { NodeClient, NodeOptions } from '@sentry/node';
44
import { GLOBAL_OBJ, logger } from '@sentry/utils';
@@ -86,14 +86,7 @@ export function init(options: NodeOptions): NodeClient | undefined {
8686
return;
8787
}
8888

89-
const customDefaultIntegrations = [
90-
...getDefaultIntegrations(options).filter(
91-
integration =>
92-
// Next.js comes with its own Http instrumentation for OTel which would lead to double spans for route handler requests
93-
integration.name !== 'Http',
94-
),
95-
httpIntegration(),
96-
];
89+
const customDefaultIntegrations = getDefaultIntegrations(options);
9790

9891
// Turn off Next.js' own fetch instrumentation
9992
// https://github.com/lforst/nextjs-fork/blob/1994fd186defda77ad971c36dc3163db263c993f/packages/next/src/server/lib/patch-fetch.ts#L245
@@ -128,7 +121,6 @@ export function init(options: NodeOptions): NodeClient | undefined {
128121
applySdkMetadata(opts, 'nextjs', ['nextjs', 'node']);
129122

130123
const client = nodeInit(opts);
131-
132124
// If we encounter a span emitted by Next.js, we do not want to sample it
133125
// The reason for this is that the data quality of the spans varies, it is different per version of Next,
134126
// and we need to keep our manual instrumentation around for the edge runtime anyhow.
@@ -160,11 +152,10 @@ export function init(options: NodeOptions): NodeClient | undefined {
160152
return null;
161153
}
162154

163-
// 'BaseServer.handleRequest' spans are the only Next.js spans we sample. However, we only want to actually keep these spans/transactions,
164-
// when they server RSCs (app router routes). We mark these transactions with a "sentry.rsc" attribute in our RSC wrappers so we can
165-
// filter them here.
155+
// We only want to use our HTTP integration/instrumentation for app router requests, which are marked with the `sentry.rsc` attribute.
166156
if (
167-
event.contexts?.trace?.data?.['next.span_type'] === 'BaseServer.handleRequest' &&
157+
(event.contexts?.trace?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] === 'auto.http.otel.http' ||
158+
event.contexts?.trace?.data?.['next.span_type'] === 'BaseServer.handleRequest') &&
168159
event.contexts?.trace?.data?.['sentry.rsc'] !== true
169160
) {
170161
return null;

0 commit comments

Comments
 (0)