From 89d7648d88863dc6c278f6c4456b465b84f5c295 Mon Sep 17 00:00:00 2001 From: Francesco Gringl-Novy Date: Fri, 12 Jun 2026 11:03:58 +0200 Subject: [PATCH 1/2] ref: Update to use `@sentry/conventions` This updates all packages except node to use sentry conventions instead of opentelemetry conventions. There should be no functional change. --- packages/aws-serverless/package.json | 2 +- .../src/integration/aws/vendored/aws-sdk.ts | 11 +- .../src/integration/aws/vendored/semconv.ts | 213 ------------------ .../aws/vendored/services/bedrock-runtime.ts | 168 +++++++------- .../aws/vendored/services/dynamodb.ts | 36 +-- .../integration/aws/vendored/services/sns.ts | 5 +- .../integration/aws/vendored/services/sqs.ts | 30 +-- .../src/integration/aws/vendored/utils.ts | 7 +- .../instrumentation.ts | 19 +- .../instrumentation-aws-lambda/semconv.ts | 13 +- packages/nestjs/package.json | 2 +- packages/nestjs/rollup.npm.config.mjs | 7 + .../integrations/vendored/instrumentation.ts | 4 +- packages/nextjs/package.json | 2 +- packages/nextjs/rollup.npm.config.mjs | 6 + .../utils/dropMiddlewareTunnelRequests.ts | 5 +- .../nextjs/src/common/utils/tracingUtils.ts | 4 +- .../server/enhanceHandleRequestRootSpan.ts | 15 +- .../nextjs/src/server/handleOnSpanStart.ts | 14 +- packages/nextjs/src/server/index.ts | 17 +- packages/node-core/README.md | 6 +- packages/node-core/package.json | 6 +- packages/node-core/rollup.npm.config.mjs | 3 + .../http/httpServerSpansIntegration.ts | 33 ++- packages/opentelemetry/README.md | 1 - packages/opentelemetry/package.json | 5 +- packages/opentelemetry/src/propagator.ts | 7 +- packages/opentelemetry/src/resource.ts | 25 +- packages/opentelemetry/src/sampler.ts | 17 +- packages/opentelemetry/src/spanExporter.ts | 9 +- .../src/utils/getRequestSpanData.ts | 17 +- .../src/utils/isSentryRequest.ts | 7 +- packages/opentelemetry/src/utils/mapStatus.ts | 12 +- .../src/utils/parseSpanDescription.ts | 52 ++--- packages/opentelemetry/test/resource.test.ts | 30 +-- packages/opentelemetry/test/sampler.test.ts | 8 +- .../opentelemetry/test/spanExporter.test.ts | 4 +- packages/opentelemetry/test/trace.test.ts | 21 +- .../test/utils/getRequestSpanData.test.ts | 12 +- .../test/utils/mapStatus.test.ts | 12 +- .../test/utils/parseSpanDescription.test.ts | 211 +++++++++-------- packages/react-router/package.json | 2 +- packages/react-router/rollup.npm.config.mjs | 3 + .../src/server/createServerInstrumentation.ts | 6 +- .../src/server/instrumentation/reactRouter.ts | 5 +- .../server/integration/reactRouterServer.ts | 10 +- .../src/server/wrapSentryHandleRequest.ts | 6 +- .../src/server/wrapServerAction.ts | 5 +- .../src/server/wrapServerLoader.ts | 5 +- .../integration/reactRouterServer.test.ts | 34 +-- .../server/wrapSentryHandleRequest.test.ts | 6 +- packages/remix/package.json | 2 +- packages/remix/rollup.npm.config.mjs | 3 + packages/remix/src/vendor/instrumentation.ts | 16 +- packages/server-utils/package.json | 3 + packages/server-utils/rollup.npm.config.mjs | 1 + .../src/redis/redis-dc-subscriber.ts | 39 ++-- packages/tanstackstart-react/package.json | 2 +- .../tanstackstart-react/rollup.npm.config.mjs | 7 + .../src/server/routeParametrization.ts | 6 +- packages/typescript/tsconfig.json | 9 +- yarn.lock | 5 + 62 files changed, 510 insertions(+), 743 deletions(-) diff --git a/packages/aws-serverless/package.json b/packages/aws-serverless/package.json index 3b12bb1807ad..6183a9cc8a37 100644 --- a/packages/aws-serverless/package.json +++ b/packages/aws-serverless/package.json @@ -69,13 +69,13 @@ "@opentelemetry/api": "^1.9.1", "@opentelemetry/core": "^2.6.1", "@opentelemetry/instrumentation": "^0.214.0", - "@opentelemetry/semantic-conventions": "^1.40.0", "@sentry/core": "10.57.0", "@sentry/node": "10.57.0", "@sentry/node-core": "10.57.0", "@types/aws-lambda": "^8.10.161" }, "devDependencies": { + "@sentry/conventions": "^0.11.0", "@types/node": "^18.19.1", "@vercel/nft": "^1.3.0" }, diff --git a/packages/aws-serverless/src/integration/aws/vendored/aws-sdk.ts b/packages/aws-serverless/src/integration/aws/vendored/aws-sdk.ts index b20c1387d72a..0a4b6ee1cd0d 100644 --- a/packages/aws-serverless/src/integration/aws/vendored/aws-sdk.ts +++ b/packages/aws-serverless/src/integration/aws/vendored/aws-sdk.ts @@ -19,6 +19,7 @@ */ /* eslint-disable */ +import { HTTP_RESPONSE_STATUS_CODE, HTTP_STATUS_CODE } from '@sentry/conventions/attributes'; import { Span, SpanKind, context, trace, diag, SpanStatusCode } from '@opentelemetry/api'; import { suppressTracing } from '@opentelemetry/core'; import { AttributeNames } from './enums'; @@ -55,8 +56,6 @@ import { } from './utils'; import { propwrap } from './propwrap'; import { RequestMetadata } from './services/ServiceExtension'; -import { ATTR_HTTP_STATUS_CODE } from './semconv'; -import { ATTR_HTTP_RESPONSE_STATUS_CODE } from '@opentelemetry/semantic-conventions'; import { SDK_VERSION, timestampInSeconds } from '@sentry/core'; const PACKAGE_NAME = '@sentry/instrumentation-aws-sdk'; @@ -332,10 +331,10 @@ export class AwsInstrumentation extends InstrumentationBase { switch (response.request.commandName) { case 'SendMessage': - span.setAttribute(ATTR_MESSAGING_MESSAGE_ID, response?.data?.MessageId); + span.setAttribute(MESSAGING_MESSAGE_ID, response?.data?.MessageId); break; case 'SendMessageBatch': @@ -120,7 +120,7 @@ export class SqsServiceExtension implements ServiceExtension { case 'ReceiveMessage': { const messages: SQS.Message[] = response?.data?.Messages || []; - span.setAttribute(ATTR_MESSAGING_BATCH_MESSAGE_COUNT, messages.length); + span.setAttribute(MESSAGING_BATCH_MESSAGE_COUNT, messages.length); for (const message of messages) { const propagatedContext = propagation.extract( @@ -135,7 +135,7 @@ export class SqsServiceExtension implements ServiceExtension { span.addLink({ context: spanContext, attributes: { - [ATTR_MESSAGING_MESSAGE_ID]: message.MessageId, + [MESSAGING_MESSAGE_ID]: message.MessageId, }, }); } diff --git a/packages/aws-serverless/src/integration/aws/vendored/utils.ts b/packages/aws-serverless/src/integration/aws/vendored/utils.ts index 2ebefcbe2745..d316b852a1d3 100644 --- a/packages/aws-serverless/src/integration/aws/vendored/utils.ts +++ b/packages/aws-serverless/src/integration/aws/vendored/utils.ts @@ -19,8 +19,9 @@ */ /* eslint-disable */ +import { RPC_METHOD, RPC_SERVICE } from '@sentry/conventions/attributes'; import { Attributes, Context, context } from '@opentelemetry/api'; -import { ATTR_RPC_METHOD, ATTR_RPC_SERVICE, ATTR_RPC_SYSTEM } from './semconv'; +import { ATTR_RPC_SYSTEM } from './semconv'; import { AttributeNames } from './enums'; import { NormalizedRequest } from './types'; @@ -46,8 +47,8 @@ export const normalizeV3Request = ( export const extractAttributesFromNormalizedRequest = (normalizedRequest: NormalizedRequest): Attributes => { return { [ATTR_RPC_SYSTEM]: 'aws-api', - [ATTR_RPC_METHOD]: normalizedRequest.commandName, - [ATTR_RPC_SERVICE]: normalizedRequest.serviceName, + [RPC_METHOD]: normalizedRequest.commandName, + [RPC_SERVICE]: normalizedRequest.serviceName, [AttributeNames.CLOUD_REGION]: normalizedRequest.region, }; }; diff --git a/packages/aws-serverless/src/integration/instrumentation-aws-lambda/instrumentation.ts b/packages/aws-serverless/src/integration/instrumentation-aws-lambda/instrumentation.ts index c564a8dc583a..8241ee1f9dbc 100644 --- a/packages/aws-serverless/src/integration/instrumentation-aws-lambda/instrumentation.ts +++ b/packages/aws-serverless/src/integration/instrumentation-aws-lambda/instrumentation.ts @@ -45,17 +45,12 @@ import { isWrapped, safeExecuteInTheMiddle, } from '@opentelemetry/instrumentation'; -import { - ATTR_URL_FULL, - SEMATTRS_FAAS_EXECUTION, - SEMRESATTRS_CLOUD_ACCOUNT_ID, - SEMRESATTRS_FAAS_ID, -} from '@opentelemetry/semantic-conventions'; +import { CLOUD_ACCOUNT_ID, FAAS_COLDSTART, URL_FULL } from '@sentry/conventions/attributes'; import type { APIGatewayProxyEventHeaders, Callback, Context, Handler, StreamifyHandler } from 'aws-lambda'; import * as fs from 'fs'; import * as path from 'path'; import type { LambdaModule } from './internal-types'; -import { ATTR_FAAS_COLDSTART } from './semconv'; +import { ATTR_FAAS_EXECUTION, ATTR_FAAS_ID } from './semconv'; import type { AwsLambdaInstrumentationConfig, EventContextExtractor } from './types'; import { wrapHandler } from '../../sdk'; import { SDK_VERSION } from '@sentry/core'; @@ -338,10 +333,10 @@ export class AwsLambdaInstrumentation extends InstrumentationBase): boolean { return false; } - // eslint-disable-next-line deprecation/deprecation - const httpTarget = spanAttributes[SEMATTRS_HTTP_TARGET]; + const httpTarget = spanAttributes[HTTP_TARGET]; if (typeof httpTarget === 'string') { // Extract pathname from the target (e.g., "/tunnel?o=123&p=456" -> "/tunnel") diff --git a/packages/nextjs/src/common/utils/tracingUtils.ts b/packages/nextjs/src/common/utils/tracingUtils.ts index ce6bab7946e1..6039f0eff50b 100644 --- a/packages/nextjs/src/common/utils/tracingUtils.ts +++ b/packages/nextjs/src/common/utils/tracingUtils.ts @@ -1,4 +1,4 @@ -import { ATTR_HTTP_ROUTE } from '@opentelemetry/semantic-conventions'; +import { HTTP_ROUTE } from '@sentry/conventions/attributes'; import type { PropagationContext, Span, SpanAttributes } from '@sentry/core'; import { debug, @@ -177,7 +177,7 @@ export function maybeEnhanceServerComponentSpanName( } const segment = spanAttributes[ATTR_NEXT_SEGMENT] as string; - const route = rootSpanAttributes[ATTR_HTTP_ROUTE]; + const route = rootSpanAttributes[HTTP_ROUTE]; const enhancedName = getEnhancedResolveSegmentSpanName({ segment, route: typeof route === 'string' ? route : '' }); activeSpan.updateName(enhancedName); activeSpan.setAttributes({ diff --git a/packages/nextjs/src/server/enhanceHandleRequestRootSpan.ts b/packages/nextjs/src/server/enhanceHandleRequestRootSpan.ts index a934380492dc..11af0ee73a61 100644 --- a/packages/nextjs/src/server/enhanceHandleRequestRootSpan.ts +++ b/packages/nextjs/src/server/enhanceHandleRequestRootSpan.ts @@ -1,9 +1,4 @@ -import { - ATTR_HTTP_REQUEST_METHOD, - ATTR_HTTP_ROUTE, - SEMATTRS_HTTP_METHOD, - SEMATTRS_HTTP_TARGET, -} from '@opentelemetry/semantic-conventions'; +import { HTTP_METHOD, HTTP_REQUEST_METHOD, HTTP_ROUTE, HTTP_TARGET } from '@sentry/conventions/attributes'; import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, stripUrlQueryAndFragment } from '@sentry/core'; import { ATTR_NEXT_ROUTE, ATTR_NEXT_SPAN_NAME, ATTR_NEXT_SPAN_TYPE } from '../common/nextSpanAttributes'; import { TRANSACTION_ATTR_SENTRY_ROUTE_BACKFILL } from '../common/span-attributes-with-logic-attached'; @@ -41,11 +36,9 @@ export function enhanceHandleRequestRootSpan(span: MutableRootSpan): void { span.setName(stripUrlQueryAndFragment(currentName)); } - // eslint-disable-next-line deprecation/deprecation - const method = attributes[SEMATTRS_HTTP_METHOD] ?? attributes[ATTR_HTTP_REQUEST_METHOD]; - // eslint-disable-next-line deprecation/deprecation - const target = attributes[SEMATTRS_HTTP_TARGET]; - const route = attributes[ATTR_HTTP_ROUTE] || attributes[ATTR_NEXT_ROUTE]; + const method = attributes[HTTP_METHOD] ?? attributes[HTTP_REQUEST_METHOD]; + const target = attributes[HTTP_TARGET]; + const route = attributes[HTTP_ROUTE] || attributes[ATTR_NEXT_ROUTE]; const spanName = attributes[ATTR_NEXT_SPAN_NAME]; if (typeof method === 'string' && typeof route === 'string' && !route.startsWith('middleware')) { diff --git a/packages/nextjs/src/server/handleOnSpanStart.ts b/packages/nextjs/src/server/handleOnSpanStart.ts index 21af973f2b2f..b852ed729e04 100644 --- a/packages/nextjs/src/server/handleOnSpanStart.ts +++ b/packages/nextjs/src/server/handleOnSpanStart.ts @@ -1,5 +1,5 @@ import { context } from '@opentelemetry/api'; -import { ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_ROUTE, SEMATTRS_HTTP_METHOD } from '@opentelemetry/semantic-conventions'; +import { HTTP_METHOD, HTTP_REQUEST_METHOD, HTTP_ROUTE } from '@sentry/conventions/attributes'; import type { Span } from '@sentry/core'; import { getCapturedScopesOnSpan, @@ -37,20 +37,18 @@ export function handleOnSpanStart(span: Span): void { if (typeof spanAttributes?.[ATTR_NEXT_ROUTE] === 'string') { // Only hoist the http.route attribute if the transaction doesn't already have it if ( - // eslint-disable-next-line deprecation/deprecation - (rootSpanAttributes?.[ATTR_HTTP_REQUEST_METHOD] || rootSpanAttributes?.[SEMATTRS_HTTP_METHOD]) && - !rootSpanAttributes?.[ATTR_HTTP_ROUTE] + (rootSpanAttributes?.[HTTP_REQUEST_METHOD] || rootSpanAttributes?.[HTTP_METHOD]) && + !rootSpanAttributes?.[HTTP_ROUTE] ) { const route = spanAttributes[ATTR_NEXT_ROUTE].replace(/\/route$/, ''); rootSpan.updateName(route); - rootSpan.setAttribute(ATTR_HTTP_ROUTE, route); + rootSpan.setAttribute(HTTP_ROUTE, route); // Preserving the original attribute despite internally not depending on it rootSpan.setAttribute(ATTR_NEXT_ROUTE, route); // Update the isolation scope's transaction name so that non-transaction events // (e.g. captureMessage, captureException) also get the parameterized route. - // eslint-disable-next-line deprecation/deprecation - const method = rootSpanAttributes?.[ATTR_HTTP_REQUEST_METHOD] || rootSpanAttributes?.[SEMATTRS_HTTP_METHOD]; + const method = rootSpanAttributes?.[HTTP_REQUEST_METHOD] || rootSpanAttributes?.[HTTP_METHOD]; if (typeof method === 'string') { getIsolationScope().setTransactionName(`${method} ${route}`); } @@ -67,7 +65,7 @@ export function handleOnSpanStart(span: Span): void { const middlewareName = spanAttributes[ATTR_NEXT_SPAN_NAME]; if (typeof middlewareName === 'string') { rootSpan.updateName(middlewareName); - rootSpan.setAttribute(ATTR_HTTP_ROUTE, middlewareName); + rootSpan.setAttribute(HTTP_ROUTE, middlewareName); rootSpan.setAttribute(ATTR_NEXT_SPAN_NAME, middlewareName); } span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, 'auto'); diff --git a/packages/nextjs/src/server/index.ts b/packages/nextjs/src/server/index.ts index b22ba38ecd83..cf5de6760a47 100644 --- a/packages/nextjs/src/server/index.ts +++ b/packages/nextjs/src/server/index.ts @@ -1,7 +1,7 @@ // import/export got a false positive, and affects most of our index barrel files // can be removed once following issue is fixed: https://github.com/import-js/eslint-plugin-import/issues/703 /* eslint-disable import/export */ -import { ATTR_URL_QUERY, SEMATTRS_HTTP_TARGET } from '@opentelemetry/semantic-conventions'; +import { HTTP_TARGET, URL_QUERY } from '@sentry/conventions/attributes'; import type { EventProcessor } from '@sentry/core'; import { applySdkMetadata, @@ -185,15 +185,12 @@ export function init(options: NodeOptions): NodeClient | undefined { // because we didn't get the chance to do `suppressTracing`, since this happens outside of userland. // We need to drop these spans. if ( - // eslint-disable-next-line deprecation/deprecation - (typeof spanAttributes[SEMATTRS_HTTP_TARGET] === 'string' && - // eslint-disable-next-line deprecation/deprecation - spanAttributes[SEMATTRS_HTTP_TARGET].includes('sentry_key') && - // eslint-disable-next-line deprecation/deprecation - spanAttributes[SEMATTRS_HTTP_TARGET].includes('sentry_client')) || - (typeof spanAttributes[ATTR_URL_QUERY] === 'string' && - spanAttributes[ATTR_URL_QUERY].includes('sentry_key') && - spanAttributes[ATTR_URL_QUERY].includes('sentry_client')) + (typeof spanAttributes[HTTP_TARGET] === 'string' && + spanAttributes[HTTP_TARGET].includes('sentry_key') && + spanAttributes[HTTP_TARGET].includes('sentry_client')) || + (typeof spanAttributes[URL_QUERY] === 'string' && + spanAttributes[URL_QUERY].includes('sentry_key') && + spanAttributes[URL_QUERY].includes('sentry_client')) ) { samplingDecision.decision = false; } diff --git a/packages/node-core/README.md b/packages/node-core/README.md index c3ccc6df1b3d..2cf02ba553c1 100644 --- a/packages/node-core/README.md +++ b/packages/node-core/README.md @@ -15,17 +15,15 @@ Unlike the `@sentry/node` SDK, this SDK comes with no OpenTelemetry auto-instrum - `@opentelemetry/api` - `@opentelemetry/core` - `@opentelemetry/instrumentation` -- `@opentelemetry/resources` - `@opentelemetry/sdk-trace-base` -- `@opentelemetry/semantic-conventions`. ## Installation ```bash -npm install @sentry/node-core @sentry/opentelemetry @opentelemetry/api @opentelemetry/core @opentelemetry/instrumentation @opentelemetry/resources @opentelemetry/sdk-trace-base @opentelemetry/semantic-conventions +npm install @sentry/node-core @sentry/opentelemetry @opentelemetry/api @opentelemetry/core @opentelemetry/instrumentation @opentelemetry/sdk-trace-base # Or yarn -yarn add @sentry/node-core @sentry/opentelemetry @opentelemetry/api @opentelemetry/core @opentelemetry/instrumentation @opentelemetry/resources @opentelemetry/sdk-trace-base @opentelemetry/semantic-conventions +yarn add @sentry/node-core @sentry/opentelemetry @opentelemetry/api @opentelemetry/core @opentelemetry/instrumentation @opentelemetry/sdk-trace-base ``` ## Usage diff --git a/packages/node-core/package.json b/packages/node-core/package.json index 704e31523540..ad04c86b5515 100644 --- a/packages/node-core/package.json +++ b/packages/node-core/package.json @@ -81,7 +81,6 @@ "@opentelemetry/core": "^1.30.1 || ^2.1.0", "@opentelemetry/instrumentation": ">=0.57.1 <1", "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0", - "@opentelemetry/semantic-conventions": "^1.39.0", "@opentelemetry/exporter-trace-otlp-http": ">=0.57.0 <1" }, "peerDependenciesMeta": { @@ -97,9 +96,6 @@ "@opentelemetry/sdk-trace-base": { "optional": true }, - "@opentelemetry/semantic-conventions": { - "optional": true - }, "@opentelemetry/exporter-trace-otlp-http": { "optional": true } @@ -115,7 +111,7 @@ "@opentelemetry/exporter-trace-otlp-http": "^0.214.0", "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/sdk-trace-base": "^2.6.1", - "@opentelemetry/semantic-conventions": "^1.40.0", + "@sentry/conventions": "^0.11.0", "@types/node": "^18.19.1" }, "scripts": { diff --git a/packages/node-core/rollup.npm.config.mjs b/packages/node-core/rollup.npm.config.mjs index 9fa0a1fb19b9..444d57087420 100644 --- a/packages/node-core/rollup.npm.config.mjs +++ b/packages/node-core/rollup.npm.config.mjs @@ -25,6 +25,9 @@ export default [ // set exports to 'named' or 'auto' so that rollup doesn't warn exports: 'named', preserveModules: true, + // keep emitted module paths relative to `src` so the bundled `@sentry/conventions` + // (a devDependency, vendored into the build) doesn't shift the output layout + preserveModulesRoot: 'src', }, plugins: [ replace({ diff --git a/packages/node-core/src/integrations/http/httpServerSpansIntegration.ts b/packages/node-core/src/integrations/http/httpServerSpansIntegration.ts index 48a6bb08897e..83218ca5b98c 100644 --- a/packages/node-core/src/integrations/http/httpServerSpansIntegration.ts +++ b/packages/node-core/src/integrations/http/httpServerSpansIntegration.ts @@ -4,13 +4,13 @@ import { context, SpanKind, trace } from '@opentelemetry/api'; import type { RPCMetadata } from '@opentelemetry/core'; import { getRPCMetadata, isTracingSuppressed, RPCType, setRPCMetadata } from '@opentelemetry/core'; import { - ATTR_HTTP_RESPONSE_STATUS_CODE, - ATTR_HTTP_ROUTE, - SEMATTRS_HTTP_STATUS_CODE, - SEMATTRS_NET_HOST_IP, - SEMATTRS_NET_HOST_PORT, - SEMATTRS_NET_PEER_IP, -} from '@opentelemetry/semantic-conventions'; + HTTP_RESPONSE_STATUS_CODE, + HTTP_ROUTE, + HTTP_STATUS_CODE, + NET_HOST_IP, + NET_HOST_PORT, + NET_PEER_IP, +} from '@sentry/conventions/attributes'; import type { Event, HttpClientRequest, @@ -375,30 +375,25 @@ function getIncomingRequestAttributesOnResponse( const { statusCode, statusMessage } = response; const newAttributes: SpanAttributes = { - [ATTR_HTTP_RESPONSE_STATUS_CODE]: statusCode, - // eslint-disable-next-line deprecation/deprecation - [SEMATTRS_HTTP_STATUS_CODE]: statusCode, + [HTTP_RESPONSE_STATUS_CODE]: statusCode, + [HTTP_STATUS_CODE]: statusCode, 'http.status_text': statusMessage?.toUpperCase(), }; const rpcMetadata = getRPCMetadata(context.active()); if (socket) { const { localAddress, localPort, remoteAddress, remotePort } = socket; - // eslint-disable-next-line deprecation/deprecation - newAttributes[SEMATTRS_NET_HOST_IP] = localAddress; - // eslint-disable-next-line deprecation/deprecation - newAttributes[SEMATTRS_NET_HOST_PORT] = localPort; - // eslint-disable-next-line deprecation/deprecation - newAttributes[SEMATTRS_NET_PEER_IP] = remoteAddress; + newAttributes[NET_HOST_IP] = localAddress; + newAttributes[NET_HOST_PORT] = localPort; + newAttributes[NET_PEER_IP] = remoteAddress; newAttributes['net.peer.port'] = remotePort; } - // eslint-disable-next-line deprecation/deprecation - newAttributes[SEMATTRS_HTTP_STATUS_CODE] = statusCode; + newAttributes[HTTP_STATUS_CODE] = statusCode; newAttributes['http.status_text'] = (statusMessage || '').toUpperCase(); if (rpcMetadata?.type === RPCType.HTTP && rpcMetadata.route !== undefined) { const routeName = rpcMetadata.route; - newAttributes[ATTR_HTTP_ROUTE] = routeName; + newAttributes[HTTP_ROUTE] = routeName; } return newAttributes; diff --git a/packages/opentelemetry/README.md b/packages/opentelemetry/README.md index dd20135c268b..18f2589a8701 100644 --- a/packages/opentelemetry/README.md +++ b/packages/opentelemetry/README.md @@ -28,7 +28,6 @@ Note that `@sentry/opentelemetry` depends on the following peer dependencies: - `@opentelemetry/api` version `1.0.0` or greater - `@opentelemetry/core` version `1.0.0` or greater -- `@opentelemetry/semantic-conventions` version `1.0.0` or greater - `@opentelemetry/sdk-trace-base` version `1.0.0` or greater, or a package that implements that, like `@opentelemetry/sdk-node`. diff --git a/packages/opentelemetry/package.json b/packages/opentelemetry/package.json index 7093c1f31db2..724433e6cdfd 100644 --- a/packages/opentelemetry/package.json +++ b/packages/opentelemetry/package.json @@ -70,14 +70,13 @@ "peerDependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/core": "^1.30.1 || ^2.1.0", - "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0", - "@opentelemetry/semantic-conventions": "^1.39.0" + "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0" }, "devDependencies": { "@opentelemetry/api": "^1.9.1", "@opentelemetry/core": "^2.6.1", "@opentelemetry/sdk-trace-base": "^2.6.1", - "@opentelemetry/semantic-conventions": "^1.40.0" + "@sentry/conventions": "^0.11.0" }, "scripts": { "build": "run-p build:transpile build:types", diff --git a/packages/opentelemetry/src/propagator.ts b/packages/opentelemetry/src/propagator.ts index b8582b37d964..5e3a37f981da 100644 --- a/packages/opentelemetry/src/propagator.ts +++ b/packages/opentelemetry/src/propagator.ts @@ -1,7 +1,7 @@ import type { Baggage, Context, Span, SpanContext, TextMapGetter, TextMapSetter } from '@opentelemetry/api'; import { context, INVALID_TRACEID, propagation, trace, TraceFlags } from '@opentelemetry/api'; import { isTracingSuppressed, W3CBaggagePropagator } from '@opentelemetry/core'; -import { ATTR_URL_FULL, SEMATTRS_HTTP_URL } from '@opentelemetry/semantic-conventions'; +import { HTTP_URL, URL_FULL } from '@sentry/conventions/attributes'; import type { Client, continueTrace, DynamicSamplingContext, Scope } from '@sentry/core'; import { baggageHeaderToDynamicSamplingContext, @@ -275,9 +275,8 @@ function getExistingSentryTrace(carrier: unknown): string | string[] | undefined */ function getCurrentURL(span: Span): string | undefined { const spanData = spanToJSON(span).data; - // `ATTR_URL_FULL` is the new attribute, but we still support the old one, `SEMATTRS_HTTP_URL`, for now. - // eslint-disable-next-line deprecation/deprecation - const urlAttribute = spanData[SEMATTRS_HTTP_URL] || spanData[ATTR_URL_FULL]; + // `URL_FULL` is the new attribute, but we still support the old one, `HTTP_URL`, for now. + const urlAttribute = spanData[HTTP_URL] || spanData[URL_FULL]; if (typeof urlAttribute === 'string') { return urlAttribute; } diff --git a/packages/opentelemetry/src/resource.ts b/packages/opentelemetry/src/resource.ts index 89b84927ee65..75ec3ac742d2 100644 --- a/packages/opentelemetry/src/resource.ts +++ b/packages/opentelemetry/src/resource.ts @@ -1,15 +1,17 @@ import type { Attributes, AttributeValue } from '@opentelemetry/api'; import { SDK_INFO } from '@opentelemetry/core'; -import { - ATTR_SERVICE_NAME, - ATTR_SERVICE_VERSION, - ATTR_TELEMETRY_SDK_LANGUAGE, - ATTR_TELEMETRY_SDK_NAME, - ATTR_TELEMETRY_SDK_VERSION, - SEMRESATTRS_SERVICE_NAMESPACE, -} from '@opentelemetry/semantic-conventions'; +import { SERVICE_NAME, SERVICE_VERSION } from '@sentry/conventions/attributes'; import { SDK_VERSION } from '@sentry/core'; +// These resource attributes are not (yet) part of `@sentry/conventions`, so we inline the +// stable OTel attribute keys here as plain strings rather than depending on +// `@opentelemetry/semantic-conventions`. The string values must match exactly, as +// `SDK_INFO` (from `@opentelemetry/core`) is keyed by them. +const ATTR_TELEMETRY_SDK_LANGUAGE = 'telemetry.sdk.language'; +const ATTR_TELEMETRY_SDK_NAME = 'telemetry.sdk.name'; +const ATTR_TELEMETRY_SDK_VERSION = 'telemetry.sdk.version'; +const SEMRESATTRS_SERVICE_NAMESPACE = 'service.namespace'; + type RawResourceAttribute = [string, AttributeValue | undefined]; /** @@ -83,15 +85,14 @@ export function getSentryResource(serviceNameFallback: string): SentryResource { return new SentryResource({ // Lowest priority: Sentry defaults - // eslint-disable-next-line deprecation/deprecation [SEMRESATTRS_SERVICE_NAMESPACE]: 'sentry', - [ATTR_SERVICE_NAME]: serviceNameFallback, + [SERVICE_NAME]: serviceNameFallback, // OTEL_RESOURCE_ATTRIBUTES overrides defaults (including service.name and service.namespace) ...otelResourceAttrs, // OTEL_SERVICE_NAME explicitly overrides service.name - ...(otelServiceName ? { [ATTR_SERVICE_NAME]: otelServiceName } : {}), + ...(otelServiceName ? { [SERVICE_NAME]: otelServiceName } : {}), // Highest priority: Sentry SDK telemetry attrs (cannot be overridden by env vars) - [ATTR_SERVICE_VERSION]: SDK_VERSION, + [SERVICE_VERSION]: SDK_VERSION, [ATTR_TELEMETRY_SDK_LANGUAGE]: SDK_INFO[ATTR_TELEMETRY_SDK_LANGUAGE], [ATTR_TELEMETRY_SDK_NAME]: SDK_INFO[ATTR_TELEMETRY_SDK_NAME], [ATTR_TELEMETRY_SDK_VERSION]: SDK_INFO[ATTR_TELEMETRY_SDK_VERSION], diff --git a/packages/opentelemetry/src/sampler.ts b/packages/opentelemetry/src/sampler.ts index be873aca3d51..7da742cda889 100644 --- a/packages/opentelemetry/src/sampler.ts +++ b/packages/opentelemetry/src/sampler.ts @@ -4,12 +4,7 @@ import { isSpanContextValid, SpanKind, trace } from '@opentelemetry/api'; import { TraceState } from './utils/TraceState'; import type { Sampler, SamplingResult } from '@opentelemetry/sdk-trace-base'; import { SamplingDecision } from '@opentelemetry/sdk-trace-base'; -import { - ATTR_HTTP_REQUEST_METHOD, - ATTR_URL_FULL, - SEMATTRS_HTTP_METHOD, - SEMATTRS_HTTP_URL, -} from '@opentelemetry/semantic-conventions'; +import { HTTP_METHOD, HTTP_REQUEST_METHOD, HTTP_URL, URL_FULL } from '@sentry/conventions/attributes'; import type { Client, SpanAttributes } from '@sentry/core'; import { _INTERNAL_safeMathRandom, @@ -70,9 +65,8 @@ export class SentrySampler implements Sampler { return wrapSamplingDecision({ decision: undefined, context, spanAttributes }); } - // `ATTR_HTTP_REQUEST_METHOD` is the new attribute, but we still support the old one, `SEMATTRS_HTTP_METHOD`, for now. - // eslint-disable-next-line deprecation/deprecation - const maybeSpanHttpMethod = spanAttributes[SEMATTRS_HTTP_METHOD] || spanAttributes[ATTR_HTTP_REQUEST_METHOD]; + // `HTTP_REQUEST_METHOD` is the new attribute, but we still support the old one, `HTTP_METHOD`, for now. + const maybeSpanHttpMethod = spanAttributes[HTTP_METHOD] || spanAttributes[HTTP_REQUEST_METHOD]; // If we have a http.client span that has no local parent, we never want to sample it // but we want to leave downstream sampling decisions up to the server. @@ -331,9 +325,8 @@ function getBaseTraceState(context: Context, spanAttributes: SpanAttributes): Tr let traceState = parentContext?.traceState || new TraceState(); // We always keep the URL on the trace state, so we can access it in the propagator - // `ATTR_URL_FULL` is the new attribute, but we still support the old one, `ATTR_HTTP_URL`, for now. - // eslint-disable-next-line deprecation/deprecation - const url = spanAttributes[SEMATTRS_HTTP_URL] || spanAttributes[ATTR_URL_FULL]; + // `URL_FULL` is the new attribute, but we still support the old one, `HTTP_URL`, for now. + const url = spanAttributes[HTTP_URL] || spanAttributes[URL_FULL]; if (url && typeof url === 'string') { traceState = traceState.set(SENTRY_TRACE_STATE_URL, url); } diff --git a/packages/opentelemetry/src/spanExporter.ts b/packages/opentelemetry/src/spanExporter.ts index b050c92a583d..72e1a2df06fa 100644 --- a/packages/opentelemetry/src/spanExporter.ts +++ b/packages/opentelemetry/src/spanExporter.ts @@ -2,7 +2,7 @@ import type { Span } from '@opentelemetry/api'; import { SpanKind } from '@opentelemetry/api'; import type { ReadableSpan } from '@opentelemetry/sdk-trace-base'; -import { ATTR_HTTP_RESPONSE_STATUS_CODE, SEMATTRS_HTTP_STATUS_CODE } from '@opentelemetry/semantic-conventions'; +import { HTTP_RESPONSE_STATUS_CODE, HTTP_STATUS_CODE } from '@sentry/conventions/attributes'; import type { SpanAttributes, SpanJSON, @@ -295,7 +295,7 @@ export function createTransactionForOtelSpan(span: ReadableSpan): TransactionEve links: convertSpanLinksForEnvelope(links), }; - const statusCode = attributes[ATTR_HTTP_RESPONSE_STATUS_CODE]; + const statusCode = attributes[HTTP_RESPONSE_STATUS_CODE]; const responseContext = typeof statusCode === 'number' ? { response: { status_code: statusCode } } : undefined; const transactionEvent: TransactionEvent = { @@ -441,10 +441,9 @@ function getData(span: ReadableSpan): Record { data['otel.kind'] = SpanKind[span.kind]; } - // eslint-disable-next-line deprecation/deprecation - const maybeHttpStatusCodeAttribute = attributes[SEMATTRS_HTTP_STATUS_CODE]; + const maybeHttpStatusCodeAttribute = attributes[HTTP_STATUS_CODE]; if (maybeHttpStatusCodeAttribute) { - data[ATTR_HTTP_RESPONSE_STATUS_CODE] = maybeHttpStatusCodeAttribute as string; + data[HTTP_RESPONSE_STATUS_CODE] = maybeHttpStatusCodeAttribute as string; } const requestData = getRequestSpanData(span); diff --git a/packages/opentelemetry/src/utils/getRequestSpanData.ts b/packages/opentelemetry/src/utils/getRequestSpanData.ts index 083434e07559..a0cc7456a119 100644 --- a/packages/opentelemetry/src/utils/getRequestSpanData.ts +++ b/packages/opentelemetry/src/utils/getRequestSpanData.ts @@ -1,11 +1,6 @@ import type { Span } from '@opentelemetry/api'; import type { ReadableSpan } from '@opentelemetry/sdk-trace-base'; -import { - ATTR_HTTP_REQUEST_METHOD, - ATTR_URL_FULL, - SEMATTRS_HTTP_METHOD, - SEMATTRS_HTTP_URL, -} from '@opentelemetry/semantic-conventions'; +import { HTTP_METHOD, HTTP_REQUEST_METHOD, HTTP_URL, URL_FULL } from '@sentry/conventions/attributes'; import type { SanitizedRequestData } from '@sentry/core'; import { getSanitizedUrlString, parseUrl } from '@sentry/core'; import { spanHasAttributes } from './spanTypes'; @@ -19,17 +14,11 @@ export function getRequestSpanData(span: Span | ReadableSpan): Partial = { url: maybeUrlAttribute, - // eslint-disable-next-line deprecation/deprecation - 'http.method': (span.attributes[ATTR_HTTP_REQUEST_METHOD] || span.attributes[SEMATTRS_HTTP_METHOD]) as - | string - | undefined, + 'http.method': (span.attributes[HTTP_REQUEST_METHOD] || span.attributes[HTTP_METHOD]) as string | undefined, }; // Default to GET if URL is set but method is not diff --git a/packages/opentelemetry/src/utils/isSentryRequest.ts b/packages/opentelemetry/src/utils/isSentryRequest.ts index d6b59880137b..79d5728d09d5 100644 --- a/packages/opentelemetry/src/utils/isSentryRequest.ts +++ b/packages/opentelemetry/src/utils/isSentryRequest.ts @@ -1,4 +1,4 @@ -import { ATTR_URL_FULL, SEMATTRS_HTTP_URL } from '@opentelemetry/semantic-conventions'; +import { HTTP_URL, URL_FULL } from '@sentry/conventions/attributes'; import { getClient, isSentryRequestUrl } from '@sentry/core'; import type { AbstractSpan } from '../types'; import { spanHasAttributes } from './spanTypes'; @@ -15,9 +15,8 @@ export function isSentryRequestSpan(span: AbstractSpan): boolean { const { attributes } = span; - // `ATTR_URL_FULL` is the new attribute, but we still support the old one, `ATTR_HTTP_URL`, for now. - // eslint-disable-next-line deprecation/deprecation - const httpUrl = attributes[SEMATTRS_HTTP_URL] || attributes[ATTR_URL_FULL]; + // `URL_FULL` is the new attribute, but we still support the old one, `HTTP_URL`, for now. + const httpUrl = attributes[HTTP_URL] || attributes[URL_FULL]; if (!httpUrl) { return false; diff --git a/packages/opentelemetry/src/utils/mapStatus.ts b/packages/opentelemetry/src/utils/mapStatus.ts index b12abbb4a17f..f9bcfe474760 100644 --- a/packages/opentelemetry/src/utils/mapStatus.ts +++ b/packages/opentelemetry/src/utils/mapStatus.ts @@ -1,9 +1,5 @@ import { SpanStatusCode } from '@opentelemetry/api'; -import { - ATTR_HTTP_RESPONSE_STATUS_CODE, - SEMATTRS_HTTP_STATUS_CODE, - SEMATTRS_RPC_GRPC_STATUS_CODE, -} from '@opentelemetry/semantic-conventions'; +import { HTTP_RESPONSE_STATUS_CODE, HTTP_STATUS_CODE, RPC_GRPC_STATUS_CODE } from '@sentry/conventions/attributes'; import type { SpanAttributes, SpanStatus } from '@sentry/core'; import { getSpanStatusFromHttpCode, SPAN_STATUS_ERROR, SPAN_STATUS_OK } from '@sentry/core'; import type { AbstractSpan } from '../types'; @@ -79,10 +75,8 @@ export function mapStatus(span: AbstractSpan): SpanStatus { function inferStatusFromAttributes(attributes: SpanAttributes): SpanStatus | undefined { // If the span status is UNSET, we try to infer it from HTTP or GRPC status codes. - // eslint-disable-next-line deprecation/deprecation - const httpCodeAttribute = attributes[ATTR_HTTP_RESPONSE_STATUS_CODE] || attributes[SEMATTRS_HTTP_STATUS_CODE]; - // eslint-disable-next-line deprecation/deprecation - const grpcCodeAttribute = attributes[SEMATTRS_RPC_GRPC_STATUS_CODE]; + const httpCodeAttribute = attributes[HTTP_RESPONSE_STATUS_CODE] || attributes[HTTP_STATUS_CODE]; + const grpcCodeAttribute = attributes[RPC_GRPC_STATUS_CODE]; const numberHttpCode = typeof httpCodeAttribute === 'number' diff --git a/packages/opentelemetry/src/utils/parseSpanDescription.ts b/packages/opentelemetry/src/utils/parseSpanDescription.ts index fc0f92143516..d3176d7e3850 100644 --- a/packages/opentelemetry/src/utils/parseSpanDescription.ts +++ b/packages/opentelemetry/src/utils/parseSpanDescription.ts @@ -1,19 +1,19 @@ import type { Attributes, AttributeValue } from '@opentelemetry/api'; import { SpanKind } from '@opentelemetry/api'; import { - ATTR_DB_SYSTEM_NAME, - ATTR_HTTP_REQUEST_METHOD, - ATTR_HTTP_ROUTE, - ATTR_URL_FULL, - SEMATTRS_DB_STATEMENT, - SEMATTRS_DB_SYSTEM, - SEMATTRS_FAAS_TRIGGER, - SEMATTRS_HTTP_METHOD, - SEMATTRS_HTTP_TARGET, - SEMATTRS_HTTP_URL, - SEMATTRS_MESSAGING_SYSTEM, - SEMATTRS_RPC_SERVICE, -} from '@opentelemetry/semantic-conventions'; + DB_STATEMENT, + DB_SYSTEM, + DB_SYSTEM_NAME, + FAAS_TRIGGER, + HTTP_METHOD, + HTTP_REQUEST_METHOD, + HTTP_ROUTE, + HTTP_TARGET, + HTTP_URL, + MESSAGING_SYSTEM, + RPC_SERVICE, + URL_FULL, +} from '@sentry/conventions/attributes'; import type { SpanAttributes, TransactionSource } from '@sentry/core'; import { getSanitizedUrlString, @@ -41,14 +41,12 @@ interface SpanDescription { */ export function inferSpanData(spanName: string, attributes: SpanAttributes, kind: SpanKind): SpanDescription { // if http.method exists, this is an http request span - // eslint-disable-next-line deprecation/deprecation - const httpMethod = attributes[ATTR_HTTP_REQUEST_METHOD] || attributes[SEMATTRS_HTTP_METHOD]; + const httpMethod = attributes[HTTP_REQUEST_METHOD] || attributes[HTTP_METHOD]; if (httpMethod) { return descriptionForHttpMethod({ attributes, name: spanName, kind }, httpMethod); } - // eslint-disable-next-line deprecation/deprecation - const dbSystem = attributes[ATTR_DB_SYSTEM_NAME] || attributes[SEMATTRS_DB_SYSTEM]; + const dbSystem = attributes[DB_SYSTEM_NAME] || attributes[DB_SYSTEM]; const opIsCache = typeof attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] === 'string' && attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP].startsWith('cache.'); @@ -62,8 +60,7 @@ export function inferSpanData(spanName: string, attributes: SpanAttributes, kind const customSourceOrRoute = attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] === 'custom' ? 'custom' : 'route'; // If rpc.service exists then this is a rpc call span. - // eslint-disable-next-line deprecation/deprecation - const rpcService = attributes[SEMATTRS_RPC_SERVICE]; + const rpcService = attributes[RPC_SERVICE]; if (rpcService) { return { ...getUserUpdatedNameAndSource(spanName, attributes, 'route'), @@ -72,8 +69,7 @@ export function inferSpanData(spanName: string, attributes: SpanAttributes, kind } // If messaging.system exists then this is a messaging system span. - // eslint-disable-next-line deprecation/deprecation - const messagingSystem = attributes[SEMATTRS_MESSAGING_SYSTEM]; + const messagingSystem = attributes[MESSAGING_SYSTEM]; if (messagingSystem) { return { ...getUserUpdatedNameAndSource(spanName, attributes, customSourceOrRoute), @@ -82,8 +78,7 @@ export function inferSpanData(spanName: string, attributes: SpanAttributes, kind } // If faas.trigger exists then this is a function as a service span. - // eslint-disable-next-line deprecation/deprecation - const faasTrigger = attributes[SEMATTRS_FAAS_TRIGGER]; + const faasTrigger = attributes[FAAS_TRIGGER]; if (faasTrigger) { return { ...getUserUpdatedNameAndSource(spanName, attributes, customSourceOrRoute), @@ -128,8 +123,7 @@ function descriptionForDbSystem({ attributes, name }: { attributes: Attributes; } // Use DB statement (Ex "SELECT * FROM table") if possible as description. - // eslint-disable-next-line deprecation/deprecation - const statement = attributes[SEMATTRS_DB_STATEMENT]; + const statement = attributes[DB_STATEMENT]; const description = statement ? statement.toString() : name; @@ -247,13 +241,11 @@ export function getSanitizedUrl( hasRoute: boolean; } { // This is the relative path of the URL, e.g. /sub - // eslint-disable-next-line deprecation/deprecation - const httpTarget = attributes[SEMATTRS_HTTP_TARGET]; + const httpTarget = attributes[HTTP_TARGET]; // This is the full URL, including host & query params etc., e.g. https://example.com/sub?foo=bar - // eslint-disable-next-line deprecation/deprecation - const httpUrl = attributes[SEMATTRS_HTTP_URL] || attributes[ATTR_URL_FULL]; + const httpUrl = attributes[HTTP_URL] || attributes[URL_FULL]; // This is the normalized route name - may not always be available! - const httpRoute = attributes[ATTR_HTTP_ROUTE]; + const httpRoute = attributes[HTTP_ROUTE]; const parsedUrl = typeof httpUrl === 'string' ? parseUrl(httpUrl) : undefined; const url = parsedUrl ? getSanitizedUrlString(parsedUrl) : undefined; diff --git a/packages/opentelemetry/test/resource.test.ts b/packages/opentelemetry/test/resource.test.ts index 1a6ebedf34d4..43dcb3b00f4e 100644 --- a/packages/opentelemetry/test/resource.test.ts +++ b/packages/opentelemetry/test/resource.test.ts @@ -1,16 +1,16 @@ -import { - ATTR_SERVICE_NAME, - ATTR_SERVICE_VERSION, - ATTR_TELEMETRY_SDK_LANGUAGE, - ATTR_TELEMETRY_SDK_NAME, - ATTR_TELEMETRY_SDK_VERSION, - SEMRESATTRS_SERVICE_NAMESPACE, -} from '@opentelemetry/semantic-conventions'; +import { SERVICE_NAME, SERVICE_VERSION } from '@sentry/conventions/attributes'; import { SDK_VERSION } from '@sentry/core'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { getSentryResource } from '../src/resource'; import { SDK_INFO } from '@opentelemetry/core'; +// These resource attributes are not (yet) part of `@sentry/conventions`, so we inline the +// stable OTel attribute keys here as plain strings (mirroring `src/resource.ts`). +const ATTR_TELEMETRY_SDK_LANGUAGE = 'telemetry.sdk.language'; +const ATTR_TELEMETRY_SDK_NAME = 'telemetry.sdk.name'; +const ATTR_TELEMETRY_SDK_VERSION = 'telemetry.sdk.version'; +const SEMRESATTRS_SERVICE_NAMESPACE = 'service.namespace'; + describe('getSentryResource', () => { const originalEnv = process.env; @@ -28,19 +28,19 @@ describe('getSentryResource', () => { it('uses serviceNameFallback when no env vars are set', () => { const resource = getSentryResource('node'); - expect(resource.attributes[ATTR_SERVICE_NAME]).toBe('node'); + expect(resource.attributes[SERVICE_NAME]).toBe('node'); }); it('uses OTEL_SERVICE_NAME over the fallback', () => { process.env['OTEL_SERVICE_NAME'] = 'my-service'; const resource = getSentryResource('node'); - expect(resource.attributes[ATTR_SERVICE_NAME]).toBe('my-service'); + expect(resource.attributes[SERVICE_NAME]).toBe('my-service'); }); it('ignores empty OTEL_SERVICE_NAME and falls back to serviceNameFallback', () => { process.env['OTEL_SERVICE_NAME'] = ''; const resource = getSentryResource('node'); - expect(resource.attributes[ATTR_SERVICE_NAME]).toBe('node'); + expect(resource.attributes[SERVICE_NAME]).toBe('node'); }); it('includes OTEL_RESOURCE_ATTRIBUTES key=value pairs', () => { @@ -53,14 +53,14 @@ describe('getSentryResource', () => { it('OTEL_RESOURCE_ATTRIBUTES can override service.name (but OTEL_SERVICE_NAME takes precedence over it)', () => { process.env['OTEL_RESOURCE_ATTRIBUTES'] = 'service.name=from-attrs'; const resource = getSentryResource('node'); - expect(resource.attributes[ATTR_SERVICE_NAME]).toBe('from-attrs'); + expect(resource.attributes[SERVICE_NAME]).toBe('from-attrs'); }); it('OTEL_SERVICE_NAME takes precedence over service.name from OTEL_RESOURCE_ATTRIBUTES', () => { process.env['OTEL_RESOURCE_ATTRIBUTES'] = 'service.name=from-attrs'; process.env['OTEL_SERVICE_NAME'] = 'from-service-name'; const resource = getSentryResource('node'); - expect(resource.attributes[ATTR_SERVICE_NAME]).toBe('from-service-name'); + expect(resource.attributes[SERVICE_NAME]).toBe('from-service-name'); }); it('OTEL_RESOURCE_ATTRIBUTES can override service.namespace', () => { @@ -83,7 +83,7 @@ describe('getSentryResource', () => { it('Sentry SDK telemetry attrs cannot be overridden by OTEL_SERVICE_NAME (service.version)', () => { process.env['OTEL_RESOURCE_ATTRIBUTES'] = 'service.version=0.0.0'; const resource = getSentryResource('node'); - expect(resource.attributes[ATTR_SERVICE_VERSION]).toBe(SDK_VERSION); + expect(resource.attributes[SERVICE_VERSION]).toBe(SDK_VERSION); }); it('always includes Sentry SDK telemetry attributes', () => { @@ -91,7 +91,7 @@ describe('getSentryResource', () => { expect(resource.attributes[ATTR_TELEMETRY_SDK_LANGUAGE]).toBeDefined(); expect(resource.attributes[ATTR_TELEMETRY_SDK_NAME]).toBeDefined(); expect(resource.attributes[ATTR_TELEMETRY_SDK_VERSION]).toBeDefined(); - expect(resource.attributes[ATTR_SERVICE_VERSION]).toBe(SDK_VERSION); + expect(resource.attributes[SERVICE_VERSION]).toBe(SDK_VERSION); }); it('always sets service.namespace to sentry by default', () => { diff --git a/packages/opentelemetry/test/sampler.test.ts b/packages/opentelemetry/test/sampler.test.ts index 37c444a7b8d4..95f705ce4d96 100644 --- a/packages/opentelemetry/test/sampler.test.ts +++ b/packages/opentelemetry/test/sampler.test.ts @@ -1,7 +1,7 @@ import { context, SpanKind, trace, TraceFlags } from '@opentelemetry/api'; import { TraceState } from '../src/utils/TraceState'; import { SamplingDecision } from '@opentelemetry/sdk-trace-base'; -import { ATTR_HTTP_REQUEST_METHOD } from '@opentelemetry/semantic-conventions'; +import { HTTP_REQUEST_METHOD } from '@sentry/conventions/attributes'; import { generateSpanId, generateTraceId } from '@sentry/core'; import { afterEach, describe, expect, it, vi } from 'vitest'; import { @@ -130,7 +130,7 @@ describe('SentrySampler', () => { const spanName = 'test'; const spanKind = SpanKind.CLIENT; const spanAttributes = { - [ATTR_HTTP_REQUEST_METHOD]: 'GET', + [HTTP_REQUEST_METHOD]: 'GET', }; const links = undefined; @@ -205,7 +205,7 @@ describe('SentrySampler', () => { const traceId = generateTraceId(); const spanName = 'GET /health'; const spanKind = SpanKind.SERVER; - const spanAttributes = { [ATTR_HTTP_REQUEST_METHOD]: 'GET' }; + const spanAttributes = { [HTTP_REQUEST_METHOD]: 'GET' }; const actual = sampler.shouldSample(ctx, traceId, spanName, spanKind, spanAttributes, undefined); expect(actual.decision).toBe(SamplingDecision.NOT_RECORD); @@ -359,7 +359,7 @@ describe('SentrySampler', () => { const spanName = 'GET http://example.com/api'; const spanKind = SpanKind.CLIENT; const spanAttributes = { - [ATTR_HTTP_REQUEST_METHOD]: 'GET', + [HTTP_REQUEST_METHOD]: 'GET', }; const actual = sampler.shouldSample(ctx, traceId, spanName, spanKind, spanAttributes, undefined); diff --git a/packages/opentelemetry/test/spanExporter.test.ts b/packages/opentelemetry/test/spanExporter.test.ts index 1f233c65c055..d84d326bac1f 100644 --- a/packages/opentelemetry/test/spanExporter.test.ts +++ b/packages/opentelemetry/test/spanExporter.test.ts @@ -1,4 +1,4 @@ -import { ATTR_HTTP_RESPONSE_STATUS_CODE } from '@opentelemetry/semantic-conventions'; +import { HTTP_RESPONSE_STATUS_CODE } from '@sentry/conventions/attributes'; import { SDK_VERSION, SEMANTIC_ATTRIBUTE_SENTRY_OP, startInactiveSpan, startSpanManual } from '@sentry/core'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { createTransactionForOtelSpan } from '../src/spanExporter'; @@ -62,7 +62,7 @@ describe('createTransactionForOtelSpan', () => { startTime: 1733821670000, attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.server', - [ATTR_HTTP_RESPONSE_STATUS_CODE]: 200, + [HTTP_RESPONSE_STATUS_CODE]: 200, }, }); span.end(1733821672000); diff --git a/packages/opentelemetry/test/trace.test.ts b/packages/opentelemetry/test/trace.test.ts index 7b39b3698286..afe21a90a0f8 100644 --- a/packages/opentelemetry/test/trace.test.ts +++ b/packages/opentelemetry/test/trace.test.ts @@ -2,7 +2,7 @@ import type { Span, TimeInput } from '@opentelemetry/api'; import { context, ROOT_CONTEXT, SpanKind, trace, TraceFlags } from '@opentelemetry/api'; import type { ReadableSpan } from '@opentelemetry/sdk-trace-base'; -import { SEMATTRS_HTTP_METHOD } from '@opentelemetry/semantic-conventions'; +import { HTTP_METHOD } from '@sentry/conventions/attributes'; import type { Event, Scope } from '@sentry/core'; import { getClient, @@ -1879,36 +1879,33 @@ describe('HTTP methods (sampling)', () => { }); it('does sample when HTTP method is other than OPTIONS or HEAD', () => { - const spanGET = startSpanManual({ name: 'test span', attributes: { [SEMATTRS_HTTP_METHOD]: 'GET' } }, span => { + const spanGET = startSpanManual({ name: 'test span', attributes: { [HTTP_METHOD]: 'GET' } }, span => { return span; }); expect(spanIsSampled(spanGET)).toBe(true); expect(getSamplingDecision(spanGET.spanContext())).toBe(true); - const spanPOST = startSpanManual({ name: 'test span', attributes: { [SEMATTRS_HTTP_METHOD]: 'POST' } }, span => { + const spanPOST = startSpanManual({ name: 'test span', attributes: { [HTTP_METHOD]: 'POST' } }, span => { return span; }); expect(spanIsSampled(spanPOST)).toBe(true); expect(getSamplingDecision(spanPOST.spanContext())).toBe(true); - const spanPUT = startSpanManual({ name: 'test span', attributes: { [SEMATTRS_HTTP_METHOD]: 'PUT' } }, span => { + const spanPUT = startSpanManual({ name: 'test span', attributes: { [HTTP_METHOD]: 'PUT' } }, span => { return span; }); expect(spanIsSampled(spanPUT)).toBe(true); expect(getSamplingDecision(spanPUT.spanContext())).toBe(true); - const spanDELETE = startSpanManual( - { name: 'test span', attributes: { [SEMATTRS_HTTP_METHOD]: 'DELETE' } }, - span => { - return span; - }, - ); + const spanDELETE = startSpanManual({ name: 'test span', attributes: { [HTTP_METHOD]: 'DELETE' } }, span => { + return span; + }); expect(spanIsSampled(spanDELETE)).toBe(true); expect(getSamplingDecision(spanDELETE.spanContext())).toBe(true); }); it('does not sample when HTTP method is OPTIONS', () => { - const span = startSpanManual({ name: 'test span', attributes: { [SEMATTRS_HTTP_METHOD]: 'OPTIONS' } }, span => { + const span = startSpanManual({ name: 'test span', attributes: { [HTTP_METHOD]: 'OPTIONS' } }, span => { return span; }); expect(spanIsSampled(span)).toBe(false); @@ -1916,7 +1913,7 @@ describe('HTTP methods (sampling)', () => { }); it('does not sample when HTTP method is HEAD', () => { - const span = startSpanManual({ name: 'test span', attributes: { [SEMATTRS_HTTP_METHOD]: 'HEAD' } }, span => { + const span = startSpanManual({ name: 'test span', attributes: { [HTTP_METHOD]: 'HEAD' } }, span => { return span; }); expect(spanIsSampled(span)).toBe(false); diff --git a/packages/opentelemetry/test/utils/getRequestSpanData.test.ts b/packages/opentelemetry/test/utils/getRequestSpanData.test.ts index ad40ec83d480..71d4edb46d47 100644 --- a/packages/opentelemetry/test/utils/getRequestSpanData.test.ts +++ b/packages/opentelemetry/test/utils/getRequestSpanData.test.ts @@ -2,7 +2,7 @@ import type { Span } from '@opentelemetry/api'; import { trace } from '@opentelemetry/api'; import type { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; -import { SEMATTRS_HTTP_METHOD, SEMATTRS_HTTP_URL } from '@opentelemetry/semantic-conventions'; +import { HTTP_METHOD, HTTP_URL } from '@sentry/conventions/attributes'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { getRequestSpanData } from '../../src/utils/getRequestSpanData'; import { setupOtel } from '../helpers/initOtel'; @@ -35,8 +35,8 @@ describe('getRequestSpanData', () => { it('works with http span', () => { const span = createSpan('test-span'); span.setAttributes({ - [SEMATTRS_HTTP_URL]: 'http://example.com?foo=bar#baz', - [SEMATTRS_HTTP_METHOD]: 'GET', + [HTTP_URL]: 'http://example.com?foo=bar#baz', + [HTTP_METHOD]: 'GET', }); const data = getRequestSpanData(span); @@ -52,7 +52,7 @@ describe('getRequestSpanData', () => { it('works without method', () => { const span = createSpan('test-span'); span.setAttributes({ - [SEMATTRS_HTTP_URL]: 'http://example.com', + [HTTP_URL]: 'http://example.com', }); const data = getRequestSpanData(span); @@ -66,8 +66,8 @@ describe('getRequestSpanData', () => { it('works with incorrect URL', () => { const span = createSpan('test-span'); span.setAttributes({ - [SEMATTRS_HTTP_URL]: 'malformed-url-here', - [SEMATTRS_HTTP_METHOD]: 'GET', + [HTTP_URL]: 'malformed-url-here', + [HTTP_METHOD]: 'GET', }); const data = getRequestSpanData(span); diff --git a/packages/opentelemetry/test/utils/mapStatus.test.ts b/packages/opentelemetry/test/utils/mapStatus.test.ts index b754e121e276..89f6902d8b72 100644 --- a/packages/opentelemetry/test/utils/mapStatus.test.ts +++ b/packages/opentelemetry/test/utils/mapStatus.test.ts @@ -2,7 +2,7 @@ import type { Span } from '@opentelemetry/api'; import { trace } from '@opentelemetry/api'; import type { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; -import { SEMATTRS_HTTP_STATUS_CODE, SEMATTRS_RPC_GRPC_STATUS_CODE } from '@opentelemetry/semantic-conventions'; +import { HTTP_STATUS_CODE, RPC_GRPC_STATUS_CODE } from '@sentry/conventions/attributes'; import type { SpanStatus } from '@sentry/core'; import { SPAN_STATUS_ERROR, SPAN_STATUS_OK } from '@sentry/core'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; @@ -70,22 +70,22 @@ describe('mapStatus', () => { span.setStatus({ code: 0 }); // UNSET if (httpCode) { - span.setAttribute(SEMATTRS_HTTP_STATUS_CODE, httpCode); + span.setAttribute(HTTP_STATUS_CODE, httpCode); } if (grpcCode) { - span.setAttribute(SEMATTRS_RPC_GRPC_STATUS_CODE, grpcCode); + span.setAttribute(RPC_GRPC_STATUS_CODE, grpcCode); } const actual = mapStatus(span); expect(actual).toEqual(expected); }); - it('works with string SEMATTRS_HTTP_STATUS_CODE', () => { + it('works with string HTTP_STATUS_CODE', () => { const span = createSpan('test-span'); span.setStatus({ code: 0 }); // UNSET - span.setAttribute(SEMATTRS_HTTP_STATUS_CODE, '400'); + span.setAttribute(HTTP_STATUS_CODE, '400'); const actual = mapStatus(span); expect(actual).toEqual({ code: SPAN_STATUS_ERROR, message: 'invalid_argument' }); @@ -117,7 +117,7 @@ describe('mapStatus', () => { it('infers error status form attributes when span already has error status without message', () => { const span = createSpan('test-span'); - span.setAttribute(SEMATTRS_HTTP_STATUS_CODE, 500); + span.setAttribute(HTTP_STATUS_CODE, 500); span.setStatus({ code: 2 }); // ERROR expect(mapStatus(span)).toEqual({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); }); diff --git a/packages/opentelemetry/test/utils/parseSpanDescription.test.ts b/packages/opentelemetry/test/utils/parseSpanDescription.test.ts index 529866b8a2ac..709235ad65e7 100644 --- a/packages/opentelemetry/test/utils/parseSpanDescription.test.ts +++ b/packages/opentelemetry/test/utils/parseSpanDescription.test.ts @@ -1,20 +1,19 @@ -/* eslint-disable deprecation/deprecation */ import type { Span } from '@opentelemetry/api'; import { SpanKind } from '@opentelemetry/api'; import { - ATTR_DB_SYSTEM_NAME, - ATTR_HTTP_ROUTE, - SEMATTRS_DB_STATEMENT, - SEMATTRS_DB_SYSTEM, - SEMATTRS_FAAS_TRIGGER, - SEMATTRS_HTTP_HOST, - SEMATTRS_HTTP_METHOD, - SEMATTRS_HTTP_STATUS_CODE, - SEMATTRS_HTTP_TARGET, - SEMATTRS_HTTP_URL, - SEMATTRS_MESSAGING_SYSTEM, - SEMATTRS_RPC_SERVICE, -} from '@opentelemetry/semantic-conventions'; + DB_STATEMENT, + DB_SYSTEM, + DB_SYSTEM_NAME, + FAAS_TRIGGER, + HTTP_HOST, + HTTP_METHOD, + HTTP_ROUTE, + HTTP_STATUS_CODE, + HTTP_TARGET, + HTTP_URL, + MESSAGING_SYSTEM, + RPC_SERVICE, +} from '@sentry/conventions/attributes'; import { SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core'; import { describe, expect, it } from 'vitest'; import { @@ -51,7 +50,7 @@ describe('parseSpanDescription', () => { [ 'works with deprecated http method', { - [SEMATTRS_HTTP_METHOD]: 'GET', + [HTTP_METHOD]: 'GET', }, 'test name', SpanKind.CLIENT, @@ -77,8 +76,8 @@ describe('parseSpanDescription', () => { [ 'works with db system', { - [SEMATTRS_DB_SYSTEM]: 'mysql', - [SEMATTRS_DB_STATEMENT]: 'SELECT * from users', + [DB_SYSTEM]: 'mysql', + [DB_STATEMENT]: 'SELECT * from users', }, 'test name', SpanKind.CLIENT, @@ -92,8 +91,8 @@ describe('parseSpanDescription', () => { 'works with db system and custom source', { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom', - [SEMATTRS_DB_SYSTEM]: 'mysql', - [SEMATTRS_DB_STATEMENT]: 'SELECT * from users', + [DB_SYSTEM]: 'mysql', + [DB_STATEMENT]: 'SELECT * from users', }, 'test name', SpanKind.CLIENT, @@ -107,8 +106,8 @@ describe('parseSpanDescription', () => { 'works with db system and custom source and custom name', { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom', - [SEMATTRS_DB_SYSTEM]: 'mysql', - [SEMATTRS_DB_STATEMENT]: 'SELECT * from users', + [DB_SYSTEM]: 'mysql', + [DB_STATEMENT]: 'SELECT * from users', [SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]: 'custom name', }, 'test name', @@ -123,8 +122,8 @@ describe('parseSpanDescription', () => { 'works with db system and component source and custom name', { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'component', - [SEMATTRS_DB_SYSTEM]: 'mysql', - [SEMATTRS_DB_STATEMENT]: 'SELECT * from users', + [DB_SYSTEM]: 'mysql', + [DB_STATEMENT]: 'SELECT * from users', [SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]: 'custom name', }, 'test name', @@ -138,7 +137,7 @@ describe('parseSpanDescription', () => { [ 'works with db system without statement', { - [SEMATTRS_DB_SYSTEM]: 'mysql', + [DB_SYSTEM]: 'mysql', }, 'test name', SpanKind.CLIENT, @@ -151,8 +150,8 @@ describe('parseSpanDescription', () => { [ 'works with db.system.name (stable attribute)', { - [ATTR_DB_SYSTEM_NAME]: 'postgresql', - [SEMATTRS_DB_STATEMENT]: 'SELECT * from users', + [DB_SYSTEM_NAME]: 'postgresql', + [DB_STATEMENT]: 'SELECT * from users', }, 'test name', SpanKind.CLIENT, @@ -165,7 +164,7 @@ describe('parseSpanDescription', () => { [ 'works with db.system.name without statement', { - [ATTR_DB_SYSTEM_NAME]: 'postgresql', + [DB_SYSTEM_NAME]: 'postgresql', }, 'test name', SpanKind.CLIENT, @@ -178,9 +177,9 @@ describe('parseSpanDescription', () => { [ 'prefers db.system.name over deprecated db.system', { - [ATTR_DB_SYSTEM_NAME]: 'postgresql', - [SEMATTRS_DB_SYSTEM]: 'mysql', - [SEMATTRS_DB_STATEMENT]: 'SELECT * from users', + [DB_SYSTEM_NAME]: 'postgresql', + [DB_SYSTEM]: 'mysql', + [DB_STATEMENT]: 'SELECT * from users', }, 'test name', SpanKind.CLIENT, @@ -193,7 +192,7 @@ describe('parseSpanDescription', () => { [ 'works with rpc service', { - [SEMATTRS_RPC_SERVICE]: 'rpc-test-service', + [RPC_SERVICE]: 'rpc-test-service', }, 'test name', undefined, @@ -207,7 +206,7 @@ describe('parseSpanDescription', () => { 'works with rpc service and custom source', { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom', - [SEMATTRS_RPC_SERVICE]: 'rpc-test-service', + [RPC_SERVICE]: 'rpc-test-service', }, 'test name', undefined, @@ -221,7 +220,7 @@ describe('parseSpanDescription', () => { 'works with rpc service and custom source and custom name', { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom', - [SEMATTRS_RPC_SERVICE]: 'rpc-test-service', + [RPC_SERVICE]: 'rpc-test-service', [SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]: 'custom name', }, 'test name', @@ -236,7 +235,7 @@ describe('parseSpanDescription', () => { 'works with rpc service and component source and custom name', { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'component', - [SEMATTRS_RPC_SERVICE]: 'rpc-test-service', + [RPC_SERVICE]: 'rpc-test-service', [SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]: 'custom name', }, 'test name', @@ -250,7 +249,7 @@ describe('parseSpanDescription', () => { [ 'works with messaging system', { - [SEMATTRS_MESSAGING_SYSTEM]: 'test-messaging-system', + [MESSAGING_SYSTEM]: 'test-messaging-system', }, 'test name', undefined, @@ -264,7 +263,7 @@ describe('parseSpanDescription', () => { 'works with messaging system and custom source', { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom', - [SEMATTRS_MESSAGING_SYSTEM]: 'test-messaging-system', + [MESSAGING_SYSTEM]: 'test-messaging-system', }, 'test name', undefined, @@ -278,7 +277,7 @@ describe('parseSpanDescription', () => { 'works with messaging system and custom source and custom name', { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom', - [SEMATTRS_MESSAGING_SYSTEM]: 'test-messaging-system', + [MESSAGING_SYSTEM]: 'test-messaging-system', [SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]: 'custom name', }, 'test name', @@ -293,7 +292,7 @@ describe('parseSpanDescription', () => { 'works with messaging system and component source and custom name', { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'component', - [SEMATTRS_MESSAGING_SYSTEM]: 'test-messaging-system', + [MESSAGING_SYSTEM]: 'test-messaging-system', [SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]: 'custom name', }, 'test name', @@ -307,7 +306,7 @@ describe('parseSpanDescription', () => { [ 'works with faas trigger', { - [SEMATTRS_FAAS_TRIGGER]: 'test-faas-trigger', + [FAAS_TRIGGER]: 'test-faas-trigger', }, 'test name', undefined, @@ -321,7 +320,7 @@ describe('parseSpanDescription', () => { 'works with faas trigger and custom source', { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom', - [SEMATTRS_FAAS_TRIGGER]: 'test-faas-trigger', + [FAAS_TRIGGER]: 'test-faas-trigger', }, 'test name', undefined, @@ -335,7 +334,7 @@ describe('parseSpanDescription', () => { 'works with faas trigger and custom source and custom name', { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom', - [SEMATTRS_FAAS_TRIGGER]: 'test-faas-trigger', + [FAAS_TRIGGER]: 'test-faas-trigger', [SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]: 'custom name', }, 'test name', @@ -350,7 +349,7 @@ describe('parseSpanDescription', () => { 'works with faas trigger and component source and custom name', { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'component', - [SEMATTRS_FAAS_TRIGGER]: 'test-faas-trigger', + [FAAS_TRIGGER]: 'test-faas-trigger', [SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]: 'custom name', }, 'test name', @@ -385,9 +384,9 @@ describe('descriptionForHttpMethod', () => { 'works with basic client GET', 'GET', { - [SEMATTRS_HTTP_METHOD]: 'GET', - [SEMATTRS_HTTP_URL]: 'https://www.example.com/my-path', - [SEMATTRS_HTTP_TARGET]: '/my-path', + [HTTP_METHOD]: 'GET', + [HTTP_URL]: 'https://www.example.com/my-path', + [HTTP_TARGET]: '/my-path', }, 'test name', SpanKind.CLIENT, @@ -404,9 +403,9 @@ describe('descriptionForHttpMethod', () => { 'works with prefetch request', 'GET', { - [SEMATTRS_HTTP_METHOD]: 'GET', - [SEMATTRS_HTTP_URL]: 'https://www.example.com/my-path', - [SEMATTRS_HTTP_TARGET]: '/my-path', + [HTTP_METHOD]: 'GET', + [HTTP_URL]: 'https://www.example.com/my-path', + [HTTP_TARGET]: '/my-path', 'sentry.http.prefetch': true, }, 'test name', @@ -424,9 +423,9 @@ describe('descriptionForHttpMethod', () => { 'works with basic server POST', 'POST', { - [SEMATTRS_HTTP_METHOD]: 'POST', - [SEMATTRS_HTTP_URL]: 'https://www.example.com/my-path', - [SEMATTRS_HTTP_TARGET]: '/my-path', + [HTTP_METHOD]: 'POST', + [HTTP_URL]: 'https://www.example.com/my-path', + [HTTP_TARGET]: '/my-path', }, 'test name', SpanKind.SERVER, @@ -443,10 +442,10 @@ describe('descriptionForHttpMethod', () => { 'works with client GET with route', 'GET', { - [SEMATTRS_HTTP_METHOD]: 'GET', - [SEMATTRS_HTTP_URL]: 'https://www.example.com/my-path/123', - [SEMATTRS_HTTP_TARGET]: '/my-path/123', - [ATTR_HTTP_ROUTE]: '/my-path/:id', + [HTTP_METHOD]: 'GET', + [HTTP_URL]: 'https://www.example.com/my-path/123', + [HTTP_TARGET]: '/my-path/123', + [HTTP_ROUTE]: '/my-path/:id', }, 'test name', SpanKind.CLIENT, @@ -463,9 +462,9 @@ describe('descriptionForHttpMethod', () => { 'works with basic client GET with SpanKind.INTERNAL', 'GET', { - [SEMATTRS_HTTP_METHOD]: 'GET', - [SEMATTRS_HTTP_URL]: 'https://www.example.com/my-path', - [SEMATTRS_HTTP_TARGET]: '/my-path', + [HTTP_METHOD]: 'GET', + [HTTP_URL]: 'https://www.example.com/my-path', + [HTTP_TARGET]: '/my-path', }, 'test name', SpanKind.INTERNAL, @@ -482,10 +481,10 @@ describe('descriptionForHttpMethod', () => { "doesn't overwrite span name with source custom", 'GET', { - [SEMATTRS_HTTP_METHOD]: 'GET', - [SEMATTRS_HTTP_URL]: 'https://www.example.com/my-path/123', - [SEMATTRS_HTTP_TARGET]: '/my-path/123', - [ATTR_HTTP_ROUTE]: '/my-path/:id', + [HTTP_METHOD]: 'GET', + [HTTP_URL]: 'https://www.example.com/my-path/123', + [HTTP_TARGET]: '/my-path/123', + [HTTP_ROUTE]: '/my-path/:id', [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom', }, 'test name', @@ -503,10 +502,10 @@ describe('descriptionForHttpMethod', () => { 'takes user-passed span name (with source custom)', 'GET', { - [SEMATTRS_HTTP_METHOD]: 'GET', - [SEMATTRS_HTTP_URL]: 'https://www.example.com/my-path/123', - [SEMATTRS_HTTP_TARGET]: '/my-path/123', - [ATTR_HTTP_ROUTE]: '/my-path/:id', + [HTTP_METHOD]: 'GET', + [HTTP_URL]: 'https://www.example.com/my-path/123', + [HTTP_TARGET]: '/my-path/123', + [HTTP_ROUTE]: '/my-path/:id', [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom', [SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]: 'custom name', }, @@ -525,10 +524,10 @@ describe('descriptionForHttpMethod', () => { 'takes user-passed span name (with source component)', 'GET', { - [SEMATTRS_HTTP_METHOD]: 'GET', - [SEMATTRS_HTTP_URL]: 'https://www.example.com/my-path/123', - [SEMATTRS_HTTP_TARGET]: '/my-path/123', - [ATTR_HTTP_ROUTE]: '/my-path/:id', + [HTTP_METHOD]: 'GET', + [HTTP_URL]: 'https://www.example.com/my-path/123', + [HTTP_TARGET]: '/my-path/123', + [HTTP_ROUTE]: '/my-path/:id', [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'component', [SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]: 'custom name', }, @@ -566,11 +565,11 @@ describe('getSanitizedUrl', () => { [ 'uses url without query for client request', { - [SEMATTRS_HTTP_URL]: 'http://example.com/?what=true', - [SEMATTRS_HTTP_METHOD]: 'GET', - [SEMATTRS_HTTP_TARGET]: '/?what=true', - [SEMATTRS_HTTP_HOST]: 'example.com:80', - [SEMATTRS_HTTP_STATUS_CODE]: 200, + [HTTP_URL]: 'http://example.com/?what=true', + [HTTP_METHOD]: 'GET', + [HTTP_TARGET]: '/?what=true', + [HTTP_HOST]: 'example.com:80', + [HTTP_STATUS_CODE]: 200, }, SpanKind.CLIENT, { @@ -584,11 +583,11 @@ describe('getSanitizedUrl', () => { [ 'uses url without hash for client request', { - [SEMATTRS_HTTP_URL]: 'http://example.com/sub#hash', - [SEMATTRS_HTTP_METHOD]: 'GET', - [SEMATTRS_HTTP_TARGET]: '/sub#hash', - [SEMATTRS_HTTP_HOST]: 'example.com:80', - [SEMATTRS_HTTP_STATUS_CODE]: 200, + [HTTP_URL]: 'http://example.com/sub#hash', + [HTTP_METHOD]: 'GET', + [HTTP_TARGET]: '/sub#hash', + [HTTP_HOST]: 'example.com:80', + [HTTP_STATUS_CODE]: 200, }, SpanKind.CLIENT, { @@ -602,12 +601,12 @@ describe('getSanitizedUrl', () => { [ 'uses route if available for client request', { - [SEMATTRS_HTTP_URL]: 'http://example.com/?what=true', - [SEMATTRS_HTTP_METHOD]: 'GET', - [SEMATTRS_HTTP_TARGET]: '/?what=true', - [ATTR_HTTP_ROUTE]: '/my-route', - [SEMATTRS_HTTP_HOST]: 'example.com:80', - [SEMATTRS_HTTP_STATUS_CODE]: 200, + [HTTP_URL]: 'http://example.com/?what=true', + [HTTP_METHOD]: 'GET', + [HTTP_TARGET]: '/?what=true', + [HTTP_ROUTE]: '/my-route', + [HTTP_HOST]: 'example.com:80', + [HTTP_STATUS_CODE]: 200, }, SpanKind.CLIENT, { @@ -621,10 +620,10 @@ describe('getSanitizedUrl', () => { [ 'falls back to target for client request if url not available', { - [SEMATTRS_HTTP_METHOD]: 'GET', - [SEMATTRS_HTTP_TARGET]: '/?what=true', - [SEMATTRS_HTTP_HOST]: 'example.com:80', - [SEMATTRS_HTTP_STATUS_CODE]: 200, + [HTTP_METHOD]: 'GET', + [HTTP_TARGET]: '/?what=true', + [HTTP_HOST]: 'example.com:80', + [HTTP_STATUS_CODE]: 200, }, SpanKind.CLIENT, { @@ -638,11 +637,11 @@ describe('getSanitizedUrl', () => { [ 'uses target without query for server request', { - [SEMATTRS_HTTP_URL]: 'http://example.com/?what=true', - [SEMATTRS_HTTP_METHOD]: 'GET', - [SEMATTRS_HTTP_TARGET]: '/?what=true', - [SEMATTRS_HTTP_HOST]: 'example.com:80', - [SEMATTRS_HTTP_STATUS_CODE]: 200, + [HTTP_URL]: 'http://example.com/?what=true', + [HTTP_METHOD]: 'GET', + [HTTP_TARGET]: '/?what=true', + [HTTP_HOST]: 'example.com:80', + [HTTP_STATUS_CODE]: 200, }, SpanKind.SERVER, { @@ -656,11 +655,11 @@ describe('getSanitizedUrl', () => { [ 'uses target without hash for server request', { - [SEMATTRS_HTTP_URL]: 'http://example.com/?what=true', - [SEMATTRS_HTTP_METHOD]: 'GET', - [SEMATTRS_HTTP_TARGET]: '/sub#hash', - [SEMATTRS_HTTP_HOST]: 'example.com:80', - [SEMATTRS_HTTP_STATUS_CODE]: 200, + [HTTP_URL]: 'http://example.com/?what=true', + [HTTP_METHOD]: 'GET', + [HTTP_TARGET]: '/sub#hash', + [HTTP_HOST]: 'example.com:80', + [HTTP_STATUS_CODE]: 200, }, SpanKind.SERVER, { @@ -674,12 +673,12 @@ describe('getSanitizedUrl', () => { [ 'uses route for server request if available', { - [SEMATTRS_HTTP_URL]: 'http://example.com/?what=true', - [SEMATTRS_HTTP_METHOD]: 'GET', - [SEMATTRS_HTTP_TARGET]: '/?what=true', - [ATTR_HTTP_ROUTE]: '/my-route', - [SEMATTRS_HTTP_HOST]: 'example.com:80', - [SEMATTRS_HTTP_STATUS_CODE]: 200, + [HTTP_URL]: 'http://example.com/?what=true', + [HTTP_METHOD]: 'GET', + [HTTP_TARGET]: '/?what=true', + [HTTP_ROUTE]: '/my-route', + [HTTP_HOST]: 'example.com:80', + [HTTP_STATUS_CODE]: 200, }, SpanKind.SERVER, { diff --git a/packages/react-router/package.json b/packages/react-router/package.json index 4697eb1d8746..7b26738d0ab2 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -48,7 +48,6 @@ "@opentelemetry/api": "^1.9.1", "@opentelemetry/core": "^2.6.1", "@opentelemetry/instrumentation": "^0.214.0", - "@opentelemetry/semantic-conventions": "^1.40.0", "@sentry/browser": "10.57.0", "@sentry/cli": "^2.58.6", "@sentry/core": "10.57.0", @@ -60,6 +59,7 @@ "devDependencies": { "@react-router/dev": "^7.13.0", "@react-router/node": "^7.13.1", + "@sentry/conventions": "^0.11.0", "react": "^18.3.1", "react-router": "^7.15.0", "vite": "^6.4.2" diff --git a/packages/react-router/rollup.npm.config.mjs b/packages/react-router/rollup.npm.config.mjs index edd3e56f0cb8..01fbbde98443 100644 --- a/packages/react-router/rollup.npm.config.mjs +++ b/packages/react-router/rollup.npm.config.mjs @@ -13,6 +13,9 @@ export default [ output: { // make it so Rollup calms down about the fact that we're combining default and named exports exports: 'named', + // keep emitted module paths relative to `src` so the bundled `@sentry/conventions` + // (a devDependency, vendored into the build) doesn't shift the output layout + preserveModulesRoot: 'src', }, }, }), diff --git a/packages/react-router/src/server/createServerInstrumentation.ts b/packages/react-router/src/server/createServerInstrumentation.ts index d65191956855..2dd14014b730 100644 --- a/packages/react-router/src/server/createServerInstrumentation.ts +++ b/packages/react-router/src/server/createServerInstrumentation.ts @@ -1,5 +1,5 @@ import { context, createContextKey } from '@opentelemetry/api'; -import { ATTR_HTTP_ROUTE } from '@opentelemetry/semantic-conventions'; +import { HTTP_ROUTE } from '@sentry/conventions/attributes'; import { debug, flushIfServerless, @@ -200,7 +200,7 @@ export function createSentryServerInstrumentation( [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react_router.middleware', [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.react_router.instrumentation_api', 'react_router.route.id': routeId, - [ATTR_HTTP_ROUTE]: routePattern, + [HTTP_ROUTE]: routePattern, ...(middlewareName && { 'react_router.middleware.name': middlewareName }), 'react_router.middleware.index': middlewareIndex, }, @@ -259,7 +259,7 @@ function updateRootSpanWithRoute(method: string, pattern: string | undefined, ur const transactionName = `${method} ${routeName}`; updateSpanName(rootSpan, transactionName); rootSpan.setAttributes({ - [ATTR_HTTP_ROUTE]: routeName, + [HTTP_ROUTE]: routeName, [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: hasPattern ? 'route' : 'url', }); diff --git a/packages/react-router/src/server/instrumentation/reactRouter.ts b/packages/react-router/src/server/instrumentation/reactRouter.ts index e322caa5cfcc..c9f1beb47687 100644 --- a/packages/react-router/src/server/instrumentation/reactRouter.ts +++ b/packages/react-router/src/server/instrumentation/reactRouter.ts @@ -1,6 +1,6 @@ import type { InstrumentationConfig } from '@opentelemetry/instrumentation'; import { InstrumentationBase, InstrumentationNodeModuleDefinition } from '@opentelemetry/instrumentation'; -import { SEMATTRS_HTTP_TARGET } from '@opentelemetry/semantic-conventions'; +import { HTTP_TARGET } from '@sentry/conventions/attributes'; import { debug, getActiveSpan, @@ -112,8 +112,7 @@ export class ReactRouterInstrumentation extends InstrumentationBase { if ( event.type === 'transaction' && event.contexts?.trace?.data && - event.contexts.trace.data[ATTR_HTTP_ROUTE] === '*' + event.contexts.trace.data[HTTP_ROUTE] === '*' ) { const origin = event.contexts.trace.origin; const isInstrumentationApiOrigin = origin?.includes('instrumentation_api'); @@ -54,7 +54,7 @@ export const reactRouterServerIntegration = defineIntegration(() => { // For legacy, only clean up if the name has been adjusted (not METHOD *) if (isInstrumentationApiOrigin || !event.transaction?.endsWith(' *')) { // eslint-disable-next-line @typescript-eslint/no-dynamic-delete - delete event.contexts.trace.data[ATTR_HTTP_ROUTE]; + delete event.contexts.trace.data[HTTP_ROUTE]; } } @@ -64,7 +64,7 @@ export const reactRouterServerIntegration = defineIntegration(() => { // Express generates bogus `*` routes for data loaders, which we want to remove here // we cannot do this earlier because some OTEL instrumentation adds this at some unexpected point const attributes = span.attributes; - if (attributes?.[ATTR_HTTP_ROUTE] !== '*') { + if (attributes?.[HTTP_ROUTE] !== '*') { return; } @@ -75,7 +75,7 @@ export const reactRouterServerIntegration = defineIntegration(() => { // For legacy, only clean up if the name has been adjusted (not METHOD *) if (isInstrumentationApiOrigin || !span.name?.endsWith(' *')) { // eslint-disable-next-line @typescript-eslint/no-dynamic-delete - delete attributes[ATTR_HTTP_ROUTE]; + delete attributes[HTTP_ROUTE]; } }, }; diff --git a/packages/react-router/src/server/wrapSentryHandleRequest.ts b/packages/react-router/src/server/wrapSentryHandleRequest.ts index 8ff324e049a4..2a22b54dd4e6 100644 --- a/packages/react-router/src/server/wrapSentryHandleRequest.ts +++ b/packages/react-router/src/server/wrapSentryHandleRequest.ts @@ -1,4 +1,4 @@ -import { ATTR_HTTP_ROUTE } from '@opentelemetry/semantic-conventions'; +import { HTTP_ROUTE } from '@sentry/conventions/attributes'; import { flushIfServerless, getActiveSpan, @@ -80,12 +80,12 @@ export function wrapSentryHandleRequest( // Don't override origin when instrumentation API is used (preserve instrumentation_api origin) if (isInstrumentationApiUsed()) { rootSpan.setAttributes({ - [ATTR_HTTP_ROUTE]: routeName, + [HTTP_ROUTE]: routeName, [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', }); } else { rootSpan.setAttributes({ - [ATTR_HTTP_ROUTE]: routeName, + [HTTP_ROUTE]: routeName, [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react_router.request_handler', }); diff --git a/packages/react-router/src/server/wrapServerAction.ts b/packages/react-router/src/server/wrapServerAction.ts index 356237008650..db44ff7248ba 100644 --- a/packages/react-router/src/server/wrapServerAction.ts +++ b/packages/react-router/src/server/wrapServerAction.ts @@ -1,4 +1,4 @@ -import { SEMATTRS_HTTP_TARGET } from '@opentelemetry/semantic-conventions'; +import { HTTP_TARGET } from '@sentry/conventions/attributes'; import type { SpanAttributes } from '@sentry/core'; import { debug, @@ -66,8 +66,7 @@ export function wrapServerAction( const root = getRootSpan(active); const spanData = spanToJSON(root); if (spanData.origin === 'auto.http.otel.http') { - // eslint-disable-next-line deprecation/deprecation - const target = spanData.data[SEMATTRS_HTTP_TARGET]; + const target = spanData.data[HTTP_TARGET]; if (target) { // We cannot rely on the regular span name inferral here, as the express instrumentation sets `*` as the route diff --git a/packages/react-router/src/server/wrapServerLoader.ts b/packages/react-router/src/server/wrapServerLoader.ts index a3146d0de24a..87f1398351b1 100644 --- a/packages/react-router/src/server/wrapServerLoader.ts +++ b/packages/react-router/src/server/wrapServerLoader.ts @@ -1,4 +1,4 @@ -import { SEMATTRS_HTTP_TARGET } from '@opentelemetry/semantic-conventions'; +import { HTTP_TARGET } from '@sentry/conventions/attributes'; import type { SpanAttributes } from '@sentry/core'; import { debug, @@ -67,8 +67,7 @@ export function wrapServerLoader( const root = getRootSpan(active); const spanData = spanToJSON(root); if (spanData.origin === 'auto.http.otel.http') { - // eslint-disable-next-line deprecation/deprecation - const target = spanData.data[SEMATTRS_HTTP_TARGET]; + const target = spanData.data[HTTP_TARGET]; if (target) { // We cannot rely on the regular span name inferral here, as the express instrumentation sets `*` as the route diff --git a/packages/react-router/test/server/integration/reactRouterServer.test.ts b/packages/react-router/test/server/integration/reactRouterServer.test.ts index b97d6403bd18..8aa03fcddfc3 100644 --- a/packages/react-router/test/server/integration/reactRouterServer.test.ts +++ b/packages/react-router/test/server/integration/reactRouterServer.test.ts @@ -1,4 +1,4 @@ -import { ATTR_HTTP_ROUTE } from '@opentelemetry/semantic-conventions'; +import { HTTP_ROUTE } from '@sentry/conventions/attributes'; import type { Client, Event, EventType, StreamedSpanJSON } from '@sentry/core'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { ReactRouterInstrumentation } from '../../../src/server/instrumentation/reactRouter'; @@ -112,7 +112,7 @@ describe('reactRouterServerIntegration', () => { transaction: 'GET /users/:id', contexts: { trace: { - data: { [ATTR_HTTP_ROUTE]: '/users/:id' }, + data: { [HTTP_ROUTE]: '/users/:id' }, origin: 'auto.http.otel.http', }, }, @@ -120,7 +120,7 @@ describe('reactRouterServerIntegration', () => { integration.processEvent!(event, hint, client); - expect(event.contexts?.trace?.data?.[ATTR_HTTP_ROUTE]).toBe('/users/:id'); + expect(event.contexts?.trace?.data?.[HTTP_ROUTE]).toBe('/users/:id'); }); it('deletes bogus "*" route when origin is instrumentation_api', () => { @@ -130,7 +130,7 @@ describe('reactRouterServerIntegration', () => { transaction: 'GET *', contexts: { trace: { - data: { [ATTR_HTTP_ROUTE]: '*' }, + data: { [HTTP_ROUTE]: '*' }, origin: 'auto.http.otel.instrumentation_api', }, }, @@ -138,7 +138,7 @@ describe('reactRouterServerIntegration', () => { integration.processEvent!(event, hint, client); - expect(event.contexts?.trace?.data?.[ATTR_HTTP_ROUTE]).toBeUndefined(); + expect(event.contexts?.trace?.data?.[HTTP_ROUTE]).toBeUndefined(); }); it('deletes bogus "*" route when legacy origin and transaction name was renamed', () => { @@ -148,7 +148,7 @@ describe('reactRouterServerIntegration', () => { transaction: 'GET /api/users', contexts: { trace: { - data: { [ATTR_HTTP_ROUTE]: '*' }, + data: { [HTTP_ROUTE]: '*' }, origin: 'auto.http.otel.http', }, }, @@ -156,7 +156,7 @@ describe('reactRouterServerIntegration', () => { integration.processEvent!(event, hint, client); - expect(event.contexts?.trace?.data?.[ATTR_HTTP_ROUTE]).toBeUndefined(); + expect(event.contexts?.trace?.data?.[HTTP_ROUTE]).toBeUndefined(); }); it('keeps "*" when legacy origin and transaction name still ends with " *"', () => { @@ -166,7 +166,7 @@ describe('reactRouterServerIntegration', () => { transaction: 'GET *', contexts: { trace: { - data: { [ATTR_HTTP_ROUTE]: '*' }, + data: { [HTTP_ROUTE]: '*' }, origin: 'auto.http.otel.http', }, }, @@ -174,7 +174,7 @@ describe('reactRouterServerIntegration', () => { integration.processEvent!(event, hint, client); - expect(event.contexts?.trace?.data?.[ATTR_HTTP_ROUTE]).toBe('*'); + expect(event.contexts?.trace?.data?.[HTTP_ROUTE]).toBe('*'); }); }); @@ -185,48 +185,48 @@ describe('reactRouterServerIntegration', () => { const integration = reactRouterServerIntegration(); const span = { name: 'GET /users/:id', - attributes: { [ATTR_HTTP_ROUTE]: '/users/:id', 'sentry.origin': 'auto.http.otel.http' }, + attributes: { [HTTP_ROUTE]: '/users/:id', 'sentry.origin': 'auto.http.otel.http' }, } as unknown as StreamedSpanJSON; integration.processSegmentSpan!(span, client); - expect(span.attributes?.[ATTR_HTTP_ROUTE]).toBe('/users/:id'); + expect(span.attributes?.[HTTP_ROUTE]).toBe('/users/:id'); }); it('deletes bogus "*" route when origin is instrumentation_api', () => { const integration = reactRouterServerIntegration(); const span = { name: 'GET *', - attributes: { [ATTR_HTTP_ROUTE]: '*', 'sentry.origin': 'auto.http.otel.instrumentation_api' }, + attributes: { [HTTP_ROUTE]: '*', 'sentry.origin': 'auto.http.otel.instrumentation_api' }, } as unknown as StreamedSpanJSON; integration.processSegmentSpan!(span, client); - expect(span.attributes?.[ATTR_HTTP_ROUTE]).toBeUndefined(); + expect(span.attributes?.[HTTP_ROUTE]).toBeUndefined(); }); it('deletes bogus "*" route when legacy origin and span name was renamed', () => { const integration = reactRouterServerIntegration(); const span = { name: 'GET /api/users', - attributes: { [ATTR_HTTP_ROUTE]: '*', 'sentry.origin': 'auto.http.otel.http' }, + attributes: { [HTTP_ROUTE]: '*', 'sentry.origin': 'auto.http.otel.http' }, } as unknown as StreamedSpanJSON; integration.processSegmentSpan!(span, client); - expect(span.attributes?.[ATTR_HTTP_ROUTE]).toBeUndefined(); + expect(span.attributes?.[HTTP_ROUTE]).toBeUndefined(); }); it('keeps "*" when legacy origin and span name still ends with " *"', () => { const integration = reactRouterServerIntegration(); const span = { name: 'GET *', - attributes: { [ATTR_HTTP_ROUTE]: '*', 'sentry.origin': 'auto.http.otel.http' }, + attributes: { [HTTP_ROUTE]: '*', 'sentry.origin': 'auto.http.otel.http' }, } as unknown as StreamedSpanJSON; integration.processSegmentSpan!(span, client); - expect(span.attributes?.[ATTR_HTTP_ROUTE]).toBe('*'); + expect(span.attributes?.[HTTP_ROUTE]).toBe('*'); }); }); }); diff --git a/packages/react-router/test/server/wrapSentryHandleRequest.test.ts b/packages/react-router/test/server/wrapSentryHandleRequest.test.ts index e31ea94d06bf..45b113f887bb 100644 --- a/packages/react-router/test/server/wrapSentryHandleRequest.test.ts +++ b/packages/react-router/test/server/wrapSentryHandleRequest.test.ts @@ -1,5 +1,5 @@ import { PassThrough } from 'node:stream'; -import { ATTR_HTTP_ROUTE } from '@opentelemetry/semantic-conventions'; +import { HTTP_ROUTE } from '@sentry/conventions/attributes'; import { flushIfServerless, getActiveSpan, @@ -72,7 +72,7 @@ describe('wrapSentryHandleRequest', () => { await wrappedHandler(new Request('https://nacho.queso'), 200, new Headers(), routerContext, {} as any); expect(mockRootSpan.setAttributes).toHaveBeenCalledWith({ - [ATTR_HTTP_ROUTE]: '/some-path', + [HTTP_ROUTE]: '/some-path', [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react_router.request_handler', }); @@ -191,7 +191,7 @@ describe('wrapSentryHandleRequest', () => { // Should set route attributes without origin (to preserve instrumentation_api origin) expect(mockRootSpan.setAttributes).toHaveBeenCalledWith({ - [ATTR_HTTP_ROUTE]: '/some-path', + [HTTP_ROUTE]: '/some-path', [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', }); }); diff --git a/packages/remix/package.json b/packages/remix/package.json index f89c8592db1d..5752ad98f65f 100644 --- a/packages/remix/package.json +++ b/packages/remix/package.json @@ -66,7 +66,6 @@ "dependencies": { "@opentelemetry/api": "^1.9.1", "@opentelemetry/instrumentation": "^0.214.0", - "@opentelemetry/semantic-conventions": "^1.40.0", "@remix-run/router": "^1.23.3", "@sentry/cli": "^2.58.6", "@sentry/core": "10.57.0", @@ -78,6 +77,7 @@ "@remix-run/node": "^2.17.5", "@remix-run/react": "^2.17.5", "@remix-run/server-runtime": "^2.17.4", + "@sentry/conventions": "^0.11.0", "@types/express": "^4.17.14", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/packages/remix/rollup.npm.config.mjs b/packages/remix/rollup.npm.config.mjs index 23b3b4452816..f675cf21c7f8 100644 --- a/packages/remix/rollup.npm.config.mjs +++ b/packages/remix/rollup.npm.config.mjs @@ -19,6 +19,9 @@ export default [ output: { // make it so Rollup calms down about the fact that we're combining default and named exports exports: 'named', + // keep emitted module paths relative to `src` so the bundled `@sentry/conventions` + // (a devDependency, vendored into the build) doesn't shift the output layout + preserveModulesRoot: 'src', }, }, }), diff --git a/packages/remix/src/vendor/instrumentation.ts b/packages/remix/src/vendor/instrumentation.ts index 9da38fbd0e7a..74780bdc8785 100644 --- a/packages/remix/src/vendor/instrumentation.ts +++ b/packages/remix/src/vendor/instrumentation.ts @@ -32,7 +32,7 @@ import { InstrumentationNodeModuleFile, isWrapped, } from '@opentelemetry/instrumentation'; -import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; +import { CODE_FUNCTION, HTTP_METHOD, HTTP_ROUTE, HTTP_STATUS_CODE, HTTP_URL } from '@sentry/conventions/attributes'; import type { Params } from '@remix-run/router'; import type * as remixRunServerRuntime from '@remix-run/server-runtime'; import type * as remixRunServerRuntimeData from '@remix-run/server-runtime/dist/data'; @@ -192,7 +192,7 @@ export class RemixInstrumentation extends InstrumentationBase { const routePath = route?.path; if (span && routePath) { - span.setAttribute(SemanticAttributes.HTTP_ROUTE, routePath); + span.setAttribute(HTTP_ROUTE, routePath); span.updateName(`remix.request ${routePath}`); } @@ -220,7 +220,7 @@ export class RemixInstrumentation extends InstrumentationBase { const span = plugin.tracer.startSpan( 'remix.request', { - attributes: { [SemanticAttributes.CODE_FUNCTION]: 'requestHandler' }, + attributes: { [CODE_FUNCTION]: 'requestHandler' }, }, opentelemetry.context.active(), ); @@ -256,7 +256,7 @@ export class RemixInstrumentation extends InstrumentationBase { const span = plugin.tracer.startSpan( `LOADER ${params.routeId}`, - { attributes: { [SemanticAttributes.CODE_FUNCTION]: 'loader' } }, + { attributes: { [CODE_FUNCTION]: 'loader' } }, opentelemetry.context.active(), ); @@ -291,7 +291,7 @@ export class RemixInstrumentation extends InstrumentationBase { const clonedRequest = params.request.clone(); const span = plugin.tracer.startSpan( `ACTION ${params.routeId}`, - { attributes: { [SemanticAttributes.CODE_FUNCTION]: 'action' } }, + { attributes: { [CODE_FUNCTION]: 'action' } }, opentelemetry.context.active(), ); @@ -344,8 +344,8 @@ export class RemixInstrumentation extends InstrumentationBase { const addRequestAttributesToSpan = (span: Span, request: Request): void => { span.setAttributes({ - [SemanticAttributes.HTTP_METHOD]: request.method, - [SemanticAttributes.HTTP_URL]: request.url, + [HTTP_METHOD]: request.method, + [HTTP_URL]: request.url, }); }; @@ -362,7 +362,7 @@ const addMatchAttributesToSpan = (span: Span, match: { routeId: string; params: const addResponseAttributesToSpan = (span: Span, response: Response | null): void => { if (response) { span.setAttributes({ - [SemanticAttributes.HTTP_STATUS_CODE]: response.status, + [HTTP_STATUS_CODE]: response.status, }); } }; diff --git a/packages/server-utils/package.json b/packages/server-utils/package.json index ece9791b29d3..c452a19e054b 100644 --- a/packages/server-utils/package.json +++ b/packages/server-utils/package.json @@ -41,6 +41,9 @@ "dependencies": { "@sentry/core": "10.57.0" }, + "devDependencies": { + "@sentry/conventions": "^0.11.0" + }, "scripts": { "build": "run-p build:transpile build:types", "build:dev": "yarn build", diff --git a/packages/server-utils/rollup.npm.config.mjs b/packages/server-utils/rollup.npm.config.mjs index 7416307b5bac..f2028f2006ef 100644 --- a/packages/server-utils/rollup.npm.config.mjs +++ b/packages/server-utils/rollup.npm.config.mjs @@ -8,6 +8,7 @@ export default makeNPMConfigVariants( exports: 'named', // set preserveModules to true because we don't want to bundle everything into one file. preserveModules: true, + preserveModulesRoot: 'src', }, }, }), diff --git a/packages/server-utils/src/redis/redis-dc-subscriber.ts b/packages/server-utils/src/redis/redis-dc-subscriber.ts index 465f02f93c4c..7834fee50af4 100644 --- a/packages/server-utils/src/redis/redis-dc-subscriber.ts +++ b/packages/server-utils/src/redis/redis-dc-subscriber.ts @@ -1,3 +1,10 @@ +import { + DB_OPERATION_BATCH_SIZE, + DB_QUERY_TEXT, + DB_SYSTEM_NAME, + SERVER_ADDRESS, + SERVER_PORT, +} from '@sentry/conventions/attributes'; import type { Span } from '@sentry/core'; import { debug, @@ -19,16 +26,6 @@ export const IOREDIS_DC_CHANNEL_COMMAND = 'ioredis:command'; export const IOREDIS_DC_CHANNEL_CONNECT = 'ioredis:connect'; const ORIGIN = 'auto.db.redis.diagnostic_channel'; - -// Inlined stable semconv attribute keys — these are plain strings, no need to -// depend on @opentelemetry/semantic-conventions for them. We use the stable -// OTel names (matching `@sentry/core`'s postgresjs integration) rather than the -// deprecated `db.statement`/`db.system`/`net.peer.*` forms. -const ATTR_DB_QUERY_TEXT = 'db.query.text'; -const ATTR_DB_SYSTEM_NAME = 'db.system.name'; -const ATTR_DB_OPERATION_BATCH_SIZE = 'db.operation.batch.size'; -const ATTR_SERVER_ADDRESS = 'server.address'; -const ATTR_SERVER_PORT = 'server.port'; const DB_SYSTEM_NAME_VALUE_REDIS = 'redis'; const NOOP = (): void => {}; @@ -206,10 +203,10 @@ function setupCommandChannel( attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ORIGIN, [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'db.redis', - [ATTR_DB_SYSTEM_NAME]: DB_SYSTEM_NAME_VALUE_REDIS, - [ATTR_DB_QUERY_TEXT]: statement, - ...(data.serverAddress != null ? { [ATTR_SERVER_ADDRESS]: data.serverAddress } : {}), - ...(data.serverPort != null ? { [ATTR_SERVER_PORT]: data.serverPort } : {}), + [DB_SYSTEM_NAME]: DB_SYSTEM_NAME_VALUE_REDIS, + [DB_QUERY_TEXT]: statement, + ...(data.serverAddress != null ? { [SERVER_ADDRESS]: data.serverAddress } : {}), + ...(data.serverPort != null ? { [SERVER_PORT]: data.serverPort } : {}), }, }, span => span, @@ -250,12 +247,12 @@ function setupBatchChannel( attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ORIGIN, [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'db.redis', - [ATTR_DB_SYSTEM_NAME]: DB_SYSTEM_NAME_VALUE_REDIS, + [DB_SYSTEM_NAME]: DB_SYSTEM_NAME_VALUE_REDIS, // should only include batch size greater than 1, // or else it isn't properly considered a "batch" - ...(Number(data.batchSize) > 1 ? { [ATTR_DB_OPERATION_BATCH_SIZE]: data.batchSize } : {}), - ...(data.serverAddress != null ? { [ATTR_SERVER_ADDRESS]: data.serverAddress } : {}), - ...(data.serverPort != null ? { [ATTR_SERVER_PORT]: data.serverPort } : {}), + ...(Number(data.batchSize) > 1 ? { [DB_OPERATION_BATCH_SIZE]: data.batchSize } : {}), + ...(data.serverAddress != null ? { [SERVER_ADDRESS]: data.serverAddress } : {}), + ...(data.serverPort != null ? { [SERVER_PORT]: data.serverPort } : {}), }, }, span => span, @@ -288,9 +285,9 @@ function setupConnectChannel(tracingChannel: RedisTracingChannelFactory, channel attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ORIGIN, [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'db.redis.connect', - [ATTR_DB_SYSTEM_NAME]: DB_SYSTEM_NAME_VALUE_REDIS, - ...(data.serverAddress != null ? { [ATTR_SERVER_ADDRESS]: data.serverAddress } : {}), - ...(data.serverPort != null ? { [ATTR_SERVER_PORT]: data.serverPort } : {}), + [DB_SYSTEM_NAME]: DB_SYSTEM_NAME_VALUE_REDIS, + ...(data.serverAddress != null ? { [SERVER_ADDRESS]: data.serverAddress } : {}), + ...(data.serverPort != null ? { [SERVER_PORT]: data.serverPort } : {}), }, }, span => span, diff --git a/packages/tanstackstart-react/package.json b/packages/tanstackstart-react/package.json index f48123c849e1..2d1a728238b7 100644 --- a/packages/tanstackstart-react/package.json +++ b/packages/tanstackstart-react/package.json @@ -64,7 +64,6 @@ }, "dependencies": { "@opentelemetry/api": "^1.9.1", - "@opentelemetry/semantic-conventions": "^1.40.0", "@sentry/browser-utils": "10.57.0", "@sentry/core": "10.57.0", "@sentry/node": "10.57.0", @@ -72,6 +71,7 @@ "@sentry/vite-plugin": "^5.3.0" }, "devDependencies": { + "@sentry/conventions": "^0.11.0", "vite": "^5.4.11" }, "scripts": { diff --git a/packages/tanstackstart-react/rollup.npm.config.mjs b/packages/tanstackstart-react/rollup.npm.config.mjs index dc6e360e5941..1b587930d44c 100644 --- a/packages/tanstackstart-react/rollup.npm.config.mjs +++ b/packages/tanstackstart-react/rollup.npm.config.mjs @@ -10,6 +10,13 @@ export default [ 'src/server/index.ts', 'src/vite/index.ts', ], + packageSpecificConfig: { + output: { + // keep emitted module paths relative to `src` so the bundled `@sentry/conventions` + // (a devDependency, vendored into the build) doesn't shift the output layout + preserveModulesRoot: 'src', + }, + }, }), ), ...makeOtelLoaders('./build', 'sentry-node'), diff --git a/packages/tanstackstart-react/src/server/routeParametrization.ts b/packages/tanstackstart-react/src/server/routeParametrization.ts index bd1d1d904770..395451a1bc15 100644 --- a/packages/tanstackstart-react/src/server/routeParametrization.ts +++ b/packages/tanstackstart-react/src/server/routeParametrization.ts @@ -1,4 +1,4 @@ -import { ATTR_HTTP_ROUTE } from '@opentelemetry/semantic-conventions'; +import { HTTP_ROUTE } from '@sentry/conventions/attributes'; import { escapeStringForRegex, getActiveSpan, @@ -54,13 +54,13 @@ export function updateSpanWithRouteParametrization(method: string, pathname: str const rootSpan = getRootSpan(activeSpan); const rootSpanData = spanToJSON(rootSpan).data; - if (rootSpanData?.[ATTR_HTTP_ROUTE]) { + if (rootSpanData?.[HTTP_ROUTE]) { return; } const transactionName = `${method} ${matchedPattern}`; updateSpanName(rootSpan, transactionName); - rootSpan.setAttribute(ATTR_HTTP_ROUTE, matchedPattern); + rootSpan.setAttribute(HTTP_ROUTE, matchedPattern); rootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route'); getCurrentScope().setTransactionName(transactionName); } diff --git a/packages/typescript/tsconfig.json b/packages/typescript/tsconfig.json index 2e70d1a0c493..688c00d1ad73 100644 --- a/packages/typescript/tsconfig.json +++ b/packages/typescript/tsconfig.json @@ -18,6 +18,13 @@ "strict": true, "strictBindCallApply": false, "target": "es2020", - "noUncheckedIndexedAccess": true + "noUncheckedIndexedAccess": true, + // `@sentry/conventions` exposes its attribute keys only via the `./attributes` subpath + // export (no node10-compatible types fallback), so map it explicitly for the repo's + // `moduleResolution: "node"` type builds. Build-time only — the keys are vendored into + // the output bundles, so this never reaches published artifacts. + "paths": { + "@sentry/conventions/attributes": ["../../node_modules/@sentry/conventions/dist/attributes"] + } } } diff --git a/yarn.lock b/yarn.lock index 5f7a5fcbdbbc..29f88030819f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7578,6 +7578,11 @@ "@sentry/cli-win32-i686" "2.58.6" "@sentry/cli-win32-x64" "2.58.6" +"@sentry/conventions@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@sentry/conventions/-/conventions-0.11.0.tgz#5a324b8368dc5c141260bd8ccc684756ea3dd843" + integrity sha512-AQTAKeq9mDpOElDFSPymZTPZF/c50rk355mWTf5Y1ZxZJKKOBli5qTttskJyCxrE5ynNgN1KwcXoU5MRrMSRmQ== + "@sentry/node-cpu-profiler@^2.4.2": version "2.4.2" resolved "https://registry.yarnpkg.com/@sentry/node-cpu-profiler/-/node-cpu-profiler-2.4.2.tgz#d0ba01370545297d015df1497daf7f81e27f2ab5" From 23cd953c16dd40829100f0101de141b41e3a7333 Mon Sep 17 00:00:00 2001 From: Francesco Gringl-Novy Date: Fri, 12 Jun 2026 11:40:21 +0200 Subject: [PATCH 2/2] fixes --- packages/nitro/vite.config.ts | 1 + packages/typescript/tsconfig.json | 4 ---- vite/vite.config.ts | 10 ++++++++++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/nitro/vite.config.ts b/packages/nitro/vite.config.ts index 4c0db8cdc068..5f83f34483c3 100644 --- a/packages/nitro/vite.config.ts +++ b/packages/nitro/vite.config.ts @@ -3,6 +3,7 @@ import baseConfig from '../../vite/vite.config'; export default { ...baseConfig, test: { + ...baseConfig.test, typecheck: { enabled: true, tsconfig: './tsconfig.test.json', diff --git a/packages/typescript/tsconfig.json b/packages/typescript/tsconfig.json index 688c00d1ad73..b30d3f8887db 100644 --- a/packages/typescript/tsconfig.json +++ b/packages/typescript/tsconfig.json @@ -19,10 +19,6 @@ "strictBindCallApply": false, "target": "es2020", "noUncheckedIndexedAccess": true, - // `@sentry/conventions` exposes its attribute keys only via the `./attributes` subpath - // export (no node10-compatible types fallback), so map it explicitly for the repo's - // `moduleResolution: "node"` type builds. Build-time only — the keys are vendored into - // the output bundles, so this never reaches published artifacts. "paths": { "@sentry/conventions/attributes": ["../../node_modules/@sentry/conventions/dist/attributes"] } diff --git a/vite/vite.config.ts b/vite/vite.config.ts index c603d95fe856..7c1e01a7e66d 100644 --- a/vite/vite.config.ts +++ b/vite/vite.config.ts @@ -5,6 +5,16 @@ export default defineConfig({ __DEBUG_BUILD__: true, }, test: { + server: { + deps: { + // `@sentry/conventions` is vendored into our build output (under `build/esm/node_modules/...`) + // as ESM `.js` files. Vitest externalizes anything under a `node_modules/` path and loads it via + // native `require`, which fails on Node 18 ("ES Module shipped in a CommonJS package") because + // Node <20.19 can't `require()` ESM. Inlining makes Vitest transform it instead, so it works on + // all supported Node versions. Production is unaffected (the package resolves via its exports map). + inline: [/@sentry[/\\]conventions/], + }, + }, coverage: { enabled: true, reportsDirectory: './coverage',