Skip to content

Commit a81516b

Browse files
committed
feat(nextjs): Update Nextjs spans with more attributes for the insights page
1 parent f533f0a commit a81516b

File tree

1 file changed

+51
-17
lines changed

1 file changed

+51
-17
lines changed

packages/nextjs/src/server/index.ts

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { context } from '@opentelemetry/api';
22
import {
3-
ATTR_HTTP_REQUEST_METHOD,
43
ATTR_HTTP_ROUTE,
54
ATTR_URL_QUERY,
65
SEMATTRS_HTTP_METHOD,
@@ -160,33 +159,68 @@ export function init(options: NodeOptions): NodeClient | undefined {
160159

161160
client?.on('spanStart', span => {
162161
const spanAttributes = spanToJSON(span).data;
162+
const spanType = spanAttributes?.['next.span_type'] as string | undefined;
163+
let functionType = '';
164+
165+
if (spanType) {
166+
const [category, functionName] = spanType.split('.') as [string, string];
167+
168+
const isNextjsSpan = [
169+
'BaseServer',
170+
'LoadComponents',
171+
'NextServer',
172+
'NextNodeServer',
173+
'StartServer',
174+
'Render',
175+
'Router',
176+
'AppRender',
177+
'Node',
178+
'AppRouteRouteHandlers',
179+
'ResolveMetadata',
180+
'Middleware',
181+
'NextNodeServerSpan'
182+
].includes(category);
183+
184+
if (isNextjsSpan && functionName) {
185+
functionType = functionName;
186+
187+
if (category === 'NextNodeServerSpan' && functionName === 'clientComponentLoading') {
188+
functionType = 'Component';
189+
} else if (category === 'NextNodeServer' && functionName === 'findPageComponents') {
190+
functionType = 'Page';
191+
}
192+
else if (category === 'NextNodeServerSpan' && functionName === 'getLayoutOrPageModule') {
193+
if (spanAttributes?.['next.segment'] === '__LAYOUT__') {
194+
functionType = 'Layout';
195+
} else {
196+
functionType = 'Page';
197+
}
198+
199+
}
200+
}
201+
}
163202

164-
// What we do in this glorious piece of code, is hoist any information about parameterized routes from spans emitted
165-
// by Next.js via the `next.route` attribute, up to the transaction by setting the http.route attribute.
166-
if (typeof spanAttributes?.['next.route'] === 'string') {
167-
const rootSpan = getRootSpan(span);
168-
const rootSpanAttributes = spanToJSON(rootSpan).data;
169203

170-
// Only hoist the http.route attribute if the transaction doesn't already have it
171-
if (
172-
// eslint-disable-next-line deprecation/deprecation
173-
(rootSpanAttributes?.[ATTR_HTTP_REQUEST_METHOD] || rootSpanAttributes?.[SEMATTRS_HTTP_METHOD]) &&
174-
!rootSpanAttributes?.[ATTR_HTTP_ROUTE]
175-
) {
176-
const route = spanAttributes['next.route'].replace(/\/route$/, '');
177-
rootSpan.updateName(route);
178-
rootSpan.setAttribute(ATTR_HTTP_ROUTE, route);
204+
if (functionType) {
205+
let route = '/';
206+
const rawRoute = spanAttributes?.['next.route'];
207+
if (typeof rawRoute === 'string') {
208+
route = rawRoute;
179209
}
210+
211+
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'function.nextjs');
212+
span.setAttribute('sentry.nextjs.function.type', functionType);
213+
span.setAttribute('sentry.nextjs.function.route', route);
180214
}
181215

182216
// We want to skip span data inference for any spans generated by Next.js. Reason being that Next.js emits spans
183217
// with patterns (e.g. http.server spans) that will produce confusing data.
184-
if (spanAttributes?.['next.span_type'] !== undefined) {
218+
if (spanType !== undefined) {
185219
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, 'auto');
186220
}
187221

188222
// We want to fork the isolation scope for incoming requests
189-
if (spanAttributes?.['next.span_type'] === 'BaseServer.handleRequest' && span === getRootSpan(span)) {
223+
if (spanType === 'BaseServer.handleRequest' && span === getRootSpan(span)) {
190224
const scopes = getCapturedScopesOnSpan(span);
191225

192226
const isolationScope = (scopes.isolationScope || getIsolationScope()).clone();

0 commit comments

Comments
 (0)