Skip to content

Commit 743c4eb

Browse files
authored
feat(nextjs): Attach headers using client hook (#17831)
1 parent b830cb2 commit 743c4eb

File tree

12 files changed

+74
-35
lines changed

12 files changed

+74
-35
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { NextResponse } from 'next/server';
2+
3+
export const runtime = 'edge';
4+
export const dynamic = 'force-dynamic';
5+
6+
export async function GET() {
7+
return NextResponse.json({ message: 'Hello Edge Route Handler' });
8+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { NextResponse } from 'next/server';
2+
3+
export const dynamic = 'force-dynamic';
4+
5+
export async function GET() {
6+
return NextResponse.json({ message: 'Hello Node Route Handler' });
7+
}

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

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,6 @@ test('App router transactions should be attached to the pageload request span',
2424
});
2525

2626
test('extracts HTTP request headers as span attributes', async ({ baseURL }) => {
27-
test.skip(
28-
process.env.TEST_ENV === 'prod-turbopack' || process.env.TEST_ENV === 'dev-turbopack',
29-
'Incoming fetch request headers are not added as span attributes when Turbopack is enabled (addHeadersAsAttributes)',
30-
);
31-
3227
const serverTransactionPromise = waitForTransaction('nextjs-15', async transactionEvent => {
3328
return transactionEvent?.transaction === 'GET /pageload-tracing';
3429
});
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import test, { expect } from '@playwright/test';
2+
import { waitForTransaction } from '@sentry-internal/test-utils';
3+
4+
test('Should create a transaction for node route handlers', async ({ request }) => {
5+
const routehandlerTransactionPromise = waitForTransaction('nextjs-15', async transactionEvent => {
6+
console.log(transactionEvent?.transaction);
7+
return transactionEvent?.transaction === 'GET /route-handler/[xoxo]/node';
8+
});
9+
10+
const response = await request.get('/route-handler/123/node', { headers: { 'x-charly': 'gomez' } });
11+
expect(await response.json()).toStrictEqual({ message: 'Hello Node Route Handler' });
12+
13+
const routehandlerTransaction = await routehandlerTransactionPromise;
14+
15+
expect(routehandlerTransaction.contexts?.trace?.status).toBe('ok');
16+
expect(routehandlerTransaction.contexts?.trace?.op).toBe('http.server');
17+
18+
// This is flaking on dev mode
19+
if (process.env.TEST_ENV !== 'development' && process.env.TEST_ENV !== 'dev-turbopack') {
20+
expect(routehandlerTransaction.contexts?.trace?.data?.['http.request.header.x_charly']).toBe('gomez');
21+
}
22+
});
23+
24+
test('Should create a transaction for edge route handlers', async ({ request }) => {
25+
// This test only works for webpack builds on non-async param extraction
26+
// todo: check if we can set request headers for edge on sdkProcessingMetadata
27+
test.skip();
28+
const routehandlerTransactionPromise = waitForTransaction('nextjs-15', async transactionEvent => {
29+
return transactionEvent?.transaction === 'GET /route-handler/[xoxo]/edge';
30+
});
31+
32+
const response = await request.get('/route-handler/123/edge', { headers: { 'x-charly': 'gomez' } });
33+
expect(await response.json()).toStrictEqual({ message: 'Hello Edge Route Handler' });
34+
35+
const routehandlerTransaction = await routehandlerTransactionPromise;
36+
37+
expect(routehandlerTransaction.contexts?.trace?.status).toBe('ok');
38+
expect(routehandlerTransaction.contexts?.trace?.op).toBe('http.server');
39+
expect(routehandlerTransaction.contexts?.trace?.data?.['http.request.header.x_charly']).toBe('gomez');
40+
});

packages/nextjs/src/common/pages-router-instrumentation/wrapApiHandlerWithSentry.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import {
1515
} from '@sentry/core';
1616
import type { NextApiRequest } from 'next';
1717
import type { AugmentedNextApiResponse, NextApiHandler } from '../types';
18-
import { addHeadersAsAttributes } from '../utils/addHeadersAsAttributes';
1918
import { flushSafelyWithTimeout } from '../utils/responseEnd';
2019
import { dropNextjsRootContext, escapeNextjsTracing } from '../utils/tracingUtils';
2120

@@ -88,7 +87,6 @@ export function wrapApiHandlerWithSentry(apiHandler: NextApiHandler, parameteriz
8887
attributes: {
8988
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
9089
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.nextjs',
91-
...addHeadersAsAttributes(normalizedRequest.headers || {}),
9290
},
9391
},
9492
async span => {

packages/nextjs/src/common/wrapGenerationFunctionWithSentry.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import {
2222
import type { GenerationFunctionContext } from '../common/types';
2323
import { isNotFoundNavigationError, isRedirectNavigationError } from './nextNavigationErrorUtils';
2424
import { TRANSACTION_ATTR_SENTRY_TRACE_BACKFILL } from './span-attributes-with-logic-attached';
25-
import { addHeadersAsAttributes } from './utils/addHeadersAsAttributes';
2625
import { commonObjectToIsolationScope, commonObjectToPropagationContext } from './utils/tracingUtils';
2726
import { getSanitizedRequestUrl } from './utils/urls';
2827
import { maybeExtractSynchronousParamsAndSearchParams } from './utils/wrapperUtils';
@@ -64,11 +63,6 @@ export function wrapGenerationFunctionWithSentry<F extends (...args: any[]) => a
6463

6564
const headersDict = headers ? winterCGHeadersToDict(headers) : undefined;
6665

67-
if (activeSpan) {
68-
const rootSpan = getRootSpan(activeSpan);
69-
addHeadersAsAttributes(headers, rootSpan);
70-
}
71-
7266
let data: Record<string, unknown> | undefined = undefined;
7367
if (getClient()?.getOptions().sendDefaultPii) {
7468
const props: unknown = args[0];

packages/nextjs/src/common/wrapMiddlewareWithSentry.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import {
1313
winterCGRequestToRequestData,
1414
withIsolationScope,
1515
} from '@sentry/core';
16-
import { addHeadersAsAttributes } from '../common/utils/addHeadersAsAttributes';
1716
import { flushSafelyWithTimeout } from '../common/utils/responseEnd';
1817
import type { EdgeRouteHandler } from '../edge/types';
1918

@@ -60,16 +59,13 @@ export function wrapMiddlewareWithSentry<H extends EdgeRouteHandler>(
6059

6160
let spanName: string;
6261
let spanSource: TransactionSource;
63-
let headerAttributes: Record<string, string> = {};
6462

6563
if (req instanceof Request) {
6664
isolationScope.setSDKProcessingMetadata({
6765
normalizedRequest: winterCGRequestToRequestData(req),
6866
});
6967
spanName = `middleware ${req.method} ${new URL(req.url).pathname}`;
7068
spanSource = 'url';
71-
72-
headerAttributes = addHeadersAsAttributes(req.headers);
7369
} else {
7470
spanName = 'middleware';
7571
spanSource = 'component';
@@ -88,7 +84,6 @@ export function wrapMiddlewareWithSentry<H extends EdgeRouteHandler>(
8884
const rootSpan = getRootSpan(activeSpan);
8985
if (rootSpan) {
9086
setCapturedScopesOnSpan(rootSpan, currentScope, isolationScope);
91-
rootSpan.setAttributes(headerAttributes);
9287
}
9388
}
9489

@@ -99,7 +94,6 @@ export function wrapMiddlewareWithSentry<H extends EdgeRouteHandler>(
9994
attributes: {
10095
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: spanSource,
10196
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs.wrap_middleware',
102-
...headerAttributes,
10397
},
10498
},
10599
() => {

packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import {
1919
} from '@sentry/core';
2020
import { isNotFoundNavigationError, isRedirectNavigationError } from './nextNavigationErrorUtils';
2121
import type { RouteHandlerContext } from './types';
22-
import { addHeadersAsAttributes } from './utils/addHeadersAsAttributes';
2322
import { flushSafelyWithTimeout } from './utils/responseEnd';
2423
import { commonObjectToIsolationScope } from './utils/tracingUtils';
2524

@@ -40,10 +39,6 @@ export function wrapRouteHandlerWithSentry<F extends (...args: any[]) => any>(
4039
const activeSpan = getActiveSpan();
4140
const rootSpan = activeSpan ? getRootSpan(activeSpan) : undefined;
4241

43-
if (rootSpan && process.env.NEXT_RUNTIME !== 'edge') {
44-
addHeadersAsAttributes(headers, rootSpan);
45-
}
46-
4742
let edgeRuntimeIsolationScopeOverride: Scope | undefined;
4843
if (rootSpan && process.env.NEXT_RUNTIME === 'edge') {
4944
const isolationScope = commonObjectToIsolationScope(headers);
@@ -55,7 +50,6 @@ export function wrapRouteHandlerWithSentry<F extends (...args: any[]) => any>(
5550
rootSpan.updateName(`${method} ${parameterizedRoute}`);
5651
rootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');
5752
rootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'http.server');
58-
addHeadersAsAttributes(headers, rootSpan);
5953
}
6054

6155
return withIsolationScope(

packages/nextjs/src/common/wrapServerComponentWithSentry.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import { isNotFoundNavigationError, isRedirectNavigationError } from '../common/
2424
import type { ServerComponentContext } from '../common/types';
2525
import { flushSafelyWithTimeout } from '../common/utils/responseEnd';
2626
import { TRANSACTION_ATTR_SENTRY_TRACE_BACKFILL } from './span-attributes-with-logic-attached';
27-
import { addHeadersAsAttributes } from './utils/addHeadersAsAttributes';
2827
import { commonObjectToIsolationScope, commonObjectToPropagationContext } from './utils/tracingUtils';
2928
import { getSanitizedRequestUrl } from './utils/urls';
3029
import { maybeExtractSynchronousParamsAndSearchParams } from './utils/wrapperUtils';
@@ -62,11 +61,6 @@ export function wrapServerComponentWithSentry<F extends (...args: any[]) => any>
6261

6362
const headersDict = context.headers ? winterCGHeadersToDict(context.headers) : undefined;
6463

65-
if (activeSpan) {
66-
const rootSpan = getRootSpan(activeSpan);
67-
addHeadersAsAttributes(context.headers, rootSpan);
68-
}
69-
7064
let params: Record<string, string> | undefined = undefined;
7165

7266
if (getClient()?.getOptions().sendDefaultPii) {

packages/nextjs/src/edge/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
applySdkMetadata,
33
getGlobalScope,
4+
getIsolationScope,
45
getRootSpan,
56
GLOBAL_OBJ,
67
registerSpanErrorInstrumentation,
@@ -13,6 +14,7 @@ import {
1314
} from '@sentry/core';
1415
import type { VercelEdgeOptions } from '@sentry/vercel-edge';
1516
import { getDefaultIntegrations, init as vercelEdgeInit } from '@sentry/vercel-edge';
17+
import { addHeadersAsAttributes } from '../common/utils/addHeadersAsAttributes';
1618
import { isBuild } from '../common/utils/isBuild';
1719
import { flushSafelyWithTimeout } from '../common/utils/responseEnd';
1820
import { distDirRewriteFramesIntegration } from './distDirRewriteFramesIntegration';
@@ -59,6 +61,8 @@ export function init(options: VercelEdgeOptions = {}): void {
5961

6062
client?.on('spanStart', span => {
6163
const spanAttributes = spanToJSON(span).data;
64+
const rootSpan = getRootSpan(span);
65+
const isRootSpan = span === rootSpan;
6266

6367
// Mark all spans generated by Next.js as 'auto'
6468
if (spanAttributes?.['next.span_type'] !== undefined) {
@@ -70,6 +74,12 @@ export function init(options: VercelEdgeOptions = {}): void {
7074
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'http.server.middleware');
7175
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'url');
7276
}
77+
78+
if (isRootSpan) {
79+
// todo: check if we can set request headers for edge on sdkProcessingMetadata
80+
const headers = getIsolationScope().getScopeData().sdkProcessingMetadata?.normalizedRequest?.headers;
81+
addHeadersAsAttributes(headers, rootSpan);
82+
}
7383
});
7484

7585
// Use the preprocessEvent hook instead of an event processor, so that the users event processors receive the most

0 commit comments

Comments
 (0)