Skip to content
Draft
Show file tree
Hide file tree
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
81 changes: 78 additions & 3 deletions packages/nextjs/src/server/handleOnSpanStart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import {
HTTP_METHOD,
HTTP_REQUEST_METHOD,
HTTP_ROUTE,
HTTP_TARGET,
URL_QUERY,
} from '@sentry/conventions/attributes';
import type { Client, Span } from '@sentry/core';
import type { Client, Options, Span } from '@sentry/core';
import {
getIsolationScope,
getRootSpan,
Expand All @@ -13,14 +15,87 @@ import {
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
spanToJSON,
} from '@sentry/core';
import { enhanceMiddlewareRootSpan } from '../common/enhanceMiddlewareRootSpan';
import { ATTR_NEXT_ROUTE, ATTR_NEXT_SPAN_NAME, ATTR_NEXT_SPAN_TYPE } from '../common/nextSpanAttributes';
import { TRANSACTION_ATTR_SHOULD_DROP_TRANSACTION } from '../common/span-attributes-with-logic-attached';
import { addHeadersAsAttributes } from '../common/utils/addHeadersAsAttributes';
import { backfillHttpServerStatus } from '../common/utils/backfillHttpServerStatus';
import { dropMiddlewareTunnelRequests } from '../common/utils/dropMiddlewareTunnelRequests';
import { createLiveRootSpanAdapter } from '../common/utils/liveRootSpanAdapter';
import { maybeForkIsolationScopeForRootSpan } from '../common/utils/forkIsolationScopeForRootSpan';
import { setUrlProcessingMetadata } from '../common/utils/setUrlProcessingMetadata';
import { maybeEnhanceServerComponentSpanName } from '../common/utils/tracingUtils';
import { maybeStartCronCheckIn } from './vercelCronsMonitoring';
import { maybeEnrichQueueConsumerSpan, maybeEnrichQueueProducerSpan } from './vercelQueuesMonitoring';
import { enhanceHandleRequestRootSpan } from './enhanceHandleRequestRootSpan';
import { maybeCompleteCronCheckIn, maybeStartCronCheckIn } from './vercelCronsMonitoring';
import {
maybeCleanupQueueSpan,
maybeEnrichQueueConsumerSpan,
maybeEnrichQueueProducerSpan,
} from './vercelQueuesMonitoring';

export const NEXTJS_SERVER_IGNORE_SPANS: NonNullable<Options['ignoreSpans']> = [
// Static assets (matches `_next/static` anywhere in the name to handle custom basePath)
/^GET (\/.*)?\/_next\/static\//,
// Dev source-map fetch endpoints
/\/__nextjs_original-stack-frame/,
// Pages router /404
/^\/404$/,
// App router /404 and /_not-found segments (any HTTP method)
/^(GET|HEAD|POST|PUT|DELETE|CONNECT|OPTIONS|TRACE|PATCH) \/(404|_not-found)$/,
// Root transactions named "NextServer.getRequestHandler" containing useless tracing
/^NextServer\.getRequestHandler$/,
// Spans flagged via TRANSACTION_ATTR_SHOULD_DROP_TRANSACTION
// (set in `dropMiddlewareTunnelRequests` during `spanStart`)
{ attributes: { [TRANSACTION_ATTR_SHOULD_DROP_TRANSACTION]: true } },
];

/**
* Registers the hooks that name, enrich and filter the spans and events of the Next.js server on `client`.
*/
export function addNextjsServerSpanHooks(client: Client): void {
client.on('beforeSampling', ({ spanAttributes }, samplingDecision) => {
// There are situations where the Next.js Node.js server forwards requests for the Edge Runtime server (e.g. in
// middleware) and this causes spans for Sentry ingest requests to be created. These are not exempt from our tracing
// because we didn't get the chance to do `suppressTracing`, since this happens outside of userland.
// We need to drop these spans.
if (
// eslint-disable-next-line typescript/no-deprecated
(typeof spanAttributes[HTTP_TARGET] === 'string' &&
// eslint-disable-next-line typescript/no-deprecated
spanAttributes[HTTP_TARGET].includes('sentry_key') &&
// eslint-disable-next-line typescript/no-deprecated
spanAttributes[HTTP_TARGET].includes('sentry_client')) ||
(typeof spanAttributes[URL_QUERY] === 'string' &&
spanAttributes[URL_QUERY].includes('sentry_key') &&
spanAttributes[URL_QUERY].includes('sentry_client'))
) {
samplingDecision.decision = false;
}
});

client.on('spanStart', span => handleOnSpanStart(span, client));

// Normalize name/op/source/status on the request root span at span end, before it is serialized into
// a transaction event (legacy) or streamed span JSON. Running on the live span means both lifecycles
// pick up the changes from one place, and the cron/queue hooks below see the finalized status.
client.on('spanEnd', span => {
if (span !== getRootSpan(span)) {
return;
}

const mutableRootSpan = createLiveRootSpanAdapter(span);
enhanceHandleRequestRootSpan(mutableRootSpan);
enhanceMiddlewareRootSpan(mutableRootSpan);
backfillHttpServerStatus(span);
});

client.on('spanEnd', maybeCompleteCronCheckIn);
client.on('spanEnd', maybeCleanupQueueSpan);

client.on('preprocessEvent', event => {
setUrlProcessingMetadata(event);
});
}

/**
* Handles the on span start event for Next.js spans.
Expand Down
83 changes: 6 additions & 77 deletions packages/nextjs/src/server/index.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,19 @@
// import/export got a false positive, and affects most of our index barrel files
// can be removed once following issue is fixed: https://github.com/import-js/eslint-plugin-import/issues/703
/* eslint-disable import/export */
import { HTTP_TARGET, URL_QUERY } from '@sentry/conventions/attributes';
import type { EventProcessor } from '@sentry/core';
import {
applySdkMetadata,
debug,
getClient,
getGlobalScope,
getRootSpan,
getVercelEnv,
GLOBAL_OBJ,
} from '@sentry/core';
import { applySdkMetadata, debug, getClient, getGlobalScope, getVercelEnv, GLOBAL_OBJ } from '@sentry/core';
import type { NodeClient, NodeOptions } from '@sentry/node';
import { getDefaultIntegrations, httpIntegration, init as nodeInit } from '@sentry/node';
import { DEBUG_BUILD } from '../common/debug-build';
import { devErrorSymbolicationEventProcessor } from '../common/devErrorSymbolicationEventProcessor';
import { isPrerenderControlFlowError } from '../common/nextNavigationErrorUtils';
import { TRANSACTION_ATTR_SHOULD_DROP_TRANSACTION } from '../common/span-attributes-with-logic-attached';
import { isBuild } from '../common/utils/isBuild';
import { isAsyncContextOwnedByCloudflare, isCloudflareWaitUntilAvailable } from '../common/utils/responseEnd';
import { setUrlProcessingMetadata } from '../common/utils/setUrlProcessingMetadata';
import { distDirRewriteFramesIntegration } from './distDirRewriteFramesIntegration';
import { enhanceMiddlewareRootSpan } from '../common/enhanceMiddlewareRootSpan';
import { backfillHttpServerStatus } from '../common/utils/backfillHttpServerStatus';
import { createLiveRootSpanAdapter } from '../common/utils/liveRootSpanAdapter';
import { enhanceHandleRequestRootSpan } from './enhanceHandleRequestRootSpan';
import { handleOnSpanStart } from './handleOnSpanStart';
import { addNextjsServerSpanHooks, NEXTJS_SERVER_IGNORE_SPANS } from './handleOnSpanStart';
import { prepareSafeIdGeneratorContext } from './prepareSafeIdGeneratorContext';
import { nextjsUseCacheIntegration } from './useCacheInstrumentation';
import { maybeCompleteCronCheckIn } from './vercelCronsMonitoring';
import { maybeCleanupQueueSpan } from './vercelQueuesMonitoring';

export * from '@sentry/node';

Expand Down Expand Up @@ -159,22 +142,7 @@ export function init(options: NodeOptions): NodeClient | undefined {
...cloudflareConfig,
};

const nextjsIgnoreSpans: NonNullable<NodeOptions['ignoreSpans']> = [
// Static assets (matches `_next/static` anywhere in the name to handle custom basePath)
/^GET (\/.*)?\/_next\/static\//,
// Dev source-map fetch endpoints
/\/__nextjs_original-stack-frame/,
// Pages router /404
/^\/404$/,
// App router /404 and /_not-found segments (any HTTP method)
/^(GET|HEAD|POST|PUT|DELETE|CONNECT|OPTIONS|TRACE|PATCH) \/(404|_not-found)$/,
// Root transactions named "NextServer.getRequestHandler" containing useless tracing
/^NextServer\.getRequestHandler$/,
// Spans flagged via TRANSACTION_ATTR_SHOULD_DROP_TRANSACTION
// (set in `dropMiddlewareTunnelRequests` during `spanStart`)
{ attributes: { [TRANSACTION_ATTR_SHOULD_DROP_TRANSACTION]: true } },
];
opts.ignoreSpans = [...(opts.ignoreSpans || []), ...nextjsIgnoreSpans];
opts.ignoreSpans = [...(opts.ignoreSpans || []), ...NEXTJS_SERVER_IGNORE_SPANS];

if (DEBUG_BUILD && opts.debug) {
debug.enable();
Expand All @@ -195,44 +163,9 @@ export function init(options: NodeOptions): NodeClient | undefined {

const client = isOwnedByCloudflare ? undefined : nodeInit(opts);

client?.on('beforeSampling', ({ spanAttributes }, samplingDecision) => {
// There are situations where the Next.js Node.js server forwards requests for the Edge Runtime server (e.g. in
// middleware) and this causes spans for Sentry ingest requests to be created. These are not exempt from our tracing
// because we didn't get the chance to do `suppressTracing`, since this happens outside of userland.
// We need to drop these spans.
if (
// eslint-disable-next-line typescript/no-deprecated
(typeof spanAttributes[HTTP_TARGET] === 'string' &&
// eslint-disable-next-line typescript/no-deprecated
spanAttributes[HTTP_TARGET].includes('sentry_key') &&
// eslint-disable-next-line typescript/no-deprecated
spanAttributes[HTTP_TARGET].includes('sentry_client')) ||
(typeof spanAttributes[URL_QUERY] === 'string' &&
spanAttributes[URL_QUERY].includes('sentry_key') &&
spanAttributes[URL_QUERY].includes('sentry_client'))
) {
samplingDecision.decision = false;
}
});

client?.on('spanStart', span => handleOnSpanStart(span, client));

// Normalize name/op/source/status on the request root span at span end, before it is serialized into
// a transaction event (legacy) or streamed span JSON. Running on the live span means both lifecycles
// pick up the changes from one place, and the cron/queue hooks below see the finalized status.
client?.on('spanEnd', span => {
if (span !== getRootSpan(span)) {
return;
}

const mutableRootSpan = createLiveRootSpanAdapter(span);
enhanceHandleRequestRootSpan(mutableRootSpan);
enhanceMiddlewareRootSpan(mutableRootSpan);
backfillHttpServerStatus(span);
});

client?.on('spanEnd', maybeCompleteCronCheckIn);
client?.on('spanEnd', maybeCleanupQueueSpan);
if (client) {
addNextjsServerSpanHooks(client);
}

getGlobalScope().addEventProcessor(
Object.assign(
Expand Down Expand Up @@ -276,10 +209,6 @@ export function init(options: NodeOptions): NodeClient | undefined {
),
);

client?.on('preprocessEvent', event => {
setUrlProcessingMetadata(event);
});

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