Skip to content

Commit 3e322bd

Browse files
committed
start span if there is none
1 parent 00df1f3 commit 3e322bd

File tree

2 files changed

+62
-28
lines changed

2 files changed

+62
-28
lines changed

dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/route-handlers.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ test('Should record exceptions and transactions for faulty route handlers', asyn
5050

5151
expect(routehandlerTransaction.contexts?.trace?.status).toBe('unknown_error');
5252
expect(routehandlerTransaction.contexts?.trace?.op).toBe('http.server');
53+
expect(routehandlerTransaction.contexts?.trace?.origin).toBe('auto.function.nextjs');
5354

5455
expect(routehandlerError.exception?.values?.[0].value).toBe('route-handler-error');
5556
// TODO: Uncomment once we update the scope transaction name on the server side

packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts

Lines changed: 61 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,61 @@
11
import {
2+
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
3+
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
24
SPAN_STATUS_ERROR,
35
addTracingExtensions,
46
captureException,
57
getActiveSpan,
68
getRootSpan,
79
handleCallbackErrors,
810
setHttpStatus,
11+
startSpan,
912
withIsolationScope,
1013
} from '@sentry/core';
14+
import type { Span } from '@sentry/types';
1115
import { winterCGHeadersToDict } from '@sentry/utils';
1216
import { isNotFoundNavigationError, isRedirectNavigationError } from './nextNavigationErrorUtils';
1317
import type { RouteHandlerContext } from './types';
1418
import { platformSupportsStreaming } from './utils/platformSupportsStreaming';
1519
import { flushQueue } from './utils/responseEnd';
1620

21+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
22+
async function addSpanAttributes<F extends (...args: any[]) => any>(
23+
originalFunction: F,
24+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
25+
thisArg: any,
26+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
27+
args: any[],
28+
rootSpan?: Span,
29+
): Promise<Response> {
30+
const response: Response = await handleCallbackErrors(
31+
() => originalFunction.apply(thisArg, args),
32+
error => {
33+
// Next.js throws errors when calling `redirect()`. We don't wanna report these.
34+
if (isRedirectNavigationError(error)) {
35+
// Don't do anything
36+
} else if (isNotFoundNavigationError(error) && rootSpan) {
37+
rootSpan.setStatus({ code: SPAN_STATUS_ERROR, message: 'not_found' });
38+
} else {
39+
captureException(error, {
40+
mechanism: {
41+
handled: false,
42+
},
43+
});
44+
}
45+
},
46+
);
47+
48+
try {
49+
if (rootSpan && response.status) {
50+
setHttpStatus(rootSpan, response.status);
51+
}
52+
} catch {
53+
// best effort - response may be undefined?
54+
}
55+
56+
return response;
57+
}
58+
1759
/**
1860
* Wraps a Next.js route handler with performance and error instrumentation.
1961
*/
@@ -24,10 +66,10 @@ export function wrapRouteHandlerWithSentry<F extends (...args: any[]) => any>(
2466
): (...args: Parameters<F>) => ReturnType<F> extends Promise<unknown> ? ReturnType<F> : Promise<ReturnType<F>> {
2567
addTracingExtensions();
2668

27-
const { headers } = context;
69+
const { method, parameterizedRoute, headers } = context;
2870

2971
return new Proxy(routeHandler, {
30-
apply: async (originalFunction, thisArg, args) => {
72+
apply: (originalFunction, thisArg, args) => {
3173
return withIsolationScope(async isolationScope => {
3274
isolationScope.setSDKProcessingMetadata({
3375
request: {
@@ -39,33 +81,24 @@ export function wrapRouteHandlerWithSentry<F extends (...args: any[]) => any>(
3981
const activeSpan = getActiveSpan();
4082
const rootSpan = activeSpan && getRootSpan(activeSpan);
4183

42-
const response: Response = await handleCallbackErrors(
43-
() => originalFunction.apply(thisArg, args),
44-
error => {
45-
// Next.js throws errors when calling `redirect()`. We don't wanna report these.
46-
if (isRedirectNavigationError(error)) {
47-
// Don't do anything
48-
} else if (isNotFoundNavigationError(error) && rootSpan) {
49-
rootSpan.setStatus({ code: SPAN_STATUS_ERROR, message: 'not_found' });
50-
} else {
51-
captureException(error, {
52-
mechanism: {
53-
handled: false,
54-
},
55-
});
56-
}
57-
},
58-
);
59-
60-
try {
61-
if (rootSpan && response.status) {
62-
setHttpStatus(rootSpan, response.status);
63-
}
64-
} catch {
65-
// best effort - response may be undefined?
84+
if (rootSpan) {
85+
return await addSpanAttributes<F>(originalFunction, thisArg, args, rootSpan);
86+
} else {
87+
/** As our own HTTP integration is disabled (src/server/index.ts) the rootSpan comes from Next.js.
88+
* In case there is not root span, we start a new one. */
89+
return await startSpan(
90+
{
91+
op: 'http.server',
92+
name: `${method} ${parameterizedRoute}`,
93+
forceTransaction: true,
94+
attributes: {
95+
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
96+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs',
97+
},
98+
},
99+
async span => addSpanAttributes(originalFunction, thisArg, args, span),
100+
);
66101
}
67-
68-
return response;
69102
} finally {
70103
if (!platformSupportsStreaming() || process.env.NEXT_RUNTIME === 'edge') {
71104
// 1. Edge transport requires manual flushing

0 commit comments

Comments
 (0)