diff --git a/packages/nextjs/src/server/serverSpanHooks.ts b/packages/nextjs/src/server/serverSpanHooks.ts index 3d21bb094eae..3b61fe3b66d0 100644 --- a/packages/nextjs/src/server/serverSpanHooks.ts +++ b/packages/nextjs/src/server/serverSpanHooks.ts @@ -36,6 +36,7 @@ import { } from './vercelQueuesMonitoring'; const NEXTJS_ERROR_PAGE_ROUTES = ['/500', '/_error']; +const SENTRY_INGEST_QUERY = /sentry_key.*sentry_client|sentry_client.*sentry_key/; export const NEXTJS_SERVER_IGNORE_SPANS: NonNullable = [ // Static assets (matches `_next/static` anywhere in the name to handle custom basePath) @@ -51,32 +52,18 @@ export const NEXTJS_SERVER_IGNORE_SPANS: NonNullable = [ // Spans flagged via TRANSACTION_ATTR_SHOULD_DROP_TRANSACTION // (set in `dropMiddlewareTunnelRequests` during `spanStart`) { attributes: { [TRANSACTION_ATTR_SHOULD_DROP_TRANSACTION]: true } }, + // Requests to Sentry ingest that the Node.js server forwards to the Edge Runtime server (e.g. in middleware). These + // happen outside of userland, so we never get the chance to `suppressTracing` them. + // eslint-disable-next-line typescript/no-deprecated + { attributes: { [HTTP_TARGET]: SENTRY_INGEST_QUERY } }, + // oxlint-disable-next-line sdk/no-unfiltered-url-attributes -- a pattern to match, not a collected value + { attributes: { [URL_QUERY]: SENTRY_INGEST_QUERY } }, ]; /** * 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 diff --git a/packages/nextjs/test/server/serverSpanHooks.test.ts b/packages/nextjs/test/server/serverSpanHooks.test.ts index aef1a76dd6d2..e64337ff6352 100644 --- a/packages/nextjs/test/server/serverSpanHooks.test.ts +++ b/packages/nextjs/test/server/serverSpanHooks.test.ts @@ -1,16 +1,18 @@ import { HTTP_REQUEST_METHOD, HTTP_ROUTE, + HTTP_TARGET, SENTRY_OP, SENTRY_ORIGIN, SENTRY_SEGMENT_NAME_SOURCE, + URL_QUERY, } from '@sentry/conventions/attributes'; import { MIDDLEWARE } from '@sentry/conventions/op'; import type { Client } from '@sentry/core'; -import { addChildSpanToSpan, SentrySpan, spanToStaticSpanJSON } from '@sentry/core'; +import { addChildSpanToSpan, SentrySpan, shouldIgnoreSpan, spanToStaticSpanJSON } from '@sentry/core'; import { describe, expect, it } from 'vitest'; import { ATTR_NEXT_SPAN_NAME, ATTR_NEXT_SPAN_TYPE } from '../../src/common/nextSpanAttributes'; -import { handleOnSpanStart } from '../../src/server/serverSpanHooks'; +import { handleOnSpanStart, NEXTJS_SERVER_IGNORE_SPANS } from '../../src/server/serverSpanHooks'; const client = { getOptions: () => ({}) } as unknown as Client; @@ -171,3 +173,22 @@ describe('handleOnSpanStart', () => { expect(spanToStaticSpanJSON(rootSpan).description).toBe('GET /api/endpoint'); }); }); + +describe('NEXTJS_SERVER_IGNORE_SPANS', () => { + const isIgnored = (attributes: Record): boolean => + shouldIgnoreSpan({ description: 'GET /api/envelope', op: 'http.server', attributes }, NEXTJS_SERVER_IGNORE_SPANS); + + it.each([ + // eslint-disable-next-line typescript/no-deprecated + [HTTP_TARGET, '/api/1/envelope/?sentry_key=abc&sentry_client=sentry.javascript.nextjs'], + [URL_QUERY, '?sentry_client=sentry.javascript.nextjs&sentry_key=abc'], + ])('ignores a request to Sentry ingest by %s', (attribute, value) => { + expect(isIgnored({ [attribute]: value })).toBe(true); + }); + + it('keeps a request that has only one of the Sentry ingest query parameters', () => { + // eslint-disable-next-line typescript/no-deprecated + expect(isIgnored({ [HTTP_TARGET]: '/api/envelope?sentry_key=abc' })).toBe(false); + expect(isIgnored({ [URL_QUERY]: '?sentry_client=abc' })).toBe(false); + }); +});