Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 49 additions & 18 deletions packages/nextjs/src/server/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { context } from '@opentelemetry/api';
import {
ATTR_HTTP_REQUEST_METHOD,
ATTR_HTTP_ROUTE,
ATTR_URL_QUERY,
SEMATTRS_HTTP_METHOD,
Expand Down Expand Up @@ -160,33 +159,65 @@ export function init(options: NodeOptions): NodeClient | undefined {

client?.on('spanStart', span => {
const spanAttributes = spanToJSON(span).data;
const spanType = spanAttributes?.['next.span_type'] as string | undefined;
let functionType = '';

if (spanType) {
const [category, functionName] = spanType.split('.') as [string, string];

const isNextjsSpan = [
'BaseServer',
'LoadComponents',
'NextServer',
'NextNodeServer',
'StartServer',
'Render',
'Router',
'AppRender',
'Node',
'AppRouteRouteHandlers',
'ResolveMetadata',
'Middleware',
'NextNodeServerSpan',
].includes(category);

if (isNextjsSpan && functionName) {
functionType = functionName;

if (category === 'NextNodeServerSpan' && functionName === 'clientComponentLoading') {
functionType = 'Component';
} else if (category === 'NextNodeServer' && functionName === 'findPageComponents') {
functionType = 'Page';
} else if (category === 'NextNodeServerSpan' && functionName === 'getLayoutOrPageModule') {
if (spanAttributes?.['next.segment'] === '__LAYOUT__') {
functionType = 'Layout';
} else {
functionType = 'Page';
}
}
}
}

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

// Only hoist the http.route attribute if the transaction doesn't already have it
if (
// eslint-disable-next-line deprecation/deprecation
(rootSpanAttributes?.[ATTR_HTTP_REQUEST_METHOD] || rootSpanAttributes?.[SEMATTRS_HTTP_METHOD]) &&
!rootSpanAttributes?.[ATTR_HTTP_ROUTE]
) {
const route = spanAttributes['next.route'].replace(/\/route$/, '');
rootSpan.updateName(route);
rootSpan.setAttribute(ATTR_HTTP_ROUTE, route);
if (functionType) {
let route = '/';
const rawRoute = spanAttributes?.['next.route'];
if (typeof rawRoute === 'string') {
route = rawRoute;
}

span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'function.nextjs');
span.setAttribute('sentry.nextjs.function.type', functionType);
span.setAttribute('sentry.nextjs.function.route', route);
}

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

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

const isolationScope = (scopes.isolationScope || getIsolationScope()).clone();
Expand Down
Loading