Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(nextjs): Use preprocessEvent hook to improve span data #14007

Merged
merged 3 commits into from
Oct 17, 2024
Merged
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
128 changes: 64 additions & 64 deletions packages/nextjs/src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,22 @@ export function init(options: NodeOptions): NodeClient | undefined {
return null;
}

// Next.js 13 sometimes names the root transactions like this containing useless tracing.
if (event.transaction === 'NextServer.getRequestHandler') {
return null;
}

// Next.js 13 is not correctly picking up tracing data for trace propagation so we use a back-fill strategy
if (typeof event.contexts?.trace?.data?.[TRANSACTION_ATTR_SENTRY_TRACE_BACKFILL] === 'string') {
const traceparentData = extractTraceparentData(
event.contexts.trace.data[TRANSACTION_ATTR_SENTRY_TRACE_BACKFILL],
);

if (traceparentData?.parentSampled === false) {
return null;
}
}

return event;
} else {
return event;
Expand Down Expand Up @@ -286,78 +302,62 @@ export function init(options: NodeOptions): NodeClient | undefined {
),
);

// TODO: move this into pre-processing hook
getGlobalScope().addEventProcessor(
Object.assign(
(event => {
// Enhance route handler transactions
if (
event.type === 'transaction' &&
event.contexts?.trace?.data?.['next.span_type'] === 'BaseServer.handleRequest'
) {
event.contexts.trace.data = event.contexts.trace.data || {};
event.contexts.trace.data[SEMANTIC_ATTRIBUTE_SENTRY_OP] = 'http.server';
event.contexts.trace.op = 'http.server';

if (event.transaction) {
event.transaction = stripUrlQueryAndFragment(event.transaction);
}

// eslint-disable-next-line deprecation/deprecation
const method = event.contexts.trace.data[SEMATTRS_HTTP_METHOD];
// eslint-disable-next-line deprecation/deprecation
const target = event.contexts?.trace?.data?.[SEMATTRS_HTTP_TARGET];
const route = event.contexts.trace.data[ATTR_HTTP_ROUTE];

if (typeof method === 'string' && typeof route === 'string') {
event.transaction = `${method} ${route.replace(/\/route$/, '')}`;
event.contexts.trace.data[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] = 'route';
}
// Use the preprocessEvent hook instead of an event processor, so that the users event processors receive the most
// up-to-date value, but also so that the logic that detects changes to the transaction names to set the source to
// "custom", doesn't trigger.
client?.on('preprocessEvent', event => {
// Enhance route handler transactions
if (
event.type === 'transaction' &&
event.contexts?.trace?.data?.['next.span_type'] === 'BaseServer.handleRequest'
) {
event.contexts.trace.data = event.contexts.trace.data || {};
event.contexts.trace.data[SEMANTIC_ATTRIBUTE_SENTRY_OP] = 'http.server';
event.contexts.trace.op = 'http.server';

// backfill transaction name for pages that would otherwise contain unparameterized routes
if (event.contexts.trace.data['sentry.route_backfill'] && event.transaction !== 'GET /_app') {
event.transaction = `${method} ${event.contexts.trace.data[TRANSACTION_ATTR_SENTRY_ROUTE_BACKFILL]}`;
}
if (event.transaction) {
event.transaction = stripUrlQueryAndFragment(event.transaction);
}

// Next.js overrides transaction names for page loads that throw an error
// but we want to keep the original target name
if (event.transaction === 'GET /_error' && target) {
event.transaction = `${method ? `${method} ` : ''}${target}`;
}
}
// eslint-disable-next-line deprecation/deprecation
const method = event.contexts.trace.data[SEMATTRS_HTTP_METHOD];
// eslint-disable-next-line deprecation/deprecation
const target = event.contexts?.trace?.data?.[SEMATTRS_HTTP_TARGET];
const route = event.contexts.trace.data[ATTR_HTTP_ROUTE];

// Next.js 13 is not correctly picking up tracing data for trace propagation so we use a back-fill strategy
if (
event.type === 'transaction' &&
typeof event.contexts?.trace?.data?.[TRANSACTION_ATTR_SENTRY_TRACE_BACKFILL] === 'string'
) {
const traceparentData = extractTraceparentData(
event.contexts.trace.data[TRANSACTION_ATTR_SENTRY_TRACE_BACKFILL],
);
if (typeof method === 'string' && typeof route === 'string') {
event.transaction = `${method} ${route.replace(/\/route$/, '')}`;
event.contexts.trace.data[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] = 'route';
}

if (traceparentData?.parentSampled === false) {
return null;
}
// backfill transaction name for pages that would otherwise contain unparameterized routes
if (event.contexts.trace.data['sentry.route_backfill'] && event.transaction !== 'GET /_app') {
event.transaction = `${method} ${event.contexts.trace.data[TRANSACTION_ATTR_SENTRY_ROUTE_BACKFILL]}`;
}

if (traceparentData?.traceId) {
event.contexts.trace.trace_id = traceparentData.traceId;
}
// Next.js overrides transaction names for page loads that throw an error
// but we want to keep the original target name
if (event.transaction === 'GET /_error' && target) {
event.transaction = `${method ? `${method} ` : ''}${target}`;
}
}

if (traceparentData?.parentSpanId) {
event.contexts.trace.parent_span_id = traceparentData.parentSpanId;
}
}
// Next.js 13 is not correctly picking up tracing data for trace propagation so we use a back-fill strategy
if (
event.type === 'transaction' &&
typeof event.contexts?.trace?.data?.[TRANSACTION_ATTR_SENTRY_TRACE_BACKFILL] === 'string'
) {
const traceparentData = extractTraceparentData(event.contexts.trace.data[TRANSACTION_ATTR_SENTRY_TRACE_BACKFILL]);

// Next.js 13 sometimes names the root transactions like this containing useless tracing.
if (event.type === 'transaction' && event.transaction === 'NextServer.getRequestHandler') {
return null;
}
if (traceparentData?.traceId) {
event.contexts.trace.trace_id = traceparentData.traceId;
}

return event;
}) satisfies EventProcessor,
{ id: 'NextjsTransactionEnhancer' },
),
);
if (traceparentData?.parentSpanId) {
event.contexts.trace.parent_span_id = traceparentData.parentSpanId;
}
}
});

if (process.env.NODE_ENV === 'development') {
getGlobalScope().addEventProcessor(devErrorSymbolicationEventProcessor);
Expand Down
Loading