diff --git a/dev-packages/deno-integration-tests/suites/orchestrion-hapi/test.ts b/dev-packages/deno-integration-tests/suites/orchestrion-hapi/test.ts index b2f4afa5b7a3..af0a9c92a0c4 100644 --- a/dev-packages/deno-integration-tests/suites/orchestrion-hapi/test.ts +++ b/dev-packages/deno-integration-tests/suites/orchestrion-hapi/test.ts @@ -42,8 +42,8 @@ Deno.test('hapi instrumentation: orchestrion:@hapi/hapi:route channel wraps the "'parent' transaction", ); - const hapiSpan = parent.spans?.find(s => s.op === 'router.hapi'); - assertExists(hapiSpan, `expected a router.hapi span, got ops: ${parent.spans?.map(s => s.op).join(', ')}`); + const hapiSpan = parent.spans?.find(s => s.op === 'router'); + assertExists(hapiSpan, `expected a router span, got ops: ${parent.spans?.map(s => s.op).join(', ')}`); assertEquals(hapiSpan!.description, 'GET /hello'); assertEquals(hapiSpan!.data?.['hapi.type'], 'router'); assertEquals(hapiSpan!.data?.['http.route'], '/hello'); diff --git a/dev-packages/e2e-tests/test-applications/node-hapi/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-hapi/tests/transactions.test.ts index b56d21a31fd6..69c7c470db20 100644 --- a/dev-packages/e2e-tests/test-applications/node-hapi/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-hapi/tests/transactions.test.ts @@ -74,11 +74,11 @@ test('Sends successful transaction', async ({ baseURL }) => { 'hapi.type': 'router', 'http.method': 'GET', 'http.route': '/test-success', - 'sentry.op': 'router.hapi', + 'sentry.op': 'router', 'sentry.origin': 'auto.http.hapi', }, description: 'GET /test-success', - op: 'router.hapi', + op: 'router', origin: 'auto.http.hapi', parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), span_id: expect.stringMatching(/[a-f0-9]{16}/), @@ -91,12 +91,12 @@ test('Sends successful transaction', async ({ baseURL }) => { // this comes from "onPreResponse" data: { 'hapi.type': 'server.ext', - 'sentry.op': 'server.ext.hapi', + 'sentry.op': 'middleware', 'sentry.origin': 'auto.http.hapi', 'server.ext.type': 'onPreResponse', }, description: 'ext - onPreResponse', - op: 'server.ext.hapi', + op: 'middleware', origin: 'auto.http.hapi', 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-koa/tests/instrumented-paths.test.ts b/dev-packages/e2e-tests/test-applications/node-koa/tests/instrumented-paths.test.ts index 0e1ccb7c0787..f74be599663b 100644 --- a/dev-packages/e2e-tests/test-applications/node-koa/tests/instrumented-paths.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-koa/tests/instrumented-paths.test.ts @@ -15,11 +15,11 @@ test('instruments RegExp router routes', async ({ baseURL }) => { expect(transactionEvent.spans).toEqual( expect.arrayContaining([ expect.objectContaining({ - op: 'router.koa', + op: 'router', origin: 'auto.http.koa', data: expect.objectContaining({ 'koa.type': 'router', - 'sentry.op': 'router.koa', + 'sentry.op': 'router', 'sentry.origin': 'auto.http.koa', 'http.route': '/^\\/test-regexp/', }), @@ -43,12 +43,12 @@ test('instruments nested routers with the composed http.route', async ({ baseURL expect(transactionEvent.spans).toEqual( expect.arrayContaining([ expect.objectContaining({ - op: 'router.koa', + op: 'router', description: '/:first/details/:id', data: expect.objectContaining({ 'koa.type': 'router', 'http.route': '/:first/details/:id', - 'sentry.op': 'router.koa', + 'sentry.op': 'router', 'sentry.origin': 'auto.http.koa', }), }), @@ -69,9 +69,7 @@ test('does not instrument the same middleware twice', async ({ baseURL }) => { // The route stack is [sharedRouteMiddleware, sharedRouteMiddleware, handler]; the repeated // middleware instance is skipped, leaving one span for it plus the handler span. - const dedupSpans = transactionEvent.spans?.filter( - span => span.op === 'router.koa' && span.description === '/test-dedup', - ); + const dedupSpans = transactionEvent.spans?.filter(span => span.op === 'router' && span.description === '/test-dedup'); expect(dedupSpans).toHaveLength(2); }); @@ -90,7 +88,7 @@ test('marks the layer span as errored when a handler throws', async ({ baseURL } expect(transactionEvent.spans).toEqual( expect.arrayContaining([ expect.objectContaining({ - op: 'router.koa', + op: 'router', origin: 'auto.http.koa', status: 'internal_error', }), diff --git a/dev-packages/e2e-tests/test-applications/node-koa/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-koa/tests/transactions.test.ts index 0cc2f7d1664d..4749feefddb1 100644 --- a/dev-packages/e2e-tests/test-applications/node-koa/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-koa/tests/transactions.test.ts @@ -104,9 +104,9 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'koa.name': '/test-transaction', 'koa.type': 'router', 'sentry.origin': 'auto.http.koa', - 'sentry.op': 'router.koa', + 'sentry.op': 'router', }, - op: 'router.koa', + op: 'router', description: '/test-transaction', 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/tracing/hapi/scenario.mjs b/dev-packages/node-integration-tests/suites/tracing/hapi/scenario.mjs index 2b7589f0f345..9148e4092fe1 100644 --- a/dev-packages/node-integration-tests/suites/tracing/hapi/scenario.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/hapi/scenario.mjs @@ -64,7 +64,7 @@ const run = async () => { }, }); - // Server extension produces a `server.ext.hapi` span. + // Server extension produces a `middleware` span. server.ext('onPreResponse', (request, h) => h.continue); await Sentry.setupHapiErrorHandler(server); 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 b908a5ed13fd..90fd541416e5 100644 --- a/dev-packages/node-integration-tests/suites/tracing/hapi/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/hapi/test.ts @@ -22,10 +22,10 @@ describe('hapi auto-instrumentation', () => { 'http.method': 'GET', 'hapi.type': 'router', 'sentry.origin': origin, - 'sentry.op': 'router.hapi', + 'sentry.op': 'router', }), description: 'GET /', - op: 'router.hapi', + op: 'router', origin, status: 'ok', }), @@ -70,12 +70,12 @@ describe('hapi auto-instrumentation', () => { }), expect.objectContaining({ description: 'ext - onPreResponse', - op: 'server.ext.hapi', + op: 'middleware', origin, data: expect.objectContaining({ 'hapi.type': 'server.ext', 'server.ext.type': 'onPreResponse', - 'sentry.op': 'server.ext.hapi', + 'sentry.op': 'middleware', 'sentry.origin': origin, }), }), diff --git a/dev-packages/node-integration-tests/suites/tracing/koa/test.ts b/dev-packages/node-integration-tests/suites/tracing/koa/test.ts index 5cec23c83c9e..a6653e7d23cf 100644 --- a/dev-packages/node-integration-tests/suites/tracing/koa/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/koa/test.ts @@ -34,13 +34,13 @@ describe('koa auto-instrumentation', () => { // Router layer span (from `@koa/router`), carrying the matched route. expect.objectContaining({ description: '/', - op: 'router.koa', + op: 'router', origin, data: expect.objectContaining({ 'http.route': '/', 'koa.type': 'router', 'koa.name': '/', - 'sentry.op': 'router.koa', + 'sentry.op': 'router', 'sentry.origin': origin, }), }), @@ -73,13 +73,13 @@ describe('koa auto-instrumentation', () => { spans: expect.arrayContaining([ expect.objectContaining({ description: '/test-param/:id', - op: 'router.koa', + op: 'router', origin, data: expect.objectContaining({ 'http.route': '/test-param/:id', 'koa.type': 'router', 'koa.name': '/test-param/:id', - 'sentry.op': 'router.koa', + 'sentry.op': 'router', 'sentry.origin': origin, }), }), diff --git a/packages/core/src/integrations/express/patch-layer.ts b/packages/core/src/integrations/express/patch-layer.ts index f55554566a26..cff2853e673e 100644 --- a/packages/core/src/integrations/express/patch-layer.ts +++ b/packages/core/src/integrations/express/patch-layer.ts @@ -45,6 +45,8 @@ import { ATTR_EXPRESS_TYPE, ATTR_EXPRESS_NAME, ExpressLayerType_ROUTER, + ExpressLayerType_MIDDLEWARE, + ExpressLayerType_REQUEST_HANDLER, } from './types'; import { asErrorAndMessage, @@ -58,6 +60,13 @@ import { getDefaultIsolationScope } from '../../defaultScopes'; import { getOriginalFunction, markFunctionWrapped } from '../../utils/object'; import { setSDKProcessingMetadata } from './set-sdk-processing-metadata'; +// TODO(conventions): Replace `'handler'` and `'router'` with their span op constants once they are released in `@sentry/conventions`. +const EXPRESS_TYPE_TO_SPAN_OP: Record = { + [ExpressLayerType_MIDDLEWARE]: WEB_SERVER_MIDDLEWARE_SPAN_OP, + [ExpressLayerType_REQUEST_HANDLER]: 'handler', + [ExpressLayerType_ROUTER]: 'router', +}; + export type ExpressPatchLayerOptions = Pick< ExpressIntegrationOptions, 'onRouteResolved' | 'ignoreLayers' | 'ignoreLayersType' @@ -124,13 +133,7 @@ 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 === 'request_handler' - ? // TODO(conventions): Replace with the `handler` span op constant once it is released in `@sentry/conventions`. - 'handler' - : `${type}.express`, + [SENTRY_OP]: EXPRESS_TYPE_TO_SPAN_OP[type], }); 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 5dfea7cce636..10b5e8b2a3c3 100644 --- a/packages/core/test/lib/integrations/express/patch-layer.test.ts +++ b/packages/core/test/lib/integrations/express/patch-layer.test.ts @@ -493,7 +493,7 @@ describe('patchLayer', () => { data: { 'express.name': '/c', 'express.type': 'router', - 'sentry.op': 'router.express', + 'sentry.op': 'router', 'sentry.origin': 'auto.http.express', }, description: '/c', 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 3e89c424424d..1d8d88f7a352 100644 --- a/packages/server-utils/src/integrations/tracing-channel/express/instrumentation.ts +++ b/packages/server-utils/src/integrations/tracing-channel/express/instrumentation.ts @@ -42,6 +42,13 @@ const ORIGIN = 'auto.http.express'; const ATTR_EXPRESS_NAME = 'express.name'; const ATTR_EXPRESS_TYPE = 'express.type'; +// TODO(conventions): Replace `'handler'` and `'router'` with their span op constants once they are released in `@sentry/conventions`. +const EXPRESS_TYPE_TO_SPAN_OP: Record = { + middleware: WEB_SERVER_MIDDLEWARE_SPAN_OP, + request_handler: 'handler', + router: 'router', +}; + const NOOP = (): void => {}; let _isInstrumented = false; @@ -221,13 +228,7 @@ function getSpanForLayer(data: HandleChannelContext, options: ExpressIntegration name, attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ORIGIN, - [SENTRY_OP]: - type === 'middleware' - ? WEB_SERVER_MIDDLEWARE_SPAN_OP - : type === 'request_handler' - ? // TODO(conventions): Replace with the `handler` span op constant once it is released in `@sentry/conventions`. - 'handler' - : `${type}.express`, + [SENTRY_OP]: EXPRESS_TYPE_TO_SPAN_OP[type], [ATTR_EXPRESS_NAME]: name, [ATTR_EXPRESS_TYPE]: type, ...(matchedRoute ? { [HTTP_ROUTE]: matchedRoute } : {}), 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 9737978f8095..d947cfecae4f 100644 --- a/packages/server-utils/src/integrations/tracing-channel/hapi-utils.ts +++ b/packages/server-utils/src/integrations/tracing-channel/hapi-utils.ts @@ -3,13 +3,15 @@ * 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 `handler` op; - * span creation goes through the `@sentry/core` API and the OTel active-span - * guard is replaced with `getActiveSpan()`. + * span ops, which are normalized to cross-framework ops (`handler` for plugin + * routes, `router` for routes, `middleware` for server extensions); 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_MIDDLEWARE_SPAN_OP } from '@sentry/conventions/op'; import type { LifecycleMethod, PatchableExtMethod, @@ -105,10 +107,16 @@ export const getExtMetadata = ( }; }; +// TODO(conventions): Replace `'handler'` and `'router'` with their span op constants once they are released in `@sentry/conventions`. +const HAPI_TYPE_TO_SPAN_OP: Record = { + [HapiLayerType.PLUGIN]: 'handler', + [HapiLayerType.ROUTER]: 'router', + [HapiLayerType.EXT]: WEB_SERVER_MIDDLEWARE_SPAN_OP, +}; + function startMetadataSpan(metadata: SpanMetadata, original: () => unknown): unknown { const hapiType = metadata.attributes[AttributeNames.HAPI_TYPE]; - // 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`; + const op = hapiType ? HAPI_TYPE_TO_SPAN_OP[hapiType] : undefined; return startSpan( { name: metadata.name, diff --git a/packages/server-utils/src/integrations/tracing-channel/koa.ts b/packages/server-utils/src/integrations/tracing-channel/koa.ts index 82ca5fd4c8bb..cc418b12720a 100644 --- a/packages/server-utils/src/integrations/tracing-channel/koa.ts +++ b/packages/server-utils/src/integrations/tracing-channel/koa.ts @@ -180,7 +180,8 @@ function patchLayer( name, attributes: { ...metadata.attributes, - [SENTRY_OP]: layerType === LAYER_TYPE.MIDDLEWARE ? WEB_SERVER_MIDDLEWARE_SPAN_OP : `${layerType}.koa`, + // TODO(conventions): Replace `'router'` with the `router` span op constant once it is released in `@sentry/conventions`. + [SENTRY_OP]: layerType === LAYER_TYPE.MIDDLEWARE ? WEB_SERVER_MIDDLEWARE_SPAN_OP : 'router', [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ORIGIN, }, },