Skip to content

Commit c0ea9a6

Browse files
s1gr1dmydea
authored andcommitted
add startOrUpdateSpan function
1 parent 703db85 commit c0ea9a6

File tree

4 files changed

+79
-65
lines changed

4 files changed

+79
-65
lines changed

dev-packages/e2e-tests/test-applications/nextjs-14/tests/request-instrumentation.test.ts

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,15 @@ test('Should send a transaction with a fetch span', async ({ page }) => {
1919
}),
2020
);
2121

22-
// TODO: Uncomment the below when fixed. For whatever reason that we now have accepted, spans created with Node.js' http.get() will not attach themselves to transactions.
23-
// More info: https://github.com/getsentry/sentry-javascript/pull/11016/files#diff-10fa195789425786c6e5e769380be18790768f0b76319ee41bbb488d9fe50405R4
24-
// expect((await transactionPromise).spans).toContainEqual(
25-
// expect.objectContaining({
26-
// data: expect.objectContaining({
27-
// 'http.method': 'GET',
28-
// 'sentry.op': 'http.client',
29-
// 'sentry.origin': 'auto.http.otel.http',
30-
// }),
31-
// description: 'GET http://example.com/',
32-
// }),
33-
// );
22+
expect((await transactionPromise).spans).toContainEqual(
23+
expect.objectContaining({
24+
data: expect.objectContaining({
25+
'http.method': 'GET',
26+
'sentry.op': 'http.client',
27+
// todo: without the HTTP integration in the Next.js SDK, this is set to 'manual' -> we could rename this to be more specific
28+
'sentry.origin': 'manual',
29+
}),
30+
description: 'GET http://example.com/',
31+
}),
32+
);
3433
});

packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts

Lines changed: 57 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
SEMANTIC_ATTRIBUTE_SENTRY_OP,
23
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
34
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
45
SPAN_STATUS_ERROR,
@@ -19,42 +20,38 @@ import { platformSupportsStreaming } from './utils/platformSupportsStreaming';
1920
import { flushQueue } from './utils/responseEnd';
2021
import { withIsolationScopeOrReuseFromRootSpan } from './utils/withIsolationScopeOrReuseFromRootSpan';
2122

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,
23+
/** As our own HTTP integration is disabled (src/server/index.ts) the rootSpan comes from Next.js.
24+
* In case there is not root span, we start a new span. */
25+
function startOrUpdateSpan(
26+
spanName: string,
27+
handleResponseErrors: (rootSpan: Span) => Promise<Response>,
3028
): 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-
);
29+
const activeSpan = getActiveSpan();
30+
const rootSpan = activeSpan && getRootSpan(activeSpan);
4831

49-
try {
50-
if (rootSpan && response.status) {
51-
setHttpStatus(rootSpan, response.status);
52-
}
53-
} catch {
54-
// best effort - response may be undefined?
55-
}
32+
if (rootSpan) {
33+
rootSpan.updateName(spanName);
34+
rootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');
35+
rootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'http.server');
36+
rootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, 'auto.function.nextjs');
5637

57-
return response;
38+
return handleResponseErrors(rootSpan);
39+
} else {
40+
return startSpan(
41+
{
42+
op: 'http.server',
43+
name: spanName,
44+
forceTransaction: true,
45+
attributes: {
46+
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
47+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs',
48+
},
49+
},
50+
(span: Span) => {
51+
return handleResponseErrors(span);
52+
},
53+
);
54+
}
5855
}
5956

6057
/**
@@ -79,27 +76,35 @@ export function wrapRouteHandlerWithSentry<F extends (...args: any[]) => any>(
7976
});
8077

8178
try {
82-
const activeSpan = getActiveSpan();
83-
const rootSpan = activeSpan && getRootSpan(activeSpan);
84-
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-
},
79+
return await startOrUpdateSpan(`${method} ${parameterizedRoute}`, async (rootSpan: Span) => {
80+
const response: Response = await handleCallbackErrors(
81+
() => originalFunction.apply(thisArg, args),
82+
error => {
83+
// Next.js throws errors when calling `redirect()`. We don't wanna report these.
84+
if (isRedirectNavigationError(error)) {
85+
// Don't do anything
86+
} else if (isNotFoundNavigationError(error) && rootSpan) {
87+
rootSpan.setStatus({ code: SPAN_STATUS_ERROR, message: 'not_found' });
88+
} else {
89+
captureException(error, {
90+
mechanism: {
91+
handled: false,
92+
},
93+
});
94+
}
9995
},
100-
async span => addSpanAttributes(originalFunction, thisArg, args, span),
10196
);
102-
}
97+
98+
try {
99+
if (rootSpan && response.status) {
100+
setHttpStatus(rootSpan, response.status);
101+
}
102+
} catch {
103+
// best effort - response may be undefined?
104+
}
105+
106+
return response;
107+
});
103108
} finally {
104109
if (!platformSupportsStreaming() || process.env.NEXT_RUNTIME === 'edge') {
105110
// 1. Edge transport requires manual flushing

packages/nextjs/src/server/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { onUncaughtExceptionIntegration } from './onUncaughtExceptionIntegration
1212

1313
export * from '@sentry/node';
1414
import type { EventProcessor } from '@sentry/types';
15+
import { requestIsolationScopeIntegration } from './requestIsolationScopeIntegration';
1516

1617
export { captureUnderscoreErrorException } from '../common/_error';
1718
export { onUncaughtExceptionIntegration } from './onUncaughtExceptionIntegration';
@@ -81,6 +82,7 @@ export function init(options: NodeOptions): void {
8182
integration.name !== 'Http',
8283
),
8384
onUncaughtExceptionIntegration(),
85+
requestIsolationScopeIntegration(),
8486
];
8587

8688
// This value is injected at build time, based on the output directory specified in the build config. Though a default

packages/opentelemetry/src/utils/mapStatus.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ const canonicalGrpcErrorCodesMap: Record<string, SpanStatus['message']> = {
2626
'16': 'unauthenticated',
2727
} as const;
2828

29+
const isStatusErrorMessageValid = (message: string): boolean => {
30+
return Object.values(canonicalGrpcErrorCodesMap).includes(message as SpanStatus['message']);
31+
};
32+
2933
/**
3034
* Get a Sentry span status from an otel span.
3135
*/
@@ -39,7 +43,11 @@ export function mapStatus(span: AbstractSpan): SpanStatus {
3943
return { code: SPAN_STATUS_OK };
4044
// If the span is already marked as erroneous we return that exact status
4145
} else if (status.code === SpanStatusCode.ERROR) {
42-
return { code: SPAN_STATUS_ERROR, message: status.message };
46+
if (typeof status.message === 'undefined' || isStatusErrorMessageValid(status.message)) {
47+
return { code: SPAN_STATUS_ERROR, message: status.message };
48+
} else {
49+
return { code: SPAN_STATUS_ERROR, message: 'unknown_error' };
50+
}
4351
}
4452
}
4553

0 commit comments

Comments
 (0)