diff --git a/dev-packages/e2e-tests/test-applications/nextjs-14/app/generation-functions/with-redirect/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-14/app/generation-functions/with-redirect/page.tsx deleted file mode 100644 index f1f37d7a32c6..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-14/app/generation-functions/with-redirect/page.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { redirect } from 'next/navigation'; - -export const dynamic = 'force-dynamic'; - -export default function PageWithRedirect() { - return

Hello World!

; -} - -export async function generateMetadata() { - redirect('/'); -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-14/tests/generation-functions.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-14/tests/generation-functions.test.ts index 084824824225..384e1e35055d 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-14/tests/generation-functions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-14/tests/generation-functions.test.ts @@ -108,20 +108,3 @@ test('Should send a transaction and an error event for a faulty generateViewport expect(errorEvent.transaction).toBe('Page.generateViewport (/generation-functions)'); }); - -test('Should send a transaction event with correct status for a generateMetadata() function invocation with redirect()', async ({ - page, -}) => { - const testTitle = 'redirect-foobar'; - - const transactionPromise = waitForTransaction('nextjs-14', async transactionEvent => { - return ( - transactionEvent.contexts?.trace?.data?.['http.target'] === - `/generation-functions/with-redirect?metadataTitle=${testTitle}` - ); - }); - - await page.goto(`/generation-functions/with-redirect?metadataTitle=${testTitle}`); - - expect((await transactionPromise).contexts?.trace?.status).toBe('ok'); -}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/app/route-handlers/[param]/route.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/app/route-handlers/[param]/route.ts index df5361852508..13535427a3b8 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/app/route-handlers/[param]/route.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/app/route-handlers/[param]/route.ts @@ -5,5 +5,5 @@ export async function GET() { } export async function POST() { - return NextResponse.json({ name: 'John Doe' }, { status: 403 }); + return NextResponse.json({ name: 'John Doe' }, { status: 400 }); } diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/route-handlers.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/route-handlers.test.ts index 02f2b6dc4f24..946f9e20911a 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/route-handlers.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/route-handlers.test.ts @@ -28,7 +28,7 @@ test('Should create a transaction for route handlers and correctly set span stat const routehandlerTransaction = await routehandlerTransactionPromise; - expect(routehandlerTransaction.contexts?.trace?.status).toBe('permission_denied'); + expect(routehandlerTransaction.contexts?.trace?.status).toBe('invalid_argument'); expect(routehandlerTransaction.contexts?.trace?.op).toBe('http.server'); }); diff --git a/dev-packages/e2e-tests/test-applications/node-express/src/app.ts b/dev-packages/e2e-tests/test-applications/node-express/src/app.ts index 35b21a97b9aa..9f7b0055b66d 100644 --- a/dev-packages/e2e-tests/test-applications/node-express/src/app.ts +++ b/dev-packages/e2e-tests/test-applications/node-express/src/app.ts @@ -143,8 +143,8 @@ export const appRouter = t.router({ .mutation(() => { throw new Error('I crashed in a trpc handler'); }), - unauthorized: procedure.mutation(() => { - throw new TRPCError({ code: 'UNAUTHORIZED', cause: new Error('Unauthorized') }); + badRequest: procedure.mutation(() => { + throw new TRPCError({ code: 'BAD_REQUEST', cause: new Error('Bad Request') }); }), }); diff --git a/dev-packages/e2e-tests/test-applications/node-express/tests/trpc.test.ts b/dev-packages/e2e-tests/test-applications/node-express/tests/trpc.test.ts index 3cb458f81175..e27789b7e4c5 100644 --- a/dev-packages/e2e-tests/test-applications/node-express/tests/trpc.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-express/tests/trpc.test.ts @@ -109,12 +109,12 @@ test('Should record transaction and error for a trpc handler that returns a stat const transactionEventPromise = waitForTransaction('node-express', transactionEvent => { return ( transactionEvent.transaction === 'POST /trpc' && - !!transactionEvent.spans?.find(span => span.description === 'trpc/unauthorized') + !!transactionEvent.spans?.find(span => span.description === 'trpc/badRequest') ); }); const errorEventPromise = waitForError('node-express', errorEvent => { - return !!errorEvent?.exception?.values?.some(exception => exception.value?.includes('Unauthorized')); + return !!errorEvent?.exception?.values?.some(exception => exception.value?.includes('Bad Request')); }); const trpcClient = createTRPCProxyClient({ @@ -125,7 +125,7 @@ test('Should record transaction and error for a trpc handler that returns a stat ], }); - await expect(trpcClient.unauthorized.mutate()).rejects.toBeDefined(); + await expect(trpcClient.badRequest.mutate()).rejects.toBeDefined(); await expect(transactionEventPromise).resolves.toBeDefined(); await expect(errorEventPromise).resolves.toBeDefined(); diff --git a/dev-packages/node-integration-tests/suites/express-v5/tracing/scenario-filterStatusCode.mjs b/dev-packages/node-integration-tests/suites/express-v5/tracing/scenario-filterStatusCode.mjs index f2e20014f48f..c53a72951970 100644 --- a/dev-packages/node-integration-tests/suites/express-v5/tracing/scenario-filterStatusCode.mjs +++ b/dev-packages/node-integration-tests/suites/express-v5/tracing/scenario-filterStatusCode.mjs @@ -8,6 +8,18 @@ app.get('/', (_req, res) => { res.send({ response: 'response 0' }); }); +app.get('/401', (_req, res) => { + res.status(401).send({ response: 'response 401' }); +}); + +app.get('/402', (_req, res) => { + res.status(402).send({ response: 'response 402' }); +}); + +app.get('/403', (_req, res) => { + res.status(403).send({ response: 'response 403' }); +}); + app.get('/499', (_req, res) => { res.status(499).send({ response: 'response 499' }); }); diff --git a/dev-packages/node-integration-tests/suites/express-v5/tracing/scenario.mjs b/dev-packages/node-integration-tests/suites/express-v5/tracing/scenario.mjs index fe3e190a4bdd..b0aebcbe8a79 100644 --- a/dev-packages/node-integration-tests/suites/express-v5/tracing/scenario.mjs +++ b/dev-packages/node-integration-tests/suites/express-v5/tracing/scenario.mjs @@ -15,6 +15,18 @@ app.get('/', (_req, res) => { res.send({ response: 'response 0' }); }); +app.get('/401', (_req, res) => { + res.status(401).send({ response: 'response 401' }); +}); + +app.get('/402', (_req, res) => { + res.status(402).send({ response: 'response 402' }); +}); + +app.get('/403', (_req, res) => { + res.status(403).send({ response: 'response 403' }); +}); + app.get('/test/express', (_req, res) => { res.send({ response: 'response 1' }); }); diff --git a/dev-packages/node-integration-tests/suites/express-v5/tracing/test.ts b/dev-packages/node-integration-tests/suites/express-v5/tracing/test.ts index 4618f801a087..5ed3878572b8 100644 --- a/dev-packages/node-integration-tests/suites/express-v5/tracing/test.ts +++ b/dev-packages/node-integration-tests/suites/express-v5/tracing/test.ts @@ -89,16 +89,16 @@ describe('express v5 tracing', () => { await runner.completed(); }); - test('ignores 404 routes by default', async () => { + test.each(['/401', '/402', '/403', '/does-not-exist'])('ignores %s route by default', async (url: string) => { const runner = createRunner() .expect({ - // No transaction is sent for the 404 route + // No transaction is sent for the 401, 402, 403, 404 routes transaction: { transaction: 'GET /', }, }) .start(); - runner.makeRequest('get', '/does-not-exist', { expectError: true }); + runner.makeRequest('get', url, { expectError: true }); runner.makeRequest('get', '/'); await runner.completed(); }); @@ -284,33 +284,41 @@ describe('express v5 tracing', () => { 'scenario-filterStatusCode.mjs', 'instrument-filterStatusCode.mjs', (createRunner, test) => { - // We opt-out of the default 404 filtering in order to test how 404 spans are handled - test('handles 404 route correctly', async () => { - const runner = createRunner() - .expect({ - transaction: { - transaction: 'GET /does-not-exist', - contexts: { - trace: { - span_id: expect.stringMatching(/[a-f0-9]{16}/), - trace_id: expect.stringMatching(/[a-f0-9]{32}/), - data: { - 'http.response.status_code': 404, - url: expect.stringMatching(/\/does-not-exist$/), - 'http.method': 'GET', - 'http.url': expect.stringMatching(/\/does-not-exist$/), - 'http.target': '/does-not-exist', + // We opt-out of the default [401, 404] fitering in order to test how these spans are handled + test.each([ + { status_code: 401, url: '/401', status: 'unauthenticated' }, + { status_code: 402, url: '/402', status: 'invalid_argument' }, + { status_code: 403, url: '/403', status: 'permission_denied' }, + { status_code: 404, url: '/does-not-exist', status: 'not_found' }, + ])( + 'handles %s route correctly', + async ({ status_code, url, status }: { status_code: number; url: string; status: string }) => { + const runner = createRunner() + .expect({ + transaction: { + transaction: `GET ${url}`, + contexts: { + trace: { + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + data: { + 'http.response.status_code': status_code, + url: expect.stringMatching(url), + 'http.method': 'GET', + 'http.url': expect.stringMatching(url), + 'http.target': url, + }, + op: 'http.server', + status, }, - op: 'http.server', - status: 'not_found', }, }, - }, - }) - .start(); - runner.makeRequest('get', '/does-not-exist', { expectError: true }); - await runner.completed(); - }); + }) + .start(); + runner.makeRequest('get', url, { expectError: true }); + await runner.completed(); + }, + ); test('filters defined status codes', async () => { const runner = createRunner() diff --git a/dev-packages/node-integration-tests/suites/express/tracing/scenario-filterStatusCode.mjs b/dev-packages/node-integration-tests/suites/express/tracing/scenario-filterStatusCode.mjs index f2e20014f48f..c53a72951970 100644 --- a/dev-packages/node-integration-tests/suites/express/tracing/scenario-filterStatusCode.mjs +++ b/dev-packages/node-integration-tests/suites/express/tracing/scenario-filterStatusCode.mjs @@ -8,6 +8,18 @@ app.get('/', (_req, res) => { res.send({ response: 'response 0' }); }); +app.get('/401', (_req, res) => { + res.status(401).send({ response: 'response 401' }); +}); + +app.get('/402', (_req, res) => { + res.status(402).send({ response: 'response 402' }); +}); + +app.get('/403', (_req, res) => { + res.status(403).send({ response: 'response 403' }); +}); + app.get('/499', (_req, res) => { res.status(499).send({ response: 'response 499' }); }); diff --git a/dev-packages/node-integration-tests/suites/express/tracing/scenario.mjs b/dev-packages/node-integration-tests/suites/express/tracing/scenario.mjs index 8b48ee0dbc44..4e32a052908d 100644 --- a/dev-packages/node-integration-tests/suites/express/tracing/scenario.mjs +++ b/dev-packages/node-integration-tests/suites/express/tracing/scenario.mjs @@ -15,6 +15,18 @@ app.get('/', (_req, res) => { res.send({ response: 'response 0' }); }); +app.get('/401', (_req, res) => { + res.status(401).send({ response: 'response 401' }); +}); + +app.get('/402', (_req, res) => { + res.status(402).send({ response: 'response 402' }); +}); + +app.get('/403', (_req, res) => { + res.status(403).send({ response: 'response 403' }); +}); + app.get('/test/express', (_req, res) => { res.send({ response: 'response 1' }); }); diff --git a/dev-packages/node-integration-tests/suites/express/tracing/test.ts b/dev-packages/node-integration-tests/suites/express/tracing/test.ts index 63706c0e5cb2..f5a772dbf096 100644 --- a/dev-packages/node-integration-tests/suites/express/tracing/test.ts +++ b/dev-packages/node-integration-tests/suites/express/tracing/test.ts @@ -90,16 +90,16 @@ describe('express tracing', () => { await runner.completed(); }); - test('ignores 404 routes by default', async () => { + test.each(['/401', '/402', '/403', '/does-not-exist'])('ignores %s route by default', async (url: string) => { const runner = createRunner() .expect({ - // No transaction is sent for the 404 route + // No transaction is sent for the 401, 402, 403, 404 routes transaction: { transaction: 'GET /', }, }) .start(); - runner.makeRequest('get', '/does-not-exist', { expectError: true }); + runner.makeRequest('get', url, { expectError: true }); runner.makeRequest('get', '/'); await runner.completed(); }); @@ -315,34 +315,42 @@ describe('express tracing', () => { 'scenario-filterStatusCode.mjs', 'instrument-filterStatusCode.mjs', (createRunner, test) => { - // We opt-out of the default 404 filtering in order to test how 404 spans are handled - test('handles 404 route correctly', async () => { - const runner = createRunner() - .expect({ - transaction: { - // FIXME: This is incorrect, sadly :( - transaction: 'GET /', - contexts: { - trace: { - span_id: expect.stringMatching(/[a-f0-9]{16}/), - trace_id: expect.stringMatching(/[a-f0-9]{32}/), - data: { - 'http.response.status_code': 404, - url: expect.stringMatching(/\/does-not-exist$/), - 'http.method': 'GET', - 'http.url': expect.stringMatching(/\/does-not-exist$/), - 'http.target': '/does-not-exist', + // We opt-out of the default [401, 404] filtering in order to test how these spans are handled + test.each([ + { status_code: 401, url: '/401', status: 'unauthenticated' }, + { status_code: 402, url: '/402', status: 'invalid_argument' }, + { status_code: 403, url: '/403', status: 'permission_denied' }, + { status_code: 404, url: '/does-not-exist', status: 'not_found' }, + ])( + 'handles %s route correctly', + async ({ status_code, url, status }: { status_code: number; url: string; status: string }) => { + const runner = createRunner() + .expect({ + transaction: { + // TODO(v10): This is incorrect on OpenTelemetry v1 but can be fixed in v2 + transaction: `GET ${status_code === 404 ? '/' : url}`, + contexts: { + trace: { + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + data: { + 'http.response.status_code': status_code, + url: expect.stringMatching(url), + 'http.method': 'GET', + 'http.url': expect.stringMatching(url), + 'http.target': url, + }, + op: 'http.server', + status, }, - op: 'http.server', - status: 'not_found', }, }, - }, - }) - .start(); - runner.makeRequest('get', '/does-not-exist', { expectError: true }); - await runner.completed(); - }); + }) + .start(); + runner.makeRequest('get', url, { expectError: true }); + await runner.completed(); + }, + ); test('filters defined status codes', async () => { const runner = createRunner() diff --git a/dev-packages/rollup-utils/npmHelpers.mjs b/dev-packages/rollup-utils/npmHelpers.mjs index 83053aaeea98..cff113d622d6 100644 --- a/dev-packages/rollup-utils/npmHelpers.mjs +++ b/dev-packages/rollup-utils/npmHelpers.mjs @@ -93,7 +93,7 @@ export function makeBaseNPMConfig(options = {}) { } return true; - } + }, }, plugins: [nodeResolvePlugin, sucrasePlugin, debugBuildStatementReplacePlugin, rrwebBuildPlugin, cleanupPlugin], diff --git a/packages/node-core/src/integrations/http/index.ts b/packages/node-core/src/integrations/http/index.ts index 8bd69c22a8e7..7e574712990d 100644 --- a/packages/node-core/src/integrations/http/index.ts +++ b/packages/node-core/src/integrations/http/index.ts @@ -57,7 +57,7 @@ interface HttpOptions { * By default, spans with 404 status code are ignored. * Expects an array of status codes or a range of status codes, e.g. [[300,399], 404] would ignore 3xx and 404 status codes. * - * @default `[404]` + * @default `[[401, 404], [300, 399]]` */ dropSpansForIncomingRequestStatusCodes?: (number | [number, number])[]; @@ -105,7 +105,10 @@ const instrumentSentryHttp = generateInstrumentOnce { - const dropSpansForIncomingRequestStatusCodes = options.dropSpansForIncomingRequestStatusCodes ?? [404]; + const dropSpansForIncomingRequestStatusCodes = options.dropSpansForIncomingRequestStatusCodes ?? [ + [401, 404], + [300, 399], + ]; return { name: INTEGRATION_NAME, diff --git a/packages/node/src/integrations/http/index.ts b/packages/node/src/integrations/http/index.ts index e46e830f9d16..e56842be85cb 100644 --- a/packages/node/src/integrations/http/index.ts +++ b/packages/node/src/integrations/http/index.ts @@ -79,7 +79,7 @@ interface HttpOptions { * By default, spans with 404 status code are ignored. * Expects an array of status codes or a range of status codes, e.g. [[300,399], 404] would ignore 3xx and 404 status codes. * - * @default `[404]` + * @default `[[401, 404], [300, 399]]` */ dropSpansForIncomingRequestStatusCodes?: (number | [number, number])[]; @@ -184,7 +184,10 @@ export function _shouldInstrumentSpans(options: HttpOptions, clientOptions: Part * It creates breadcrumbs and spans for outgoing HTTP requests which will be attached to the currently active span. */ export const httpIntegration = defineIntegration((options: HttpOptions = {}) => { - const dropSpansForIncomingRequestStatusCodes = options.dropSpansForIncomingRequestStatusCodes ?? [404]; + const dropSpansForIncomingRequestStatusCodes = options.dropSpansForIncomingRequestStatusCodes ?? [ + [401, 404], + [300, 399], + ]; return { name: INTEGRATION_NAME, diff --git a/packages/remix/test/integration/test/server/instrumentation/action.test.ts b/packages/remix/test/integration/test/server/instrumentation/action.test.ts index 3681e43807b8..bca38429ee29 100644 --- a/packages/remix/test/integration/test/server/instrumentation/action.test.ts +++ b/packages/remix/test/integration/test/server/instrumentation/action.test.ts @@ -152,28 +152,15 @@ describe('Remix API Actions', () => { const envelopes = await env.getMultipleEnvelopeRequest({ url, - count: 3, + count: 2, method: 'post', envelopeType: ['transaction', 'event'], }); - const [transaction_1, transaction_2] = envelopes.filter(envelope => envelope[1].type === 'transaction'); + const [transaction] = envelopes.filter(envelope => envelope[1].type === 'transaction'); const [event] = envelopes.filter(envelope => envelope[1].type === 'event'); - assertSentryTransaction(transaction_1[2], { - contexts: { - trace: { - op: 'http.server', - status: 'ok', - data: { - 'http.response.status_code': 302, - }, - }, - }, - transaction: `POST action-json-response/:id`, - }); - - assertSentryTransaction(transaction_2[2], { + assertSentryTransaction(transaction[2], { contexts: { trace: { op: 'http.server', diff --git a/packages/remix/test/integration/test/server/instrumentation/loader.test.ts b/packages/remix/test/integration/test/server/instrumentation/loader.test.ts index eef2a9683813..c90c23d135c8 100644 --- a/packages/remix/test/integration/test/server/instrumentation/loader.test.ts +++ b/packages/remix/test/integration/test/server/instrumentation/loader.test.ts @@ -123,27 +123,14 @@ describe('Remix API Loaders', () => { const envelopes = await env.getMultipleEnvelopeRequest({ url, - count: 3, + count: 2, envelopeType: ['transaction', 'event'], }); - const [transaction_1, transaction_2] = envelopes.filter(envelope => envelope[1].type === 'transaction'); + const [transaction] = envelopes.filter(envelope => envelope[1].type === 'transaction'); const [event] = envelopes.filter(envelope => envelope[1].type === 'event'); - assertSentryTransaction(transaction_1[2], { - contexts: { - trace: { - op: 'http.server', - status: 'ok', - data: { - 'http.response.status_code': 302, - }, - }, - }, - transaction: `GET loader-json-response/:id`, - }); - - assertSentryTransaction(transaction_2[2], { + assertSentryTransaction(transaction[2], { contexts: { trace: { op: 'http.server',