From cd0dda9eadfcc7b12eccc132ee913303bea799b4 Mon Sep 17 00:00:00 2001 From: Martin Sonnberger Date: Thu, 30 Jul 2026 14:54:44 +0200 Subject: [PATCH 1/4] feat(node)!: Use `function` span op for terminal request handlers Migrate the terminal request-handler span ops across the server integrations to the cross-framework `function` op, and trace Hono's internal `app.request()` dispatch as an `http.server` span: - express: `request_handler.express` -> `function` - fastify: `request_handler.fastify` -> `function` - elysia: `request_handler.elysia` -> `function` - nestjs: `handler.nestjs` -> `function` - hono: `hono.request` -> `http.server` Co-Authored-By: Claude Opus 4.8 --- .../elysia-bun/tests/transactions.test.ts | 2 +- .../elysia-node/tests/transactions.test.ts | 2 +- .../hono-4/tests/multi-fetch.test.ts | 24 ++++++++++++------- .../nestjs-11/tests/transactions.test.ts | 8 +++---- .../nestjs-8/tests/transactions.test.ts | 8 +++---- .../nestjs-basic/tests/transactions.test.ts | 8 +++---- .../nestjs-fastify/tests/transactions.test.ts | 10 ++++---- .../tests/transactions.test.ts | 2 +- .../tests/transactions.test.ts | 8 +++---- .../tests/transactions.test.ts | 8 +++---- .../tests/server.test.ts | 4 ++-- .../tests/transactions.test.ts | 4 ++-- .../tests/transactions.test.ts | 4 ++-- .../tests/transactions.test.ts | 4 ++-- .../node-express/tests/transactions.test.ts | 8 +++---- .../node-fastify-3/tests/transactions.test.ts | 8 +++---- .../performance/trace-propagation.test.ts | 2 +- .../performance/trace-propagation.test.ts | 2 +- .../tsx-express/tests/transactions.test.ts | 8 +++---- .../suites/express/tracing/test.ts | 2 +- .../suites/tracing/fastify/test.ts | 4 ++-- .../ignoreSpans-streamed/children/test.ts | 2 +- .../src/integrations/express/patch-layer.ts | 9 +++++-- .../integrations/express/patch-layer.test.ts | 6 ++--- packages/elysia/src/withElysia.ts | 4 ++-- packages/hono/src/shared/patchAppRequest.ts | 6 ++--- .../hono/test/shared/applyPatches.test.ts | 3 +-- .../hono/test/shared/earlyPatchRoute.test.ts | 10 ++------ .../hono/test/shared/patchAppRequest.test.ts | 3 +-- .../nestjs/src/integrations/wrap-route.ts | 12 +++++----- .../orchestrion-subscriber.test.ts | 2 +- .../express/instrumentation.ts | 9 +++++-- .../fastify/instrumentation.ts | 6 ++--- 33 files changed, 105 insertions(+), 97 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/elysia-bun/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/elysia-bun/tests/transactions.test.ts index fd51305e733a..0c09def64b7f 100644 --- a/dev-packages/e2e-tests/test-applications/elysia-bun/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/elysia-bun/tests/transactions.test.ts @@ -132,7 +132,7 @@ test('Creates lifecycle spans for Elysia hooks', async ({ baseURL, request }) => expect(spans).toContainEqual( expect.objectContaining({ description: 'Handle', - op: 'request_handler.elysia', + op: 'function', origin: 'auto.http.elysia', }), ); diff --git a/dev-packages/e2e-tests/test-applications/elysia-node/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/elysia-node/tests/transactions.test.ts index a5949b507dfa..75ba4d08994a 100644 --- a/dev-packages/e2e-tests/test-applications/elysia-node/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/elysia-node/tests/transactions.test.ts @@ -132,7 +132,7 @@ test('Creates lifecycle spans for Elysia hooks', async ({ baseURL, request }) => expect(spans).toContainEqual( expect.objectContaining({ description: 'Handle', - op: 'request_handler.elysia', + op: 'function', origin: 'auto.http.elysia', }), ); diff --git a/dev-packages/e2e-tests/test-applications/hono-4/tests/multi-fetch.test.ts b/dev-packages/e2e-tests/test-applications/hono-4/tests/multi-fetch.test.ts index da369c1c6c31..3904a02ebc30 100644 --- a/dev-packages/e2e-tests/test-applications/hono-4/tests/multi-fetch.test.ts +++ b/dev-packages/e2e-tests/test-applications/hono-4/tests/multi-fetch.test.ts @@ -185,7 +185,7 @@ test.describe('multi-fetch: internal .request() calls between sub-apps', () => { }); test.describe('trace propagation through internal .request() calls', () => { - test('single internal fetch produces a hono.request child span', async ({ baseURL }) => { + test('single internal fetch produces an internal-request child span', async ({ baseURL }) => { const transactionPromise = waitForTransaction(APP_NAME, event => { return ( event.contexts?.trace?.op === 'http.server' && event.transaction === `GET ${STOREFRONT}/product/:productId` @@ -198,12 +198,14 @@ test.describe('multi-fetch: internal .request() calls between sub-apps', () => { const traceId = transaction.contexts?.trace?.trace_id; const spans = transaction.spans || []; - const internalRequestSpans = spans.filter((s: { op?: string }) => s.op === 'hono.request'); + const internalRequestSpans = spans.filter( + (s: { origin?: string }) => s.origin === 'auto.http.hono.internal_request', + ); expect(internalRequestSpans).toHaveLength(1); expect(internalRequestSpans[0]).toEqual( expect.objectContaining({ - op: 'hono.request', + op: 'http.server', origin: 'auto.http.hono.internal_request', trace_id: traceId, }), @@ -211,7 +213,7 @@ test.describe('multi-fetch: internal .request() calls between sub-apps', () => { expect(internalRequestSpans[0]?.description).toContain('GET /item/self-watering-plant'); }); - test('parallel internal fetches produce two sibling hono.request spans', async ({ baseURL }) => { + test('parallel internal fetches produce two sibling internal-request spans', async ({ baseURL }) => { const transactionPromise = waitForTransaction(APP_NAME, event => { return ( event.contexts?.trace?.op === 'http.server' && @@ -225,7 +227,9 @@ test.describe('multi-fetch: internal .request() calls between sub-apps', () => { const traceId = transaction.contexts?.trace?.trace_id; const spans = transaction.spans || []; - const internalRequestSpans = spans.filter((s: { op?: string }) => s.op === 'hono.request'); + const internalRequestSpans = spans.filter( + (s: { origin?: string }) => s.origin === 'auto.http.hono.internal_request', + ); expect(internalRequestSpans).toHaveLength(2); @@ -238,7 +242,7 @@ test.describe('multi-fetch: internal .request() calls between sub-apps', () => { expect(internalRequestSpans[1]?.origin).toBe('auto.http.hono.internal_request'); }); - test('sequential chained fetches produce two ordered hono.request spans', async ({ baseURL }) => { + test('sequential chained fetches produce two ordered internal-request spans', async ({ baseURL }) => { const transactionPromise = waitForTransaction(APP_NAME, event => { return ( event.contexts?.trace?.op === 'http.server' && @@ -253,7 +257,7 @@ test.describe('multi-fetch: internal .request() calls between sub-apps', () => { const spans = transaction.spans || []; const internalRequestSpans = spans - .filter((s: { op?: string }) => s.op === 'hono.request') + .filter((s: { origin?: string }) => s.origin === 'auto.http.hono.internal_request') .sort( (a: { start_timestamp?: number }, b: { start_timestamp?: number }) => (a.start_timestamp ?? 0) - (b.start_timestamp ?? 0), @@ -270,7 +274,7 @@ test.describe('multi-fetch: internal .request() calls between sub-apps', () => { expect(internalRequestSpans[1]?.trace_id).toBe(traceId); }); - test('hono.request span has no error status for internal 4xx HTTPException', async ({ baseURL }) => { + test('internal-request span has no error status for internal 4xx HTTPException', async ({ baseURL }) => { const transactionPromise = waitForTransaction(APP_NAME, event => { return ( event.contexts?.trace?.op === 'http.server' && @@ -283,7 +287,9 @@ test.describe('multi-fetch: internal .request() calls between sub-apps', () => { const transaction = await transactionPromise; const spans = transaction.spans || []; - const internalRequestSpans = spans.filter((s: { op?: string }) => s.op === 'hono.request'); + const internalRequestSpans = spans.filter( + (s: { origin?: string }) => s.origin === 'auto.http.hono.internal_request', + ); expect(internalRequestSpans).toHaveLength(1); expect(internalRequestSpans[0]?.status).not.toBe('internal_error'); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-11/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-11/tests/transactions.test.ts index 88dc03adecc8..6ac9548ed03f 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-11/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-11/tests/transactions.test.ts @@ -62,9 +62,9 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'express.type': 'request_handler', 'http.route': '/test-transaction', 'sentry.origin': 'auto.http.express', - 'sentry.op': 'request_handler.express', + 'sentry.op': 'function', }, - op: 'request_handler.express', + op: 'function', description: '/test-transaction', parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), span_id: expect.stringMatching(/[a-f0-9]{16}/), @@ -105,7 +105,7 @@ test('Sends an API route transaction', async ({ baseURL }) => { trace_id: expect.stringMatching(/[a-f0-9]{32}/), data: { 'sentry.origin': 'auto.http.nestjs', - 'sentry.op': 'handler.nestjs', + 'sentry.op': 'function', component: '@nestjs/core', 'nestjs.version': expect.any(String), 'nestjs.type': 'handler', @@ -117,7 +117,7 @@ test('Sends an API route transaction', async ({ baseURL }) => { timestamp: expect.any(Number), status: 'ok', origin: 'auto.http.nestjs', - op: 'handler.nestjs', + op: 'function', }, ]), transaction: 'GET /test-transaction', diff --git a/dev-packages/e2e-tests/test-applications/nestjs-8/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-8/tests/transactions.test.ts index b606ef7eed53..512f6d60d3b0 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-8/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-8/tests/transactions.test.ts @@ -66,9 +66,9 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'express.type': 'request_handler', 'http.route': '/test-transaction', 'sentry.origin': 'auto.http.express', - 'sentry.op': 'request_handler.express', + 'sentry.op': 'function', }, - op: 'request_handler.express', + op: 'function', description: '/test-transaction', parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), span_id: expect.stringMatching(/[a-f0-9]{16}/), @@ -109,7 +109,7 @@ test('Sends an API route transaction', async ({ baseURL }) => { trace_id: expect.stringMatching(/[a-f0-9]{32}/), data: { 'sentry.origin': 'auto.http.nestjs', - 'sentry.op': 'handler.nestjs', + 'sentry.op': 'function', component: '@nestjs/core', 'nestjs.version': expect.any(String), 'nestjs.type': 'handler', @@ -121,7 +121,7 @@ test('Sends an API route transaction', async ({ baseURL }) => { timestamp: expect.any(Number), status: 'ok', origin: 'auto.http.nestjs', - op: 'handler.nestjs', + op: 'function', }, ]), transaction: 'GET /test-transaction', diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic/tests/transactions.test.ts index 66e69b630304..2d3100b95d1d 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-basic/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/tests/transactions.test.ts @@ -88,9 +88,9 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'express.type': 'request_handler', 'http.route': '/test-transaction', 'sentry.origin': 'auto.http.express', - 'sentry.op': 'request_handler.express', + 'sentry.op': 'function', }, - op: 'request_handler.express', + op: 'function', description: '/test-transaction', parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), span_id: expect.stringMatching(/[a-f0-9]{16}/), @@ -131,7 +131,7 @@ test('Sends an API route transaction', async ({ baseURL }) => { trace_id: expect.stringMatching(/[a-f0-9]{32}/), data: { 'sentry.origin': 'auto.http.nestjs', - 'sentry.op': 'handler.nestjs', + 'sentry.op': 'function', component: '@nestjs/core', 'nestjs.version': expect.any(String), 'nestjs.type': 'handler', @@ -143,7 +143,7 @@ test('Sends an API route transaction', async ({ baseURL }) => { timestamp: expect.any(Number), status: 'ok', origin: 'auto.http.nestjs', - op: 'handler.nestjs', + op: 'function', }, ]), transaction: 'GET /test-transaction', diff --git a/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/transactions.test.ts index 70c272838a4d..5198eeaa9439 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/transactions.test.ts @@ -83,14 +83,14 @@ test.skip('Sends an API route transaction', async ({ baseURL }) => { trace_id: expect.stringMatching(/[a-f0-9]{32}/), data: { 'sentry.origin': 'auto.http.otel.fastify', - 'sentry.op': 'request_handler.fastify', + 'sentry.op': 'function', 'hook.name': 'fastify -> @sentry/instrumentation-fastify -> @fastify/middie - route-handler', 'fastify.type': 'request-handler', 'http.route': '/test-transaction', 'hook.callback.name': 'anonymous', }, description: '@fastify/middie - route-handler', - op: 'request_handler.fastify', + op: 'function', parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), start_timestamp: expect.any(Number), timestamp: expect.any(Number), @@ -125,7 +125,7 @@ test.skip('Sends an API route transaction', async ({ baseURL }) => { trace_id: expect.stringMatching(/[a-f0-9]{32}/), data: { 'sentry.origin': 'auto.http.nestjs', - 'sentry.op': 'handler.nestjs', + 'sentry.op': 'function', component: '@nestjs/core', 'nestjs.version': expect.any(String), 'nestjs.type': 'handler', @@ -136,7 +136,7 @@ test.skip('Sends an API route transaction', async ({ baseURL }) => { start_timestamp: expect.any(Number), timestamp: expect.any(Number), status: 'ok', - op: 'handler.nestjs', + op: 'function', origin: 'auto.http.nestjs', }, { @@ -799,7 +799,7 @@ test('Sets error status on nest spans when a handler throws', async ({ baseURL } expect(transactionEvent.spans).toEqual( expect.arrayContaining([ expect.objectContaining({ op: 'request_context.nestjs', status: 'internal_error' }), - expect.objectContaining({ op: 'handler.nestjs', status: 'internal_error' }), + expect.objectContaining({ op: 'function', status: 'internal_error' }), ]), ); }); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-orchestrion/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-orchestrion/tests/transactions.test.ts index 41c6c93f4e75..ef13bcfcfe7f 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-orchestrion/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-orchestrion/tests/transactions.test.ts @@ -60,7 +60,7 @@ test('request_context + handler: a route transaction nests the nestjs spans', as // request_handler span: wraps the controller method itself. const handler = (transactionEvent.spans ?? []).find( - span => span.op === 'handler.nestjs' && span.description === 'testTransaction', + span => span.op === 'function' && span.description === 'testTransaction', ); expect(handler).toBeDefined(); }); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/tests/transactions.test.ts index c2cec87de93b..8c2c3bc8cbe9 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/tests/transactions.test.ts @@ -62,9 +62,9 @@ test('Sends an API route transaction from module', async ({ baseURL }) => { 'express.type': 'request_handler', 'http.route': '/example-module/transaction', 'sentry.origin': 'auto.http.express', - 'sentry.op': 'request_handler.express', + 'sentry.op': 'function', }, - op: 'request_handler.express', + op: 'function', description: '/example-module/transaction', parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), span_id: expect.stringMatching(/[a-f0-9]{16}/), @@ -105,7 +105,7 @@ test('Sends an API route transaction from module', async ({ baseURL }) => { trace_id: expect.stringMatching(/[a-f0-9]{32}/), data: { 'sentry.origin': 'auto.http.nestjs', - 'sentry.op': 'handler.nestjs', + 'sentry.op': 'function', component: '@nestjs/core', 'nestjs.version': expect.any(String), 'nestjs.type': 'handler', @@ -117,7 +117,7 @@ test('Sends an API route transaction from module', async ({ baseURL }) => { timestamp: expect.any(Number), status: 'ok', origin: 'auto.http.nestjs', - op: 'handler.nestjs', + op: 'function', }, ]), transaction: 'GET /example-module/transaction', diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/transactions.test.ts index 1b6a02b747e2..3abf1ea5b5fa 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/transactions.test.ts @@ -62,9 +62,9 @@ test('Sends an API route transaction from module', async ({ baseURL }) => { 'express.type': 'request_handler', 'http.route': '/example-module/transaction', 'sentry.origin': 'auto.http.express', - 'sentry.op': 'request_handler.express', + 'sentry.op': 'function', }, - op: 'request_handler.express', + op: 'function', description: '/example-module/transaction', parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), span_id: expect.stringMatching(/[a-f0-9]{16}/), @@ -105,7 +105,7 @@ test('Sends an API route transaction from module', async ({ baseURL }) => { trace_id: expect.stringMatching(/[a-f0-9]{32}/), data: { 'sentry.origin': 'auto.http.nestjs', - 'sentry.op': 'handler.nestjs', + 'sentry.op': 'function', component: '@nestjs/core', 'nestjs.version': expect.any(String), 'nestjs.type': 'handler', @@ -117,7 +117,7 @@ test('Sends an API route transaction from module', async ({ baseURL }) => { timestamp: expect.any(Number), status: 'ok', origin: 'auto.http.nestjs', - op: 'handler.nestjs', + op: 'function', }, ]), transaction: 'GET /example-module/transaction', diff --git a/dev-packages/e2e-tests/test-applications/node-express-esm-loader/tests/server.test.ts b/dev-packages/e2e-tests/test-applications/node-express-esm-loader/tests/server.test.ts index 33468ad705c7..9a51236368db 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-esm-loader/tests/server.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-express-esm-loader/tests/server.test.ts @@ -102,9 +102,9 @@ test('Should record a transaction for route with parameters', async ({ request } 'express.type': 'request_handler', 'http.route': '/test-transaction/:param', 'sentry.origin': 'auto.http.express', - 'sentry.op': 'request_handler.express', + 'sentry.op': 'function', }, - op: 'request_handler.express', + op: 'function', description: '/test-transaction/:param', origin: 'auto.http.express', parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-express-orchestrion-cjs/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-express-orchestrion-cjs/tests/transactions.test.ts index 9ed354830fc8..44070eaf84a9 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-orchestrion-cjs/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-express-orchestrion-cjs/tests/transactions.test.ts @@ -86,13 +86,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { expect(spans).toContainEqual({ data: { 'sentry.origin': 'auto.http.express', - 'sentry.op': 'request_handler.express', + 'sentry.op': 'function', 'http.route': '/test-transaction', 'express.name': '/test-transaction', 'express.type': 'request_handler', }, description: '/test-transaction', - op: 'request_handler.express', + op: 'function', origin: 'auto.http.express', parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-express-orchestrion/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-express-orchestrion/tests/transactions.test.ts index 75b9ffe2f716..fee291536d64 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-orchestrion/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-express-orchestrion/tests/transactions.test.ts @@ -86,13 +86,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { expect(spans).toContainEqual({ data: { 'sentry.origin': 'auto.http.express', - 'sentry.op': 'request_handler.express', + 'sentry.op': 'function', 'http.route': '/test-transaction', 'express.name': '/test-transaction', 'express.type': 'request_handler', }, description: '/test-transaction', - op: 'request_handler.express', + op: 'function', origin: 'auto.http.express', parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-express-v5/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-express-v5/tests/transactions.test.ts index dbe870bd4c16..089832650ac6 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-v5/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-express-v5/tests/transactions.test.ts @@ -86,13 +86,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { expect(spans).toContainEqual({ data: { 'sentry.origin': 'auto.http.express', - 'sentry.op': 'request_handler.express', + 'sentry.op': 'function', 'http.route': '/test-transaction', 'express.name': '/test-transaction', 'express.type': 'request_handler', }, description: '/test-transaction', - op: 'request_handler.express', + op: 'function', origin: 'auto.http.express', parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts index 717d049158a1..0d0081e22f71 100644 --- a/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts @@ -122,13 +122,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { expect(spans).toContainEqual({ data: { 'sentry.origin': 'auto.http.express', - 'sentry.op': 'request_handler.express', + 'sentry.op': 'function', 'http.route': '/test-transaction', 'express.name': '/test-transaction', 'express.type': 'request_handler', }, description: '/test-transaction', - op: 'request_handler.express', + op: 'function', origin: 'auto.http.express', parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), span_id: expect.stringMatching(/[a-f0-9]{16}/), @@ -198,14 +198,14 @@ test('Sends an API route transaction for an errored route', async ({ baseURL }) expect(spans).toContainEqual({ data: { 'sentry.origin': 'auto.http.express', - 'sentry.op': 'request_handler.express', + 'sentry.op': 'function', 'http.route': '/test-exception/:id', 'express.name': '/test-exception/:id', 'express.type': 'request_handler', 'error.type': 'Error', }, description: '/test-exception/:id', - op: 'request_handler.express', + op: 'function', origin: 'auto.http.express', parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/transactions.test.ts index 11b27509023d..13746f07fa46 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/transactions.test.ts @@ -75,7 +75,7 @@ test.skip('Sends an API route transaction', async ({ baseURL }) => { expect(spans).toContainEqual({ data: { 'sentry.origin': 'auto.http.otel.fastify', - 'sentry.op': 'request_handler.fastify', + 'sentry.op': 'function', 'fastify.root': '@sentry/instrumentation-fastify', 'http.request.method': 'GET', 'url.path': '/test-transaction', @@ -83,7 +83,7 @@ test.skip('Sends an API route transaction', async ({ baseURL }) => { 'http.response.status_code': 200, }, description: 'GET /test-transaction', - op: 'request_handler.fastify', + op: 'function', parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), span_id: expect.stringMatching(/[a-f0-9]{16}/), start_timestamp: expect.any(Number), @@ -97,12 +97,12 @@ test.skip('Sends an API route transaction', async ({ baseURL }) => { expect(spans).toContainEqual({ data: expect.objectContaining({ 'sentry.origin': 'auto.http.otel.fastify', - 'sentry.op': 'request_handler.fastify', + 'sentry.op': 'function', 'fastify.type': expect.stringMatching(/request[-_]handler/), 'http.route': '/test-transaction', }), description: expect.stringContaining('sentry-fastify-error-handler'), - op: 'request_handler.fastify', + op: 'function', parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), span_id: expect.stringMatching(/[a-f0-9]{16}/), start_timestamp: expect.any(Number), diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/trace-propagation.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/trace-propagation.test.ts index e9b2c9409154..0a4bc17f622b 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/trace-propagation.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/trace-propagation.test.ts @@ -32,7 +32,7 @@ test.describe('Trace propagation', () => { expect(clientTx.contexts?.trace?.trace_id).toEqual(serverTx.contexts?.trace?.trace_id); - const requestHandlerSpan = serverTx.spans?.find(span => span.op === 'request_handler.express'); + const requestHandlerSpan = serverTx.spans?.find(span => span.op === 'function'); expect(requestHandlerSpan).toBeDefined(); expect(clientTx.contexts?.trace?.parent_span_id).toBe(requestHandlerSpan?.span_id); diff --git a/dev-packages/e2e-tests/test-applications/react-router-8-framework/tests/performance/trace-propagation.test.ts b/dev-packages/e2e-tests/test-applications/react-router-8-framework/tests/performance/trace-propagation.test.ts index 4ce133514ce3..9f356007cb6e 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-8-framework/tests/performance/trace-propagation.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-8-framework/tests/performance/trace-propagation.test.ts @@ -32,7 +32,7 @@ test.describe('Trace propagation', () => { expect(clientTx.contexts?.trace?.trace_id).toEqual(serverTx.contexts?.trace?.trace_id); - const requestHandlerSpan = serverTx.spans?.find(span => span.op === 'request_handler.express'); + const requestHandlerSpan = serverTx.spans?.find(span => span.op === 'function'); expect(requestHandlerSpan).toBeDefined(); expect(clientTx.contexts?.trace?.parent_span_id).toBe(requestHandlerSpan?.span_id); diff --git a/dev-packages/e2e-tests/test-applications/tsx-express/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/tsx-express/tests/transactions.test.ts index b2408811daac..0fbe6275925e 100644 --- a/dev-packages/e2e-tests/test-applications/tsx-express/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/tsx-express/tests/transactions.test.ts @@ -108,13 +108,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { expect(spans).toContainEqual({ data: { 'sentry.origin': 'auto.http.express', - 'sentry.op': 'request_handler.express', + 'sentry.op': 'function', 'http.route': '/test-transaction', 'express.name': '/test-transaction', 'express.type': 'request_handler', }, description: '/test-transaction', - op: 'request_handler.express', + op: 'function', origin: 'auto.http.express', parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), span_id: expect.stringMatching(/[a-f0-9]{16}/), @@ -184,14 +184,14 @@ test('Sends an API route transaction for an errored route', async ({ baseURL }) expect(spans).toContainEqual({ data: { 'sentry.origin': 'auto.http.express', - 'sentry.op': 'request_handler.express', + 'sentry.op': 'function', 'http.route': '/test-exception/:id', 'express.name': '/test-exception/:id', 'express.type': 'request_handler', 'error.type': 'Error', }, description: '/test-exception/:id', - op: 'request_handler.express', + op: 'function', origin: 'auto.http.express', parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), span_id: expect.stringMatching(/[a-f0-9]{16}/), 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 882732ff8d3b..231f3206e7c9 100644 --- a/dev-packages/node-integration-tests/suites/express/tracing/test.ts +++ b/dev-packages/node-integration-tests/suites/express/tracing/test.ts @@ -41,7 +41,7 @@ describe('express tracing', () => { 'express.type': 'request_handler', }), description: '/test/express', - op: 'request_handler.express', + op: 'function', origin: 'auto.http.express', }), ]), diff --git a/dev-packages/node-integration-tests/suites/tracing/fastify/test.ts b/dev-packages/node-integration-tests/suites/tracing/fastify/test.ts index f20dc2ca767b..359e1e483c28 100644 --- a/dev-packages/node-integration-tests/suites/tracing/fastify/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/fastify/test.ts @@ -26,10 +26,10 @@ describe('fastify auto-instrumentation', () => { }), }), expect.objectContaining({ - op: 'request_handler.fastify', + op: 'function', origin: 'auto.http.otel.fastify', data: expect.objectContaining({ - 'sentry.op': 'request_handler.fastify', + 'sentry.op': 'function', 'sentry.origin': 'auto.http.otel.fastify', }), }), diff --git a/dev-packages/node-integration-tests/suites/tracing/ignoreSpans-streamed/children/test.ts b/dev-packages/node-integration-tests/suites/tracing/ignoreSpans-streamed/children/test.ts index 9dc823fab5de..9e429c8946e0 100644 --- a/dev-packages/node-integration-tests/suites/tracing/ignoreSpans-streamed/children/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/ignoreSpans-streamed/children/test.ts @@ -33,7 +33,7 @@ describe('filtering child spans with ignoreSpans (streaming)', () => { ); const queryMiddlewareSpan = getSpan('query', 'middleware'); const corsMiddlewareSpan = getSpan('corsMiddleware', 'middleware'); - const requestHandlerSpan = getSpan('/test/express', 'request_handler.express'); + const requestHandlerSpan = getSpan('/test/express', 'function'); const httpServerSpan = getSpan('GET /test/express', 'http.server'); const customSpan = getSpan('custom', 'custom'); const customGrandchildSpan = getSpan('custom-grandchild', 'custom'); diff --git a/packages/core/src/integrations/express/patch-layer.ts b/packages/core/src/integrations/express/patch-layer.ts index 059dbca039f7..d8211c3c7f2f 100644 --- a/packages/core/src/integrations/express/patch-layer.ts +++ b/packages/core/src/integrations/express/patch-layer.ts @@ -28,7 +28,7 @@ */ import { SENTRY_OP } from '@sentry/conventions/attributes'; -import { WEB_SERVER_MIDDLEWARE_SPAN_OP } from '@sentry/conventions/op'; +import { WEB_SERVER_FUNCTION_SPAN_OP, WEB_SERVER_MIDDLEWARE_SPAN_OP } from '@sentry/conventions/op'; import { DEBUG_BUILD } from '../../debug-build'; import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../../semanticAttributes'; import { SPAN_STATUS_ERROR, startSpanManual, withActiveSpan } from '../../tracing'; @@ -124,7 +124,12 @@ export function patchLayer( const type = metadata.attributes[ATTR_EXPRESS_TYPE]; const attributes: SpanAttributes = Object.assign(metadata.attributes, { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.express', - [SENTRY_OP]: type === 'middleware' ? WEB_SERVER_MIDDLEWARE_SPAN_OP : `${type}.express`, + [SENTRY_OP]: + type === 'middleware' + ? WEB_SERVER_MIDDLEWARE_SPAN_OP + : type === 'request_handler' + ? WEB_SERVER_FUNCTION_SPAN_OP + : `${type}.express`, }); if (actualMatchedRoute) { attributes[ATTR_HTTP_ROUTE] = actualMatchedRoute; diff --git a/packages/core/test/lib/integrations/express/patch-layer.test.ts b/packages/core/test/lib/integrations/express/patch-layer.test.ts index aada88147215..00ee33365241 100644 --- a/packages/core/test/lib/integrations/express/patch-layer.test.ts +++ b/packages/core/test/lib/integrations/express/patch-layer.test.ts @@ -349,7 +349,7 @@ describe('patchLayer', () => { 'express.name': 'a/:boo/:car/layerPath', 'express.type': 'request_handler', 'http.route': '/a/:boo/:car/layerPath', - 'sentry.op': 'request_handler.express', + 'sentry.op': 'function', 'sentry.origin': 'auto.http.express', }, description: 'a/:boo/:car/layerPath', @@ -403,7 +403,7 @@ describe('patchLayer', () => { 'express.name': 'a/:boo/:car', 'express.type': 'request_handler', 'http.route': '/a/:boo/:car', - 'sentry.op': 'request_handler.express', + 'sentry.op': 'function', 'sentry.origin': 'auto.http.express', }, description: 'a/:boo/:car', @@ -414,7 +414,7 @@ describe('patchLayer', () => { 'express.name': 'a/:boo/:car', 'express.type': 'request_handler', 'http.route': '/a/:boo/:car', - 'sentry.op': 'request_handler.express', + 'sentry.op': 'function', 'sentry.origin': 'auto.http.express', }, description: 'a/:boo/:car', diff --git a/packages/elysia/src/withElysia.ts b/packages/elysia/src/withElysia.ts index 30253e0caad6..d147928101ce 100644 --- a/packages/elysia/src/withElysia.ts +++ b/packages/elysia/src/withElysia.ts @@ -1,5 +1,5 @@ import { HTTP_ROUTE, URL_FULL, URL_PATH } from '@sentry/conventions/attributes'; -import { WEB_SERVER_MIDDLEWARE_SPAN_OP } from '@sentry/conventions/op'; +import { WEB_SERVER_FUNCTION_SPAN_OP, WEB_SERVER_MIDDLEWARE_SPAN_OP } from '@sentry/conventions/op'; import type { Span } from '@sentry/core'; import { captureException, @@ -34,7 +34,7 @@ const ELYSIA_LIFECYCLE_OP_MAP: Record = { Parse: WEB_SERVER_MIDDLEWARE_SPAN_OP, Transform: WEB_SERVER_MIDDLEWARE_SPAN_OP, BeforeHandle: WEB_SERVER_MIDDLEWARE_SPAN_OP, - Handle: 'request_handler.elysia', + Handle: WEB_SERVER_FUNCTION_SPAN_OP, AfterHandle: WEB_SERVER_MIDDLEWARE_SPAN_OP, MapResponse: WEB_SERVER_MIDDLEWARE_SPAN_OP, AfterResponse: WEB_SERVER_MIDDLEWARE_SPAN_OP, diff --git a/packages/hono/src/shared/patchAppRequest.ts b/packages/hono/src/shared/patchAppRequest.ts index d70b379eba1e..bb0f84af68a5 100644 --- a/packages/hono/src/shared/patchAppRequest.ts +++ b/packages/hono/src/shared/patchAppRequest.ts @@ -1,3 +1,4 @@ +import { WEB_SERVER_HTTP_SERVER_SPAN_OP } from '@sentry/conventions/op'; import { debug, getActiveSpan, @@ -10,7 +11,7 @@ import { import type { Env, Hono } from 'hono'; import { DEBUG_BUILD } from '../debug-build'; -const INTERNAL_REQUEST_OP = 'hono.request'; +const INTERNAL_REQUEST_OP = WEB_SERVER_HTTP_SERVER_SPAN_OP; const INTERNAL_REQUEST_ORIGIN = 'auto.http.hono.internal_request'; function extractPathname(input: string | Request | URL): string { @@ -23,7 +24,7 @@ function extractPathname(input: string | Request | URL): string { /** * Patches `app.request()` on a Hono instance so that each internal dispatch - * is traced as a `hono.request` span — child of whatever span is active at + * is traced as an `http.server` span — child of whatever span is active at * the call site. * * `.request()` is a class field (arrow function), so this must run per-instance. @@ -53,7 +54,6 @@ export function patchAppRequest(app: Hono): void { return startSpan( { name: `${method} ${path}`, - op: INTERNAL_REQUEST_OP, onlyIfParent: true, attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_OP]: INTERNAL_REQUEST_OP, diff --git a/packages/hono/test/shared/applyPatches.test.ts b/packages/hono/test/shared/applyPatches.test.ts index 51a68859e614..129e83d341d2 100644 --- a/packages/hono/test/shared/applyPatches.test.ts +++ b/packages/hono/test/shared/applyPatches.test.ts @@ -583,9 +583,8 @@ describe('applyPatches', () => { expect(startSpanMock).toHaveBeenCalledWith( expect.objectContaining({ name: 'GET /hello', - op: 'hono.request', attributes: expect.objectContaining({ - 'sentry.op': 'hono.request', + 'sentry.op': 'http.server', 'sentry.origin': 'auto.http.hono.internal_request', }), }), diff --git a/packages/hono/test/shared/earlyPatchRoute.test.ts b/packages/hono/test/shared/earlyPatchRoute.test.ts index bd748fda978c..3e2eee6208a7 100644 --- a/packages/hono/test/shared/earlyPatchRoute.test.ts +++ b/packages/hono/test/shared/earlyPatchRoute.test.ts @@ -56,10 +56,7 @@ describe('earlyPatchHono (two-phase prototype hook)', () => { await subApp.request('/hello'); expect(startSpanMock).toHaveBeenCalledTimes(1); - expect(startSpanMock).toHaveBeenCalledWith( - expect.objectContaining({ name: 'GET /hello', op: 'hono.request' }), - expect.any(Function), - ); + expect(startSpanMock).toHaveBeenCalledWith(expect.objectContaining({ name: 'GET /hello' }), expect.any(Function)); }); it('emits a debug log and applies patchAppRequest when sub-app was mounted before applyPatches', async () => { @@ -82,10 +79,7 @@ describe('earlyPatchHono (two-phase prototype hook)', () => { // patchAppRequest is applied retroactively await subApp.request('/hello'); - expect(startSpanMock).toHaveBeenCalledWith( - expect.objectContaining({ name: 'GET /hello', op: 'hono.request' }), - expect.any(Function), - ); + expect(startSpanMock).toHaveBeenCalledWith(expect.objectContaining({ name: 'GET /hello' }), expect.any(Function)); }); it('preserves correct route behavior', async () => { diff --git a/packages/hono/test/shared/patchAppRequest.test.ts b/packages/hono/test/shared/patchAppRequest.test.ts index 81fef4c52ace..bcd5ccf3a863 100644 --- a/packages/hono/test/shared/patchAppRequest.test.ts +++ b/packages/hono/test/shared/patchAppRequest.test.ts @@ -32,10 +32,9 @@ describe('patchAppRequest', () => { expect(startSpanMock).toHaveBeenCalledWith( expect.objectContaining({ name: 'GET /hello', - op: 'hono.request', onlyIfParent: true, attributes: expect.objectContaining({ - 'sentry.op': 'hono.request', + 'sentry.op': 'http.server', 'sentry.origin': 'auto.http.hono.internal_request', }), }), diff --git a/packages/nestjs/src/integrations/wrap-route.ts b/packages/nestjs/src/integrations/wrap-route.ts index 47a8d1b4909a..271cdd349f21 100644 --- a/packages/nestjs/src/integrations/wrap-route.ts +++ b/packages/nestjs/src/integrations/wrap-route.ts @@ -1,4 +1,5 @@ -import { HTTP_METHOD, HTTP_ROUTE, URL_FULL } from '@sentry/conventions/attributes'; +import { HTTP_METHOD, HTTP_ROUTE, SENTRY_OP, URL_FULL } from '@sentry/conventions/attributes'; +import { WEB_SERVER_FUNCTION_SPAN_OP } from '@sentry/conventions/op'; import type { SpanAttributes } from '@sentry/core'; import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, startSpan } from '@sentry/core'; import type { AnyFn } from './helpers'; @@ -42,8 +43,8 @@ export function getAppCreationSpanOptions( } /** - * Wrap the route-handler callback so each invocation opens the `handler.nestjs` - * span (REQUEST_HANDLER). Preserve the original `.name` and reflect-metadata so + * Wrap the route-handler callback so each invocation opens the request-handler + * (`function` op) span. Preserve the original `.name` and reflect-metadata so * NestJS reflection is unaffected. */ export function wrapRouteHandler(callback: AnyFn, moduleVersion?: string): AnyFn { @@ -53,15 +54,14 @@ export function wrapRouteHandler(callback: AnyFn, moduleVersion?: string): AnyFn const spanName = callback.name || 'anonymous nest handler'; const attributes: SpanAttributes = { component: NESTJS_COMPONENT, + [SENTRY_OP]: WEB_SERVER_FUNCTION_SPAN_OP, [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: HTTP_ORIGIN, [AttributeNames.TYPE]: NestType.REQUEST_HANDLER, [AttributeNames.CALLBACK]: callback.name, [AttributeNames.VERSION]: moduleVersion || undefined, }; const wrapped = function (this: unknown, ...args: unknown[]): unknown { - return startSpan({ name: spanName, op: `${NestType.REQUEST_HANDLER}.nestjs`, attributes }, () => - callback.apply(this, args), - ); + return startSpan({ name: spanName, attributes }, () => callback.apply(this, args)); }; if (callback.name) { Object.defineProperty(wrapped, 'name', { value: callback.name }); diff --git a/packages/nestjs/test/integrations/orchestrion-subscriber.test.ts b/packages/nestjs/test/integrations/orchestrion-subscriber.test.ts index 8764075efe63..6a38e3b2c916 100644 --- a/packages/nestjs/test/integrations/orchestrion-subscriber.test.ts +++ b/packages/nestjs/test/integrations/orchestrion-subscriber.test.ts @@ -278,7 +278,7 @@ describe('NestJS orchestrion subscriber: request_context / request_handler', () expect(handlerSpanJson).toBeDefined(); expect(handlerSpanJson!.description).toBe('getCats'); - expect(handlerSpanJson!.op).toBe('handler.nestjs'); + expect(handlerSpanJson!.op).toBe('function'); expect(handlerSpanJson!.origin).toBe('auto.http.nestjs'); expect(handlerSpanJson!.data).toMatchObject({ component: '@nestjs/core', diff --git a/packages/server-utils/src/integrations/tracing-channel/express/instrumentation.ts b/packages/server-utils/src/integrations/tracing-channel/express/instrumentation.ts index ec3bec8df771..0d0fb6ccfac3 100644 --- a/packages/server-utils/src/integrations/tracing-channel/express/instrumentation.ts +++ b/packages/server-utils/src/integrations/tracing-channel/express/instrumentation.ts @@ -1,6 +1,6 @@ import type * as diagnosticsChannel from 'node:diagnostics_channel'; import { HTTP_ROUTE, SENTRY_OP } from '@sentry/conventions/attributes'; -import { WEB_SERVER_MIDDLEWARE_SPAN_OP } from '@sentry/conventions/op'; +import { WEB_SERVER_FUNCTION_SPAN_OP, WEB_SERVER_MIDDLEWARE_SPAN_OP } from '@sentry/conventions/op'; import type { Span } from '@sentry/core'; import { debug, @@ -221,7 +221,12 @@ function getSpanForLayer(data: HandleChannelContext, options: ExpressIntegration name, attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ORIGIN, - [SENTRY_OP]: type === 'middleware' ? WEB_SERVER_MIDDLEWARE_SPAN_OP : `${type}.express`, + [SENTRY_OP]: + type === 'middleware' + ? WEB_SERVER_MIDDLEWARE_SPAN_OP + : type === 'request_handler' + ? WEB_SERVER_FUNCTION_SPAN_OP + : `${type}.express`, [ATTR_EXPRESS_NAME]: name, [ATTR_EXPRESS_TYPE]: type, ...(matchedRoute ? { [HTTP_ROUTE]: matchedRoute } : {}), diff --git a/packages/server-utils/src/integrations/tracing-channel/fastify/instrumentation.ts b/packages/server-utils/src/integrations/tracing-channel/fastify/instrumentation.ts index f37abddc651e..3b17e10e2dbe 100644 --- a/packages/server-utils/src/integrations/tracing-channel/fastify/instrumentation.ts +++ b/packages/server-utils/src/integrations/tracing-channel/fastify/instrumentation.ts @@ -22,7 +22,7 @@ import { SENTRY_OP, URL_PATH, } from '@sentry/conventions/attributes'; -import { WEB_SERVER_MIDDLEWARE_SPAN_OP } from '@sentry/conventions/op'; +import { WEB_SERVER_FUNCTION_SPAN_OP, WEB_SERVER_MIDDLEWARE_SPAN_OP } from '@sentry/conventions/op'; import type { Span } from '@sentry/core'; import { isObjectLike, @@ -43,7 +43,7 @@ const SUPPORTED_VERSIONS = '>=3.21.0 <6'; const ORIGIN = 'auto.http.otel.fastify'; const HOOK_OP = WEB_SERVER_MIDDLEWARE_SPAN_OP; -const REQUEST_HANDLER_OP = 'request_handler.fastify'; +const REQUEST_HANDLER_OP = WEB_SERVER_FUNCTION_SPAN_OP; const FASTIFY_HOOKS = [ 'onRequest', @@ -182,6 +182,7 @@ function startRequestSpanHook(this: any, request: any, _reply: any, hookDone: () const attributes: Record = { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ORIGIN, + [SENTRY_OP]: REQUEST_HANDLER_OP, [ATTRIBUTE_FASTIFY_ROOT]: PACKAGE_NAME, [HTTP_REQUEST_METHOD]: request.method, [URL_PATH]: request.url, @@ -196,7 +197,6 @@ function startRequestSpanHook(this: any, request: any, _reply: any, hookDone: () const requestSpan = startInactiveSpan({ name: route != null ? `${request.method} ${route}` : 'request', - op: REQUEST_HANDLER_OP, attributes, }); request[kRequestSpan] = requestSpan; From 0208e3197d200df0981c0d4150f4e8eeeb06de9b Mon Sep 17 00:00:00 2001 From: Martin Sonnberger Date: Fri, 31 Jul 2026 09:30:42 +0200 Subject: [PATCH 2/4] SENTRY_OP --- packages/hono/src/shared/patchAppRequest.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/hono/src/shared/patchAppRequest.ts b/packages/hono/src/shared/patchAppRequest.ts index bb0f84af68a5..7f40c86aab12 100644 --- a/packages/hono/src/shared/patchAppRequest.ts +++ b/packages/hono/src/shared/patchAppRequest.ts @@ -1,9 +1,9 @@ +import { SENTRY_OP } from '@sentry/conventions/attributes'; import { WEB_SERVER_HTTP_SERVER_SPAN_OP } from '@sentry/conventions/op'; import { debug, getActiveSpan, getOriginalFunction, - SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, startSpan, type WrappedFunction, @@ -56,7 +56,7 @@ export function patchAppRequest(app: Hono): void { name: `${method} ${path}`, onlyIfParent: true, attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: INTERNAL_REQUEST_OP, + [SENTRY_OP]: INTERNAL_REQUEST_OP, [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: INTERNAL_REQUEST_ORIGIN, }, }, From 9c70c926e1321d142aa13acfed47d720bb7de98b Mon Sep 17 00:00:00 2001 From: Martin Sonnberger Date: Fri, 31 Jul 2026 10:11:17 +0200 Subject: [PATCH 3/4] feat(node)!: Use `function` span op for hapi plugin route handlers A plugin-registered hapi route runs the user's request handler, so it is a terminal handler like the express/fastify/hono routes in this migration. Map `plugin.hapi` to the cross-framework `function` op; `router.hapi` and `server.ext.hapi` (framework routing/extension lifecycle) keep their ops. Op is set via the `sentry.op` attribute only; `hapi.type` is unchanged. Co-Authored-By: Claude Opus 4.8 --- .../suites/tracing/hapi/scenario.mjs | 2 +- .../node-integration-tests/suites/tracing/hapi/test.ts | 4 ++-- .../src/integrations/tracing-channel/hapi-utils.ts | 9 +++++++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/tracing/hapi/scenario.mjs b/dev-packages/node-integration-tests/suites/tracing/hapi/scenario.mjs index 1aa7e8846b9f..2b7589f0f345 100644 --- a/dev-packages/node-integration-tests/suites/tracing/hapi/scenario.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/hapi/scenario.mjs @@ -51,7 +51,7 @@ const run = async () => { }, }); - // Route registered via a plugin produces a `plugin.hapi` span. + // Route registered via a plugin produces a `function` op span. await server.register({ name: 'testPlugin', version: '1.0.0', diff --git a/dev-packages/node-integration-tests/suites/tracing/hapi/test.ts b/dev-packages/node-integration-tests/suites/tracing/hapi/test.ts index 7999ff26d914..bcb62aaf04be 100644 --- a/dev-packages/node-integration-tests/suites/tracing/hapi/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/hapi/test.ts @@ -58,13 +58,13 @@ describe('hapi auto-instrumentation', () => { spans: expect.arrayContaining([ expect.objectContaining({ description: 'GET /plugin-route', - op: 'plugin.hapi', + op: 'function', origin, data: expect.objectContaining({ 'http.route': '/plugin-route', 'hapi.type': 'plugin', 'hapi.plugin.name': 'testPlugin', - 'sentry.op': 'plugin.hapi', + 'sentry.op': 'function', 'sentry.origin': origin, }), }), diff --git a/packages/server-utils/src/integrations/tracing-channel/hapi-utils.ts b/packages/server-utils/src/integrations/tracing-channel/hapi-utils.ts index 7bb9b7944232..b611a923b445 100644 --- a/packages/server-utils/src/integrations/tracing-channel/hapi-utils.ts +++ b/packages/server-utils/src/integrations/tracing-channel/hapi-utils.ts @@ -2,12 +2,15 @@ * OTel-free, `@hapi/*`-free port of the span-building helpers and handler/ext * wrap logic from the vendored `@opentelemetry/instrumentation-hapi` * (upstream @opentelemetry/instrumentation-hapi@0.64.0). Span output (names, - * ops, origins, attributes) is kept byte-identical to that instrumentation; + * origins, attributes) is kept close to that instrumentation — except the + * plugin-route op, which is normalized to the cross-framework `function` op; * span creation goes through the `@sentry/core` API and the OTel active-span * guard is replaced with `getActiveSpan()`. */ import { getActiveSpan, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, startSpan } from '@sentry/core'; +import { SENTRY_OP } from '@sentry/conventions/attributes'; +import { WEB_SERVER_FUNCTION_SPAN_OP } from '@sentry/conventions/op'; import type { LifecycleMethod, PatchableExtMethod, @@ -104,12 +107,14 @@ export const getExtMetadata = ( }; function startMetadataSpan(metadata: SpanMetadata, original: () => unknown): unknown { + const hapiType = metadata.attributes[AttributeNames.HAPI_TYPE]; + const op = hapiType === HapiLayerType.PLUGIN ? WEB_SERVER_FUNCTION_SPAN_OP : `${hapiType}.hapi`; return startSpan( { name: metadata.name, - op: `${metadata.attributes[AttributeNames.HAPI_TYPE]}.hapi`, attributes: { ...metadata.attributes, + [SENTRY_OP]: op, [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.hapi', }, }, From 9eb802c2a315a3313cbdd4c07040d478eccd99e4 Mon Sep 17 00:00:00 2001 From: Martin Sonnberger Date: Wed, 5 Aug 2026 17:35:00 +0200 Subject: [PATCH 4/4] use 'handler' instead --- .../elysia-bun/tests/transactions.test.ts | 2 +- .../elysia-node/tests/transactions.test.ts | 2 +- .../nestjs-11/tests/transactions.test.ts | 8 ++++---- .../nestjs-8/tests/transactions.test.ts | 8 ++++---- .../nestjs-basic/tests/transactions.test.ts | 8 ++++---- .../nestjs-fastify/tests/transactions.test.ts | 10 +++++----- .../nestjs-orchestrion/tests/transactions.test.ts | 2 +- .../tests/transactions.test.ts | 8 ++++---- .../nestjs-with-submodules/tests/transactions.test.ts | 8 ++++---- .../node-express-esm-loader/tests/server.test.ts | 4 ++-- .../tests/transactions.test.ts | 4 ++-- .../tests/transactions.test.ts | 4 ++-- .../node-express-v5/tests/transactions.test.ts | 4 ++-- .../node-express/tests/transactions.test.ts | 8 ++++---- .../node-fastify-3/tests/transactions.test.ts | 8 ++++---- .../tests/performance/trace-propagation.test.ts | 2 +- .../tests/performance/trace-propagation.test.ts | 2 +- .../tsx-express/tests/transactions.test.ts | 8 ++++---- .../suites/express/tracing/test.ts | 2 +- .../suites/tracing/fastify/test.ts | 4 ++-- .../node-integration-tests/suites/tracing/hapi/test.ts | 4 ++-- .../tracing/ignoreSpans-streamed/children/test.ts | 2 +- packages/core/src/integrations/express/patch-layer.ts | 5 +++-- .../test/lib/integrations/express/patch-layer.test.ts | 6 +++--- packages/elysia/src/withElysia.ts | 5 +++-- packages/nestjs/src/integrations/wrap-route.ts | 6 +++--- .../test/integrations/orchestrion-subscriber.test.ts | 2 +- .../tracing-channel/express/instrumentation.ts | 5 +++-- .../tracing-channel/fastify/instrumentation.ts | 5 +++-- .../src/integrations/tracing-channel/hapi-utils.ts | 6 +++--- 30 files changed, 78 insertions(+), 74 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/elysia-bun/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/elysia-bun/tests/transactions.test.ts index 0c09def64b7f..28ce37877f38 100644 --- a/dev-packages/e2e-tests/test-applications/elysia-bun/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/elysia-bun/tests/transactions.test.ts @@ -132,7 +132,7 @@ test('Creates lifecycle spans for Elysia hooks', async ({ baseURL, request }) => expect(spans).toContainEqual( expect.objectContaining({ description: 'Handle', - op: 'function', + op: 'handler', origin: 'auto.http.elysia', }), ); diff --git a/dev-packages/e2e-tests/test-applications/elysia-node/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/elysia-node/tests/transactions.test.ts index 75ba4d08994a..71cda9825466 100644 --- a/dev-packages/e2e-tests/test-applications/elysia-node/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/elysia-node/tests/transactions.test.ts @@ -132,7 +132,7 @@ test('Creates lifecycle spans for Elysia hooks', async ({ baseURL, request }) => expect(spans).toContainEqual( expect.objectContaining({ description: 'Handle', - op: 'function', + op: 'handler', origin: 'auto.http.elysia', }), ); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-11/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-11/tests/transactions.test.ts index 6ac9548ed03f..b6eac56cc0a0 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-11/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-11/tests/transactions.test.ts @@ -62,9 +62,9 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'express.type': 'request_handler', 'http.route': '/test-transaction', 'sentry.origin': 'auto.http.express', - 'sentry.op': 'function', + 'sentry.op': 'handler', }, - op: 'function', + op: 'handler', description: '/test-transaction', parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), span_id: expect.stringMatching(/[a-f0-9]{16}/), @@ -105,7 +105,7 @@ test('Sends an API route transaction', async ({ baseURL }) => { trace_id: expect.stringMatching(/[a-f0-9]{32}/), data: { 'sentry.origin': 'auto.http.nestjs', - 'sentry.op': 'function', + 'sentry.op': 'handler', component: '@nestjs/core', 'nestjs.version': expect.any(String), 'nestjs.type': 'handler', @@ -117,7 +117,7 @@ test('Sends an API route transaction', async ({ baseURL }) => { timestamp: expect.any(Number), status: 'ok', origin: 'auto.http.nestjs', - op: 'function', + op: 'handler', }, ]), transaction: 'GET /test-transaction', diff --git a/dev-packages/e2e-tests/test-applications/nestjs-8/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-8/tests/transactions.test.ts index 512f6d60d3b0..03593be28685 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-8/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-8/tests/transactions.test.ts @@ -66,9 +66,9 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'express.type': 'request_handler', 'http.route': '/test-transaction', 'sentry.origin': 'auto.http.express', - 'sentry.op': 'function', + 'sentry.op': 'handler', }, - op: 'function', + op: 'handler', description: '/test-transaction', parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), span_id: expect.stringMatching(/[a-f0-9]{16}/), @@ -109,7 +109,7 @@ test('Sends an API route transaction', async ({ baseURL }) => { trace_id: expect.stringMatching(/[a-f0-9]{32}/), data: { 'sentry.origin': 'auto.http.nestjs', - 'sentry.op': 'function', + 'sentry.op': 'handler', component: '@nestjs/core', 'nestjs.version': expect.any(String), 'nestjs.type': 'handler', @@ -121,7 +121,7 @@ test('Sends an API route transaction', async ({ baseURL }) => { timestamp: expect.any(Number), status: 'ok', origin: 'auto.http.nestjs', - op: 'function', + op: 'handler', }, ]), transaction: 'GET /test-transaction', diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic/tests/transactions.test.ts index 2d3100b95d1d..2e040acb1772 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-basic/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/tests/transactions.test.ts @@ -88,9 +88,9 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'express.type': 'request_handler', 'http.route': '/test-transaction', 'sentry.origin': 'auto.http.express', - 'sentry.op': 'function', + 'sentry.op': 'handler', }, - op: 'function', + op: 'handler', description: '/test-transaction', parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), span_id: expect.stringMatching(/[a-f0-9]{16}/), @@ -131,7 +131,7 @@ test('Sends an API route transaction', async ({ baseURL }) => { trace_id: expect.stringMatching(/[a-f0-9]{32}/), data: { 'sentry.origin': 'auto.http.nestjs', - 'sentry.op': 'function', + 'sentry.op': 'handler', component: '@nestjs/core', 'nestjs.version': expect.any(String), 'nestjs.type': 'handler', @@ -143,7 +143,7 @@ test('Sends an API route transaction', async ({ baseURL }) => { timestamp: expect.any(Number), status: 'ok', origin: 'auto.http.nestjs', - op: 'function', + op: 'handler', }, ]), transaction: 'GET /test-transaction', diff --git a/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/transactions.test.ts index 5198eeaa9439..c264e5cbfd7f 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/transactions.test.ts @@ -83,14 +83,14 @@ test.skip('Sends an API route transaction', async ({ baseURL }) => { trace_id: expect.stringMatching(/[a-f0-9]{32}/), data: { 'sentry.origin': 'auto.http.otel.fastify', - 'sentry.op': 'function', + 'sentry.op': 'handler', 'hook.name': 'fastify -> @sentry/instrumentation-fastify -> @fastify/middie - route-handler', 'fastify.type': 'request-handler', 'http.route': '/test-transaction', 'hook.callback.name': 'anonymous', }, description: '@fastify/middie - route-handler', - op: 'function', + op: 'handler', parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), start_timestamp: expect.any(Number), timestamp: expect.any(Number), @@ -125,7 +125,7 @@ test.skip('Sends an API route transaction', async ({ baseURL }) => { trace_id: expect.stringMatching(/[a-f0-9]{32}/), data: { 'sentry.origin': 'auto.http.nestjs', - 'sentry.op': 'function', + 'sentry.op': 'handler', component: '@nestjs/core', 'nestjs.version': expect.any(String), 'nestjs.type': 'handler', @@ -136,7 +136,7 @@ test.skip('Sends an API route transaction', async ({ baseURL }) => { start_timestamp: expect.any(Number), timestamp: expect.any(Number), status: 'ok', - op: 'function', + op: 'handler', origin: 'auto.http.nestjs', }, { @@ -799,7 +799,7 @@ test('Sets error status on nest spans when a handler throws', async ({ baseURL } expect(transactionEvent.spans).toEqual( expect.arrayContaining([ expect.objectContaining({ op: 'request_context.nestjs', status: 'internal_error' }), - expect.objectContaining({ op: 'function', status: 'internal_error' }), + expect.objectContaining({ op: 'handler', status: 'internal_error' }), ]), ); }); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-orchestrion/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-orchestrion/tests/transactions.test.ts index ef13bcfcfe7f..a61e3727a669 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-orchestrion/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-orchestrion/tests/transactions.test.ts @@ -60,7 +60,7 @@ test('request_context + handler: a route transaction nests the nestjs spans', as // request_handler span: wraps the controller method itself. const handler = (transactionEvent.spans ?? []).find( - span => span.op === 'function' && span.description === 'testTransaction', + span => span.op === 'handler' && span.description === 'testTransaction', ); expect(handler).toBeDefined(); }); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/tests/transactions.test.ts index 8c2c3bc8cbe9..27a666af610c 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/tests/transactions.test.ts @@ -62,9 +62,9 @@ test('Sends an API route transaction from module', async ({ baseURL }) => { 'express.type': 'request_handler', 'http.route': '/example-module/transaction', 'sentry.origin': 'auto.http.express', - 'sentry.op': 'function', + 'sentry.op': 'handler', }, - op: 'function', + op: 'handler', description: '/example-module/transaction', parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), span_id: expect.stringMatching(/[a-f0-9]{16}/), @@ -105,7 +105,7 @@ test('Sends an API route transaction from module', async ({ baseURL }) => { trace_id: expect.stringMatching(/[a-f0-9]{32}/), data: { 'sentry.origin': 'auto.http.nestjs', - 'sentry.op': 'function', + 'sentry.op': 'handler', component: '@nestjs/core', 'nestjs.version': expect.any(String), 'nestjs.type': 'handler', @@ -117,7 +117,7 @@ test('Sends an API route transaction from module', async ({ baseURL }) => { timestamp: expect.any(Number), status: 'ok', origin: 'auto.http.nestjs', - op: 'function', + op: 'handler', }, ]), transaction: 'GET /example-module/transaction', diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/transactions.test.ts index 3abf1ea5b5fa..1d29fdfad9a7 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/transactions.test.ts @@ -62,9 +62,9 @@ test('Sends an API route transaction from module', async ({ baseURL }) => { 'express.type': 'request_handler', 'http.route': '/example-module/transaction', 'sentry.origin': 'auto.http.express', - 'sentry.op': 'function', + 'sentry.op': 'handler', }, - op: 'function', + op: 'handler', description: '/example-module/transaction', parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), span_id: expect.stringMatching(/[a-f0-9]{16}/), @@ -105,7 +105,7 @@ test('Sends an API route transaction from module', async ({ baseURL }) => { trace_id: expect.stringMatching(/[a-f0-9]{32}/), data: { 'sentry.origin': 'auto.http.nestjs', - 'sentry.op': 'function', + 'sentry.op': 'handler', component: '@nestjs/core', 'nestjs.version': expect.any(String), 'nestjs.type': 'handler', @@ -117,7 +117,7 @@ test('Sends an API route transaction from module', async ({ baseURL }) => { timestamp: expect.any(Number), status: 'ok', origin: 'auto.http.nestjs', - op: 'function', + op: 'handler', }, ]), transaction: 'GET /example-module/transaction', diff --git a/dev-packages/e2e-tests/test-applications/node-express-esm-loader/tests/server.test.ts b/dev-packages/e2e-tests/test-applications/node-express-esm-loader/tests/server.test.ts index 9a51236368db..427c9f334910 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-esm-loader/tests/server.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-express-esm-loader/tests/server.test.ts @@ -102,9 +102,9 @@ test('Should record a transaction for route with parameters', async ({ request } 'express.type': 'request_handler', 'http.route': '/test-transaction/:param', 'sentry.origin': 'auto.http.express', - 'sentry.op': 'function', + 'sentry.op': 'handler', }, - op: 'function', + op: 'handler', description: '/test-transaction/:param', origin: 'auto.http.express', parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-express-orchestrion-cjs/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-express-orchestrion-cjs/tests/transactions.test.ts index 44070eaf84a9..024b5efa126d 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-orchestrion-cjs/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-express-orchestrion-cjs/tests/transactions.test.ts @@ -86,13 +86,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { expect(spans).toContainEqual({ data: { 'sentry.origin': 'auto.http.express', - 'sentry.op': 'function', + 'sentry.op': 'handler', 'http.route': '/test-transaction', 'express.name': '/test-transaction', 'express.type': 'request_handler', }, description: '/test-transaction', - op: 'function', + op: 'handler', origin: 'auto.http.express', parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-express-orchestrion/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-express-orchestrion/tests/transactions.test.ts index fee291536d64..4a45adad7839 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-orchestrion/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-express-orchestrion/tests/transactions.test.ts @@ -86,13 +86,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { expect(spans).toContainEqual({ data: { 'sentry.origin': 'auto.http.express', - 'sentry.op': 'function', + 'sentry.op': 'handler', 'http.route': '/test-transaction', 'express.name': '/test-transaction', 'express.type': 'request_handler', }, description: '/test-transaction', - op: 'function', + op: 'handler', origin: 'auto.http.express', parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-express-v5/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-express-v5/tests/transactions.test.ts index 089832650ac6..e3a7944dc28a 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-v5/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-express-v5/tests/transactions.test.ts @@ -86,13 +86,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { expect(spans).toContainEqual({ data: { 'sentry.origin': 'auto.http.express', - 'sentry.op': 'function', + 'sentry.op': 'handler', 'http.route': '/test-transaction', 'express.name': '/test-transaction', 'express.type': 'request_handler', }, description: '/test-transaction', - op: 'function', + op: 'handler', origin: 'auto.http.express', parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts index 0d0081e22f71..8629fefe2efb 100644 --- a/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts @@ -122,13 +122,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { expect(spans).toContainEqual({ data: { 'sentry.origin': 'auto.http.express', - 'sentry.op': 'function', + 'sentry.op': 'handler', 'http.route': '/test-transaction', 'express.name': '/test-transaction', 'express.type': 'request_handler', }, description: '/test-transaction', - op: 'function', + op: 'handler', origin: 'auto.http.express', parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), span_id: expect.stringMatching(/[a-f0-9]{16}/), @@ -198,14 +198,14 @@ test('Sends an API route transaction for an errored route', async ({ baseURL }) expect(spans).toContainEqual({ data: { 'sentry.origin': 'auto.http.express', - 'sentry.op': 'function', + 'sentry.op': 'handler', 'http.route': '/test-exception/:id', 'express.name': '/test-exception/:id', 'express.type': 'request_handler', 'error.type': 'Error', }, description: '/test-exception/:id', - op: 'function', + op: 'handler', origin: 'auto.http.express', parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/transactions.test.ts index 13746f07fa46..281d1028678c 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-fastify-3/tests/transactions.test.ts @@ -75,7 +75,7 @@ test.skip('Sends an API route transaction', async ({ baseURL }) => { expect(spans).toContainEqual({ data: { 'sentry.origin': 'auto.http.otel.fastify', - 'sentry.op': 'function', + 'sentry.op': 'handler', 'fastify.root': '@sentry/instrumentation-fastify', 'http.request.method': 'GET', 'url.path': '/test-transaction', @@ -83,7 +83,7 @@ test.skip('Sends an API route transaction', async ({ baseURL }) => { 'http.response.status_code': 200, }, description: 'GET /test-transaction', - op: 'function', + op: 'handler', parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), span_id: expect.stringMatching(/[a-f0-9]{16}/), start_timestamp: expect.any(Number), @@ -97,12 +97,12 @@ test.skip('Sends an API route transaction', async ({ baseURL }) => { expect(spans).toContainEqual({ data: expect.objectContaining({ 'sentry.origin': 'auto.http.otel.fastify', - 'sentry.op': 'function', + 'sentry.op': 'handler', 'fastify.type': expect.stringMatching(/request[-_]handler/), 'http.route': '/test-transaction', }), description: expect.stringContaining('sentry-fastify-error-handler'), - op: 'function', + op: 'handler', parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), span_id: expect.stringMatching(/[a-f0-9]{16}/), start_timestamp: expect.any(Number), diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/trace-propagation.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/trace-propagation.test.ts index 0a4bc17f622b..b62e784ff3e3 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/trace-propagation.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/trace-propagation.test.ts @@ -32,7 +32,7 @@ test.describe('Trace propagation', () => { expect(clientTx.contexts?.trace?.trace_id).toEqual(serverTx.contexts?.trace?.trace_id); - const requestHandlerSpan = serverTx.spans?.find(span => span.op === 'function'); + const requestHandlerSpan = serverTx.spans?.find(span => span.op === 'handler'); expect(requestHandlerSpan).toBeDefined(); expect(clientTx.contexts?.trace?.parent_span_id).toBe(requestHandlerSpan?.span_id); diff --git a/dev-packages/e2e-tests/test-applications/react-router-8-framework/tests/performance/trace-propagation.test.ts b/dev-packages/e2e-tests/test-applications/react-router-8-framework/tests/performance/trace-propagation.test.ts index 9f356007cb6e..2c888675429e 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-8-framework/tests/performance/trace-propagation.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-8-framework/tests/performance/trace-propagation.test.ts @@ -32,7 +32,7 @@ test.describe('Trace propagation', () => { expect(clientTx.contexts?.trace?.trace_id).toEqual(serverTx.contexts?.trace?.trace_id); - const requestHandlerSpan = serverTx.spans?.find(span => span.op === 'function'); + const requestHandlerSpan = serverTx.spans?.find(span => span.op === 'handler'); expect(requestHandlerSpan).toBeDefined(); expect(clientTx.contexts?.trace?.parent_span_id).toBe(requestHandlerSpan?.span_id); diff --git a/dev-packages/e2e-tests/test-applications/tsx-express/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/tsx-express/tests/transactions.test.ts index 0fbe6275925e..b86a86f6adc8 100644 --- a/dev-packages/e2e-tests/test-applications/tsx-express/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/tsx-express/tests/transactions.test.ts @@ -108,13 +108,13 @@ test('Sends an API route transaction', async ({ baseURL }) => { expect(spans).toContainEqual({ data: { 'sentry.origin': 'auto.http.express', - 'sentry.op': 'function', + 'sentry.op': 'handler', 'http.route': '/test-transaction', 'express.name': '/test-transaction', 'express.type': 'request_handler', }, description: '/test-transaction', - op: 'function', + op: 'handler', origin: 'auto.http.express', parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), span_id: expect.stringMatching(/[a-f0-9]{16}/), @@ -184,14 +184,14 @@ test('Sends an API route transaction for an errored route', async ({ baseURL }) expect(spans).toContainEqual({ data: { 'sentry.origin': 'auto.http.express', - 'sentry.op': 'function', + 'sentry.op': 'handler', 'http.route': '/test-exception/:id', 'express.name': '/test-exception/:id', 'express.type': 'request_handler', 'error.type': 'Error', }, description: '/test-exception/:id', - op: 'function', + op: 'handler', origin: 'auto.http.express', parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), span_id: expect.stringMatching(/[a-f0-9]{16}/), 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 231f3206e7c9..9aac6f7518b3 100644 --- a/dev-packages/node-integration-tests/suites/express/tracing/test.ts +++ b/dev-packages/node-integration-tests/suites/express/tracing/test.ts @@ -41,7 +41,7 @@ describe('express tracing', () => { 'express.type': 'request_handler', }), description: '/test/express', - op: 'function', + op: 'handler', origin: 'auto.http.express', }), ]), diff --git a/dev-packages/node-integration-tests/suites/tracing/fastify/test.ts b/dev-packages/node-integration-tests/suites/tracing/fastify/test.ts index 359e1e483c28..0806abf3de72 100644 --- a/dev-packages/node-integration-tests/suites/tracing/fastify/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/fastify/test.ts @@ -26,10 +26,10 @@ describe('fastify auto-instrumentation', () => { }), }), expect.objectContaining({ - op: 'function', + op: 'handler', origin: 'auto.http.otel.fastify', data: expect.objectContaining({ - 'sentry.op': 'function', + 'sentry.op': 'handler', 'sentry.origin': 'auto.http.otel.fastify', }), }), diff --git a/dev-packages/node-integration-tests/suites/tracing/hapi/test.ts b/dev-packages/node-integration-tests/suites/tracing/hapi/test.ts index bcb62aaf04be..b908a5ed13fd 100644 --- a/dev-packages/node-integration-tests/suites/tracing/hapi/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/hapi/test.ts @@ -58,13 +58,13 @@ describe('hapi auto-instrumentation', () => { spans: expect.arrayContaining([ expect.objectContaining({ description: 'GET /plugin-route', - op: 'function', + op: 'handler', origin, data: expect.objectContaining({ 'http.route': '/plugin-route', 'hapi.type': 'plugin', 'hapi.plugin.name': 'testPlugin', - 'sentry.op': 'function', + 'sentry.op': 'handler', 'sentry.origin': origin, }), }), diff --git a/dev-packages/node-integration-tests/suites/tracing/ignoreSpans-streamed/children/test.ts b/dev-packages/node-integration-tests/suites/tracing/ignoreSpans-streamed/children/test.ts index 9e429c8946e0..c1ac1f7fc4a6 100644 --- a/dev-packages/node-integration-tests/suites/tracing/ignoreSpans-streamed/children/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/ignoreSpans-streamed/children/test.ts @@ -33,7 +33,7 @@ describe('filtering child spans with ignoreSpans (streaming)', () => { ); const queryMiddlewareSpan = getSpan('query', 'middleware'); const corsMiddlewareSpan = getSpan('corsMiddleware', 'middleware'); - const requestHandlerSpan = getSpan('/test/express', 'function'); + const requestHandlerSpan = getSpan('/test/express', 'handler'); const httpServerSpan = getSpan('GET /test/express', 'http.server'); const customSpan = getSpan('custom', 'custom'); const customGrandchildSpan = getSpan('custom-grandchild', 'custom'); diff --git a/packages/core/src/integrations/express/patch-layer.ts b/packages/core/src/integrations/express/patch-layer.ts index d8211c3c7f2f..f55554566a26 100644 --- a/packages/core/src/integrations/express/patch-layer.ts +++ b/packages/core/src/integrations/express/patch-layer.ts @@ -28,7 +28,7 @@ */ import { SENTRY_OP } from '@sentry/conventions/attributes'; -import { WEB_SERVER_FUNCTION_SPAN_OP, WEB_SERVER_MIDDLEWARE_SPAN_OP } from '@sentry/conventions/op'; +import { WEB_SERVER_MIDDLEWARE_SPAN_OP } from '@sentry/conventions/op'; import { DEBUG_BUILD } from '../../debug-build'; import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../../semanticAttributes'; import { SPAN_STATUS_ERROR, startSpanManual, withActiveSpan } from '../../tracing'; @@ -128,7 +128,8 @@ export function patchLayer( type === 'middleware' ? WEB_SERVER_MIDDLEWARE_SPAN_OP : type === 'request_handler' - ? WEB_SERVER_FUNCTION_SPAN_OP + ? // TODO(conventions): Replace with the `handler` span op constant once it is released in `@sentry/conventions`. + 'handler' : `${type}.express`, }); if (actualMatchedRoute) { diff --git a/packages/core/test/lib/integrations/express/patch-layer.test.ts b/packages/core/test/lib/integrations/express/patch-layer.test.ts index 00ee33365241..5dfea7cce636 100644 --- a/packages/core/test/lib/integrations/express/patch-layer.test.ts +++ b/packages/core/test/lib/integrations/express/patch-layer.test.ts @@ -349,7 +349,7 @@ describe('patchLayer', () => { 'express.name': 'a/:boo/:car/layerPath', 'express.type': 'request_handler', 'http.route': '/a/:boo/:car/layerPath', - 'sentry.op': 'function', + 'sentry.op': 'handler', 'sentry.origin': 'auto.http.express', }, description: 'a/:boo/:car/layerPath', @@ -403,7 +403,7 @@ describe('patchLayer', () => { 'express.name': 'a/:boo/:car', 'express.type': 'request_handler', 'http.route': '/a/:boo/:car', - 'sentry.op': 'function', + 'sentry.op': 'handler', 'sentry.origin': 'auto.http.express', }, description: 'a/:boo/:car', @@ -414,7 +414,7 @@ describe('patchLayer', () => { 'express.name': 'a/:boo/:car', 'express.type': 'request_handler', 'http.route': '/a/:boo/:car', - 'sentry.op': 'function', + 'sentry.op': 'handler', 'sentry.origin': 'auto.http.express', }, description: 'a/:boo/:car', diff --git a/packages/elysia/src/withElysia.ts b/packages/elysia/src/withElysia.ts index d147928101ce..fd1279275c33 100644 --- a/packages/elysia/src/withElysia.ts +++ b/packages/elysia/src/withElysia.ts @@ -1,5 +1,5 @@ import { HTTP_ROUTE, URL_FULL, URL_PATH } from '@sentry/conventions/attributes'; -import { WEB_SERVER_FUNCTION_SPAN_OP, WEB_SERVER_MIDDLEWARE_SPAN_OP } from '@sentry/conventions/op'; +import { WEB_SERVER_MIDDLEWARE_SPAN_OP } from '@sentry/conventions/op'; import type { Span } from '@sentry/core'; import { captureException, @@ -34,7 +34,8 @@ const ELYSIA_LIFECYCLE_OP_MAP: Record = { Parse: WEB_SERVER_MIDDLEWARE_SPAN_OP, Transform: WEB_SERVER_MIDDLEWARE_SPAN_OP, BeforeHandle: WEB_SERVER_MIDDLEWARE_SPAN_OP, - Handle: WEB_SERVER_FUNCTION_SPAN_OP, + // TODO(conventions): Replace with the `handler` span op constant once it is released in `@sentry/conventions`. + Handle: 'handler', AfterHandle: WEB_SERVER_MIDDLEWARE_SPAN_OP, MapResponse: WEB_SERVER_MIDDLEWARE_SPAN_OP, AfterResponse: WEB_SERVER_MIDDLEWARE_SPAN_OP, diff --git a/packages/nestjs/src/integrations/wrap-route.ts b/packages/nestjs/src/integrations/wrap-route.ts index 271cdd349f21..78cd6e6957eb 100644 --- a/packages/nestjs/src/integrations/wrap-route.ts +++ b/packages/nestjs/src/integrations/wrap-route.ts @@ -1,5 +1,4 @@ import { HTTP_METHOD, HTTP_ROUTE, SENTRY_OP, URL_FULL } from '@sentry/conventions/attributes'; -import { WEB_SERVER_FUNCTION_SPAN_OP } from '@sentry/conventions/op'; import type { SpanAttributes } from '@sentry/core'; import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, startSpan } from '@sentry/core'; import type { AnyFn } from './helpers'; @@ -44,7 +43,7 @@ export function getAppCreationSpanOptions( /** * Wrap the route-handler callback so each invocation opens the request-handler - * (`function` op) span. Preserve the original `.name` and reflect-metadata so + * (`handler` op) span. Preserve the original `.name` and reflect-metadata so * NestJS reflection is unaffected. */ export function wrapRouteHandler(callback: AnyFn, moduleVersion?: string): AnyFn { @@ -54,7 +53,8 @@ export function wrapRouteHandler(callback: AnyFn, moduleVersion?: string): AnyFn const spanName = callback.name || 'anonymous nest handler'; const attributes: SpanAttributes = { component: NESTJS_COMPONENT, - [SENTRY_OP]: WEB_SERVER_FUNCTION_SPAN_OP, + // TODO(conventions): Replace with the `handler` span op constant once it is released in `@sentry/conventions`. + [SENTRY_OP]: 'handler', [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: HTTP_ORIGIN, [AttributeNames.TYPE]: NestType.REQUEST_HANDLER, [AttributeNames.CALLBACK]: callback.name, diff --git a/packages/nestjs/test/integrations/orchestrion-subscriber.test.ts b/packages/nestjs/test/integrations/orchestrion-subscriber.test.ts index 6a38e3b2c916..44171b9fe028 100644 --- a/packages/nestjs/test/integrations/orchestrion-subscriber.test.ts +++ b/packages/nestjs/test/integrations/orchestrion-subscriber.test.ts @@ -278,7 +278,7 @@ describe('NestJS orchestrion subscriber: request_context / request_handler', () expect(handlerSpanJson).toBeDefined(); expect(handlerSpanJson!.description).toBe('getCats'); - expect(handlerSpanJson!.op).toBe('function'); + expect(handlerSpanJson!.op).toBe('handler'); expect(handlerSpanJson!.origin).toBe('auto.http.nestjs'); expect(handlerSpanJson!.data).toMatchObject({ component: '@nestjs/core', diff --git a/packages/server-utils/src/integrations/tracing-channel/express/instrumentation.ts b/packages/server-utils/src/integrations/tracing-channel/express/instrumentation.ts index 0d0fb6ccfac3..3e89c424424d 100644 --- a/packages/server-utils/src/integrations/tracing-channel/express/instrumentation.ts +++ b/packages/server-utils/src/integrations/tracing-channel/express/instrumentation.ts @@ -1,6 +1,6 @@ import type * as diagnosticsChannel from 'node:diagnostics_channel'; import { HTTP_ROUTE, SENTRY_OP } from '@sentry/conventions/attributes'; -import { WEB_SERVER_FUNCTION_SPAN_OP, WEB_SERVER_MIDDLEWARE_SPAN_OP } from '@sentry/conventions/op'; +import { WEB_SERVER_MIDDLEWARE_SPAN_OP } from '@sentry/conventions/op'; import type { Span } from '@sentry/core'; import { debug, @@ -225,7 +225,8 @@ function getSpanForLayer(data: HandleChannelContext, options: ExpressIntegration type === 'middleware' ? WEB_SERVER_MIDDLEWARE_SPAN_OP : type === 'request_handler' - ? WEB_SERVER_FUNCTION_SPAN_OP + ? // TODO(conventions): Replace with the `handler` span op constant once it is released in `@sentry/conventions`. + 'handler' : `${type}.express`, [ATTR_EXPRESS_NAME]: name, [ATTR_EXPRESS_TYPE]: type, diff --git a/packages/server-utils/src/integrations/tracing-channel/fastify/instrumentation.ts b/packages/server-utils/src/integrations/tracing-channel/fastify/instrumentation.ts index 3b17e10e2dbe..cb3f64d6eca0 100644 --- a/packages/server-utils/src/integrations/tracing-channel/fastify/instrumentation.ts +++ b/packages/server-utils/src/integrations/tracing-channel/fastify/instrumentation.ts @@ -22,7 +22,7 @@ import { SENTRY_OP, URL_PATH, } from '@sentry/conventions/attributes'; -import { WEB_SERVER_FUNCTION_SPAN_OP, WEB_SERVER_MIDDLEWARE_SPAN_OP } from '@sentry/conventions/op'; +import { WEB_SERVER_MIDDLEWARE_SPAN_OP } from '@sentry/conventions/op'; import type { Span } from '@sentry/core'; import { isObjectLike, @@ -43,7 +43,8 @@ const SUPPORTED_VERSIONS = '>=3.21.0 <6'; const ORIGIN = 'auto.http.otel.fastify'; const HOOK_OP = WEB_SERVER_MIDDLEWARE_SPAN_OP; -const REQUEST_HANDLER_OP = WEB_SERVER_FUNCTION_SPAN_OP; +// TODO(conventions): Replace with the `handler` span op constant once it is released in `@sentry/conventions`. +const REQUEST_HANDLER_OP = 'handler'; const FASTIFY_HOOKS = [ 'onRequest', diff --git a/packages/server-utils/src/integrations/tracing-channel/hapi-utils.ts b/packages/server-utils/src/integrations/tracing-channel/hapi-utils.ts index b611a923b445..9737978f8095 100644 --- a/packages/server-utils/src/integrations/tracing-channel/hapi-utils.ts +++ b/packages/server-utils/src/integrations/tracing-channel/hapi-utils.ts @@ -3,14 +3,13 @@ * wrap logic from the vendored `@opentelemetry/instrumentation-hapi` * (upstream @opentelemetry/instrumentation-hapi@0.64.0). Span output (names, * origins, attributes) is kept close to that instrumentation — except the - * plugin-route op, which is normalized to the cross-framework `function` op; + * plugin-route op, which is normalized to the cross-framework `handler` op; * span creation goes through the `@sentry/core` API and the OTel active-span * guard is replaced with `getActiveSpan()`. */ import { getActiveSpan, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, startSpan } from '@sentry/core'; import { SENTRY_OP } from '@sentry/conventions/attributes'; -import { WEB_SERVER_FUNCTION_SPAN_OP } from '@sentry/conventions/op'; import type { LifecycleMethod, PatchableExtMethod, @@ -108,7 +107,8 @@ export const getExtMetadata = ( function startMetadataSpan(metadata: SpanMetadata, original: () => unknown): unknown { const hapiType = metadata.attributes[AttributeNames.HAPI_TYPE]; - const op = hapiType === HapiLayerType.PLUGIN ? WEB_SERVER_FUNCTION_SPAN_OP : `${hapiType}.hapi`; + // TODO(conventions): Replace `'handler'` with the `handler` span op constant once it is released in `@sentry/conventions`. + const op = hapiType === HapiLayerType.PLUGIN ? 'handler' : `${hapiType}.hapi`; return startSpan( { name: metadata.name,