Skip to content

Commit 8a03f27

Browse files
s1gr1dmydea
authored andcommitted
start span if there is none
1 parent b262a70 commit 8a03f27

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,20 +1,62 @@
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
import { withIsolationScopeOrReuseFromRootSpan } from './utils/withIsolationScopeOrReuseFromRootSpan';
1721

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

28-
const { headers } = context;
70+
const { method, parameterizedRoute, headers } = context;
2971

3072
return new Proxy(routeHandler, {
31-
apply: async (originalFunction, thisArg, args) => {
73+
apply: (originalFunction, thisArg, args) => {
3274
return withIsolationScope(async isolationScope => {
3375
isolationScope.setSDKProcessingMetadata({
3476
request: {
@@ -40,33 +82,24 @@ export function wrapRouteHandlerWithSentry<F extends (...args: any[]) => any>(
4082
const activeSpan = getActiveSpan();
4183
const rootSpan = activeSpan && getRootSpan(activeSpan);
4284

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

0 commit comments

Comments
 (0)