From 48acf4a0fa8beb41ef8b4d7ba8c90d0978817822 Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Thu, 22 Oct 2026 13:57:49 +0200 Subject: [PATCH 1/4] fix(v10/opentelemetry): Support `@opentelemetry/api` 1.10 types Co-Authored-By: Claude Opus 5.5 --- .../node-otel-without-tracing/package.json | 2 +- packages/cloudflare/src/opentelemetry/tracer.ts | 16 +++++++++++++++- packages/deno/src/opentelemetry/tracer.ts | 16 +++++++++++++++- packages/opentelemetry/src/resource.ts | 2 ++ packages/opentelemetry/src/sampler.ts | 8 +++++--- packages/opentelemetry/src/spanExporter.ts | 10 ++++++---- packages/opentelemetry/src/tracerProvider.ts | 7 +++---- packages/opentelemetry/src/utils/mapStatus.ts | 2 +- .../src/utils/parseSpanDescription.ts | 6 +++++- yarn.lock | 6 +++--- 10 files changed, 56 insertions(+), 19 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/package.json b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/package.json index 04a6a80a500c..21beacf9fda5 100644 --- a/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/package.json +++ b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/package.json @@ -11,7 +11,7 @@ "test:assert": "pnpm test" }, "dependencies": { - "@opentelemetry/api": "1.9.0", + "@opentelemetry/api": "^1.10.0", "@opentelemetry/sdk-trace-node": "2.6.1", "@opentelemetry/exporter-trace-otlp-http": "0.214.0", "@opentelemetry/instrumentation-undici": "0.24.0", diff --git a/packages/cloudflare/src/opentelemetry/tracer.ts b/packages/cloudflare/src/opentelemetry/tracer.ts index bb83a8550588..072d6e570eca 100644 --- a/packages/cloudflare/src/opentelemetry/tracer.ts +++ b/packages/cloudflare/src/opentelemetry/tracer.ts @@ -1,6 +1,7 @@ -import type { Context, Span, SpanOptions, Tracer, TracerProvider } from '@opentelemetry/api'; +import type { Context, Link, Span, SpanOptions, Tracer, TracerProvider } from '@opentelemetry/api'; import { trace } from '@opentelemetry/api'; import { startInactiveSpan, startSpanManual } from '@sentry/core'; +import type { SpanLink } from '@sentry/core'; /** * Set up a mock OTEL tracer to allow inter-op with OpenTelemetry emitted spans. @@ -28,6 +29,7 @@ class SentryCloudflareTracer implements Tracer { public startSpan(name: string, options?: SpanOptions): Span { return startInactiveSpan({ ...options, + links: convertSpanLinkType(options?.links), name, attributes: { ...options?.attributes, @@ -57,6 +59,7 @@ class SentryCloudflareTracer implements Tracer { const spanOpts = { ...opts, + links: convertSpanLinkType(opts.links), name, attributes: { ...opts.attributes, @@ -78,3 +81,14 @@ class SentryCloudflareTracer implements Tracer { return startSpanManual(spanOpts, callback) as ReturnType; } } + +/** + * Converts OpenTelemetry span links to Sentry span links. + * + * Since `@opentelemetry/api` 1.10.0, link attribute values are typed as `unknown`. + * Sentry's `SpanLink` only accepts strings, numbers, booleans and arrays of these as attribute values, + * so OTel links do not match its type. + */ +function convertSpanLinkType(links: Link[] | undefined): SpanLink[] | undefined { + return links as SpanLink[] | undefined; +} diff --git a/packages/deno/src/opentelemetry/tracer.ts b/packages/deno/src/opentelemetry/tracer.ts index bdd86bde6a8d..5b59bb3f2177 100644 --- a/packages/deno/src/opentelemetry/tracer.ts +++ b/packages/deno/src/opentelemetry/tracer.ts @@ -1,4 +1,4 @@ -import type { Context, Span, SpanOptions, Tracer, TracerProvider } from '@opentelemetry/api'; +import type { Context, Link, Span, SpanOptions, Tracer, TracerProvider } from '@opentelemetry/api'; import { SpanKind, trace } from '@opentelemetry/api'; import { SEMANTIC_ATTRIBUTE_SENTRY_OP, @@ -6,6 +6,7 @@ import { startInactiveSpan, startSpanManual, } from '@sentry/core'; +import type { SpanLink } from '@sentry/core'; /** * Set up a mock OTEL tracer to allow inter-op with OpenTelemetry emitted spans. @@ -39,6 +40,7 @@ class SentryDenoTracer implements Tracer { return startInactiveSpan({ ...options, + links: convertSpanLinkType(options?.links), name, attributes: { ...options?.attributes, @@ -73,6 +75,7 @@ class SentryDenoTracer implements Tracer { const spanOpts = { ...opts, + links: convertSpanLinkType(opts.links), name, attributes: { ...opts.attributes, @@ -111,3 +114,14 @@ class SentryDenoTracer implements Tracer { } } } + +/** + * Converts OpenTelemetry span links to Sentry span links. + * + * Since `@opentelemetry/api` 1.10.0, link attribute values are typed as `unknown`. + * Sentry's `SpanLink` only accepts strings, numbers, booleans and arrays of these as attribute values, + * so OTel links do not match its type. + */ +function convertSpanLinkType(links: Link[] | undefined): SpanLink[] | undefined { + return links as SpanLink[] | undefined; +} diff --git a/packages/opentelemetry/src/resource.ts b/packages/opentelemetry/src/resource.ts index f93404aa9404..bc87a0d68164 100644 --- a/packages/opentelemetry/src/resource.ts +++ b/packages/opentelemetry/src/resource.ts @@ -13,6 +13,8 @@ const ATTR_TELEMETRY_SDK_NAME = 'telemetry.sdk.name'; const ATTR_TELEMETRY_SDK_VERSION = 'telemetry.sdk.version'; const SEMRESATTRS_SERVICE_NAMESPACE = 'service.namespace'; +// `AnyValue` replaces it in `@opentelemetry/api` 1.10, but does not exist in 1.9 +// eslint-disable-next-line typescript/no-deprecated type RawResourceAttribute = [string, AttributeValue | undefined]; /** diff --git a/packages/opentelemetry/src/sampler.ts b/packages/opentelemetry/src/sampler.ts index 0d7b2acabad1..96d766782f9a 100644 --- a/packages/opentelemetry/src/sampler.ts +++ b/packages/opentelemetry/src/sampler.ts @@ -1,5 +1,5 @@ /* eslint-disable complexity */ -import type { Context, Span, TraceState as TraceStateInterface } from '@opentelemetry/api'; +import type { Attributes, Context, Span, TraceState as TraceStateInterface } from '@opentelemetry/api'; import { isSpanContextValid, SpanKind, trace } from '@opentelemetry/api'; import { TraceState } from './utils/TraceState'; import type { Sampler, SamplingResult } from '@opentelemetry/sdk-trace-base'; @@ -277,7 +277,9 @@ export function wrapSamplingDecision({ }: { decision: SamplingDecision | undefined; context: Context; - spanAttributes: SpanAttributes; + // Typed as OTel `Attributes` so custom samplers can pass their `shouldSample` attributes through, + // whichever `@opentelemetry/api` version they use (1.10 widened attribute values to `AnyValue`). + spanAttributes: Attributes; sampleRand?: number; downstreamTraceSampleRate?: number; // flags for ignored streamed spans (only set for span streaming) @@ -319,7 +321,7 @@ export function wrapSamplingDecision({ return { decision, traceState }; } -function getBaseTraceState(context: Context, spanAttributes: SpanAttributes): TraceStateInterface { +function getBaseTraceState(context: Context, spanAttributes: Attributes): TraceStateInterface { const parentSpan = trace.getSpan(context); const parentContext = parentSpan?.spanContext(); diff --git a/packages/opentelemetry/src/spanExporter.ts b/packages/opentelemetry/src/spanExporter.ts index 3e72d2a1cc0f..5762cf5500cf 100644 --- a/packages/opentelemetry/src/spanExporter.ts +++ b/packages/opentelemetry/src/spanExporter.ts @@ -6,7 +6,9 @@ import { HTTP_RESPONSE_STATUS_CODE, HTTP_STATUS_CODE } from '@sentry/conventions import type { SpanAttributes, SpanJSON, + SpanLink, SpanOrigin, + TimedEvent, TraceContext, TransactionEvent, TransactionSource, @@ -222,7 +224,7 @@ export class SentrySpanExporter { }; } - const measurements = timedEventsToMeasurements(span.events); + const measurements = timedEventsToMeasurements(span.events as TimedEvent[]); if (measurements) { transactionEvent.measurements = measurements; } @@ -292,7 +294,7 @@ export function createTransactionForOtelSpan(span: ReadableSpan): TransactionEve origin, op, status: getStatusMessage(status), // As per protocol, span status is allowed to be undefined - links: convertSpanLinksForEnvelope(links), + links: convertSpanLinksForEnvelope(links as SpanLink[]), }; const statusCode = attributes[HTTP_RESPONSE_STATUS_CODE]; @@ -378,8 +380,8 @@ function createAndFinishSpanForOtelSpan(node: SpanNode, spans: SpanJSON[], sentS status: getStatusMessage(status), // As per protocol, span status is allowed to be undefined op, origin, - measurements: timedEventsToMeasurements(span.events), - links: convertSpanLinksForEnvelope(links), + measurements: timedEventsToMeasurements(span.events as TimedEvent[]), + links: convertSpanLinksForEnvelope(links as SpanLink[]), }; spans.push(spanJSON); diff --git a/packages/opentelemetry/src/tracerProvider.ts b/packages/opentelemetry/src/tracerProvider.ts index e86edd5af68a..69db7985161a 100644 --- a/packages/opentelemetry/src/tracerProvider.ts +++ b/packages/opentelemetry/src/tracerProvider.ts @@ -1,16 +1,15 @@ -import type { Tracer, TracerOptions, TracerProvider } from '@opentelemetry/api'; -import type { SpanAttributes } from '@sentry/core'; +import type { Attributes, Tracer, TracerOptions, TracerProvider } from '@opentelemetry/api'; import { SentryTracer } from './tracer'; /** * A minimal OpenTelemetry TracerProvider which creates native Sentry spans. */ export class SentryTracerProvider implements TracerProvider { - public readonly resource?: { attributes: SpanAttributes }; + public readonly resource?: { attributes: Attributes }; private readonly _tracers = new Map(); - public constructor(options: { resource?: { attributes: SpanAttributes } } = {}) { + public constructor(options: { resource?: { attributes: Attributes } } = {}) { this.resource = options.resource; } diff --git a/packages/opentelemetry/src/utils/mapStatus.ts b/packages/opentelemetry/src/utils/mapStatus.ts index 5ebd31c912c9..45608984948c 100644 --- a/packages/opentelemetry/src/utils/mapStatus.ts +++ b/packages/opentelemetry/src/utils/mapStatus.ts @@ -33,7 +33,7 @@ export const isStatusErrorMessageValid = (message: string): boolean => { * Get a Sentry span status from an otel span. */ export function mapStatus(span: AbstractSpan): SpanStatus { - const attributes = spanHasAttributes(span) ? span.attributes : {}; + const attributes = (spanHasAttributes(span) ? span.attributes : {}) as SpanAttributes; const status = spanHasStatus(span) ? span.status : undefined; if (status) { diff --git a/packages/opentelemetry/src/utils/parseSpanDescription.ts b/packages/opentelemetry/src/utils/parseSpanDescription.ts index 478a2f2ee482..8b5aadbec8b7 100644 --- a/packages/opentelemetry/src/utils/parseSpanDescription.ts +++ b/packages/opentelemetry/src/utils/parseSpanDescription.ts @@ -123,7 +123,7 @@ export function parseSpanDescription(span: AbstractSpan): SpanDescription { } const kind = getSpanKind(span); - return inferSpanData(name, attributes, kind); + return inferSpanData(name, attributes as SpanAttributes, kind); } function descriptionForDbSystem({ attributes, name }: { attributes: Attributes; name: string }): SpanDescription { @@ -154,6 +154,8 @@ function descriptionForDbSystem({ attributes, name }: { attributes: Attributes; /** Only exported for tests. */ export function descriptionForHttpMethod( { name, kind, attributes }: { name: string; attributes: Attributes; kind: SpanKind }, + // `AnyValue` replaces it in `@opentelemetry/api` 1.10, but does not exist in 1.9 + // eslint-disable-next-line typescript/no-deprecated httpMethod: AttributeValue, ): SpanDescription { const opParts = ['http']; @@ -238,6 +240,8 @@ export function descriptionForHttpMethod( }; } +// `AnyValue` replaces it in `@opentelemetry/api` 1.10, but does not exist in 1.9 +// eslint-disable-next-line typescript/no-deprecated function getGraphqlOperationNamesFromAttribute(attr: AttributeValue): string { if (Array.isArray(attr)) { // oxlint-disable-next-line typescript/require-array-sort-compare diff --git a/yarn.lock b/yarn.lock index 72a243cf40cc..4010e78d64c8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5907,9 +5907,9 @@ integrity sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg== "@opentelemetry/api@^1.3.0", "@opentelemetry/api@^1.9.0", "@opentelemetry/api@^1.9.1": - version "1.9.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.9.1.tgz#c1b0346de336ba55af2d5a7970882037baedec05" - integrity sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q== + version "1.10.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.10.0.tgz#31d2e3d36ffc6cdd5ce165251e7fa13bbad0fd8a" + integrity sha512-lMkyes8iHrG+mk6WH7X70MdCLI2RS0FwNBY1PyQ8UemaijE2m5mv5A0Niy3HtbYTFrgkUEQjFMXmSAk2hQDuPQ== "@opentelemetry/core@2.9.0", "@opentelemetry/core@^2.9.0": version "2.9.0" From 5246f824e8e3f2007c5e3120a4a3e352f362e1cf Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Thu, 22 Oct 2026 14:11:35 +0200 Subject: [PATCH 2/4] Use types derived from `Attributes` instead of deprecated `AttributeValue` --- packages/opentelemetry/src/resource.ts | 6 ++---- .../opentelemetry/src/utils/parseSpanDescription.ts | 10 +++------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/packages/opentelemetry/src/resource.ts b/packages/opentelemetry/src/resource.ts index bc87a0d68164..4ac3de6d309f 100644 --- a/packages/opentelemetry/src/resource.ts +++ b/packages/opentelemetry/src/resource.ts @@ -1,4 +1,4 @@ -import type { Attributes, AttributeValue } from '@opentelemetry/api'; +import type { Attributes } from '@opentelemetry/api'; import { SDK_INFO } from '@opentelemetry/core'; import { SERVICE_NAME, SERVICE_VERSION } from '@sentry/conventions/attributes'; import { SDK_VERSION } from '@sentry/core'; @@ -13,9 +13,7 @@ const ATTR_TELEMETRY_SDK_NAME = 'telemetry.sdk.name'; const ATTR_TELEMETRY_SDK_VERSION = 'telemetry.sdk.version'; const SEMRESATTRS_SERVICE_NAMESPACE = 'service.namespace'; -// `AnyValue` replaces it in `@opentelemetry/api` 1.10, but does not exist in 1.9 -// eslint-disable-next-line typescript/no-deprecated -type RawResourceAttribute = [string, AttributeValue | undefined]; +type RawResourceAttribute = [string, Attributes[string]]; /** * Minimal Resource implementation that satisfies the OpenTelemetry Resource interface diff --git a/packages/opentelemetry/src/utils/parseSpanDescription.ts b/packages/opentelemetry/src/utils/parseSpanDescription.ts index 8b5aadbec8b7..20c5d8d657b4 100644 --- a/packages/opentelemetry/src/utils/parseSpanDescription.ts +++ b/packages/opentelemetry/src/utils/parseSpanDescription.ts @@ -1,4 +1,4 @@ -import type { Attributes, AttributeValue } from '@opentelemetry/api'; +import type { Attributes } from '@opentelemetry/api'; import { SpanKind } from '@opentelemetry/api'; import { DB_STATEMENT, @@ -154,9 +154,7 @@ function descriptionForDbSystem({ attributes, name }: { attributes: Attributes; /** Only exported for tests. */ export function descriptionForHttpMethod( { name, kind, attributes }: { name: string; attributes: Attributes; kind: SpanKind }, - // `AnyValue` replaces it in `@opentelemetry/api` 1.10, but does not exist in 1.9 - // eslint-disable-next-line typescript/no-deprecated - httpMethod: AttributeValue, + httpMethod: NonNullable, ): SpanDescription { const opParts = ['http']; @@ -240,9 +238,7 @@ export function descriptionForHttpMethod( }; } -// `AnyValue` replaces it in `@opentelemetry/api` 1.10, but does not exist in 1.9 -// eslint-disable-next-line typescript/no-deprecated -function getGraphqlOperationNamesFromAttribute(attr: AttributeValue): string { +function getGraphqlOperationNamesFromAttribute(attr: NonNullable): string { if (Array.isArray(attr)) { // oxlint-disable-next-line typescript/require-array-sort-compare const sorted = attr.slice().sort(); From d9d3c4f1a9f2bbd138c801df96792902fc45e00e Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Fri, 23 Oct 2026 15:19:56 +0200 Subject: [PATCH 3/4] Point early-registered instrumentations at the global tracer provider --- packages/node/src/sdk/initOtel.ts | 25 ++++++++++++++++- packages/node/test/sdk/init.test.ts | 43 +++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/packages/node/src/sdk/initOtel.ts b/packages/node/src/sdk/initOtel.ts index 203d1c9568cb..6b87d1f69e17 100644 --- a/packages/node/src/sdk/initOtel.ts +++ b/packages/node/src/sdk/initOtel.ts @@ -5,6 +5,7 @@ import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; import { debug as coreDebug, hasSpanStreamingEnabled } from '@sentry/core'; import { initializeEsmLoader, + INSTRUMENTED, type NodeClient, SentryContextManager, setupOpenTelemetryLogger, @@ -82,6 +83,25 @@ function registerGlobalTracerProvider(provider: TracerProvider): boolean { return false; } +/** + * Points the instrumentations Sentry registered at the global tracer provider, which only exists + * now that Sentry registered its own. + * + * `@opentelemetry/api` >= 1.10 ships separate ESM and CJS builds, so an ESM app can load both. + * An instrumentation registered before the global provider existed keeps a tracer from the proxy + * provider of its own api copy. If Sentry registered the global provider through the other copy, + * that proxy never gets a delegate and all spans of the instrumentation are no-ops. + */ +function updateInstrumentationTracerProviders(client: NodeClient): void { + const tracerProvider = trace.getTracerProvider(); + const instrumentations = [ + ...Object.values(INSTRUMENTED), + ...(client.getOptions().openTelemetryInstrumentations || []), + ]; + + instrumentations.forEach(instrumentation => instrumentation.setTracerProvider(tracerProvider)); +} + interface AdditionalOpenTelemetryOptions { /** Additional SpanProcessor instances that should be used. */ spanProcessors?: SpanProcessor[]; @@ -174,7 +194,9 @@ export function setupOtel( }); // Register as globals - registerGlobalTracerProvider(provider); + if (registerGlobalTracerProvider(provider)) { + updateInstrumentationTracerProviders(client); + } propagation.setGlobalPropagator(new SentryPropagator()); const ctxManager = new SentryContextManager(); @@ -200,6 +222,7 @@ function setupSentryTracerProvider( // tracer provider, so setup validation doesn't skip required checks when // registration failed. setIsSetup('SentryTracerProvider'); + updateInstrumentationTracerProviders(client); propagation.setGlobalPropagator(new SentryPropagator()); diff --git a/packages/node/test/sdk/init.test.ts b/packages/node/test/sdk/init.test.ts index 95d9b9db1de9..25d7d43a09e2 100644 --- a/packages/node/test/sdk/init.test.ts +++ b/packages/node/test/sdk/init.test.ts @@ -2,6 +2,7 @@ import { trace } from '@opentelemetry/api'; import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; import type { Integration } from '@sentry/core'; import { debug, SDK_VERSION } from '@sentry/core'; +import { INSTRUMENTED } from '@sentry/node-core'; import * as SentryOpentelemetry from '@sentry/opentelemetry'; import { afterEach, beforeEach, describe, expect, it, type Mock, type MockInstance, vi } from 'vitest'; import { getClient, NodeClient, validateOpenTelemetrySetup } from '../../src/'; @@ -300,6 +301,48 @@ describe('init()', () => { global[OTEL_API_GLOBAL_KEY] = undefined; }); + it('points instrumentations registered before init at the global tracer provider', () => { + const instrumentation = { setTracerProvider: vi.fn() }; + INSTRUMENTED['Test'] = instrumentation as unknown as (typeof INSTRUMENTED)[string]; + + init({ dsn: PUBLIC_DSN }); + + expect(instrumentation.setTracerProvider).toHaveBeenCalledWith(trace.getTracerProvider()); + + delete INSTRUMENTED['Test']; + }); + + it('points `openTelemetryInstrumentations` at the global tracer provider', () => { + const instrumentation = { + setTracerProvider: vi.fn(), + setMeterProvider: vi.fn(), + setLoggerProvider: vi.fn(), + getConfig: () => ({ enabled: true }), + }; + + init({ + dsn: PUBLIC_DSN, + openTelemetryBasicTracerProvider: true, + openTelemetryInstrumentations: [instrumentation as unknown as (typeof INSTRUMENTED)[string]], + }); + + expect(instrumentation.setTracerProvider).toHaveBeenLastCalledWith(trace.getTracerProvider()); + }); + + it('does not change the tracer provider of instrumentations when global registration fails', () => { + const setGlobalSpy = vi.spyOn(trace, 'setGlobalTracerProvider').mockReturnValue(false); + vi.spyOn(debug, 'warn').mockImplementation(() => {}); + const instrumentation = { setTracerProvider: vi.fn() }; + INSTRUMENTED['Test'] = instrumentation as unknown as (typeof INSTRUMENTED)[string]; + + init({ dsn: PUBLIC_DSN }); + + expect(instrumentation.setTracerProvider).not.toHaveBeenCalled(); + + delete INSTRUMENTED['Test']; + setGlobalSpy.mockRestore(); + }); + it('does not mark SentryTracerProvider as set up when global registration fails', () => { // Simulate another OpenTelemetry tracer provider already being registered. const setGlobalSpy = vi.spyOn(trace, 'setGlobalTracerProvider').mockReturnValue(false); From ae30119f98b5c781b6fb9b5ecd8605469a657b27 Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Fri, 23 Oct 2026 17:37:38 +0200 Subject: [PATCH 4/4] Limit OpenTelemetry to api <1.10.0 and SDK <3.0.0 --- CHANGELOG.md | 10 +++++ .../deno-streamed/package.json | 2 +- .../test-applications/deno/package.json | 2 +- .../nextjs-custom-otel/package.json | 2 +- .../package.json | 2 +- .../package.json | 2 +- .../node-core-express-otel-v1/package.json | 2 +- .../package.json | 2 +- .../package.json | 2 +- .../node-core-express-otel-v2/package.json | 2 +- .../node-core-light-otlp/package.json | 2 +- .../node-otel-custom-sampler/package.json | 2 +- .../node-otel-without-tracing/package.json | 2 +- .../node-core-integration-tests/package.json | 2 +- packages/aws-serverless/package.json | 2 +- packages/cloudflare/package.json | 2 +- .../cloudflare/src/opentelemetry/tracer.ts | 16 +------ packages/deno/package.json | 2 +- packages/deno/src/opentelemetry/tracer.ts | 16 +------ packages/hono/package.json | 2 +- packages/nestjs/package.json | 2 +- packages/nextjs/package.json | 2 +- packages/node-core/package.json | 8 ++-- packages/node/package.json | 2 +- packages/node/src/sdk/initOtel.ts | 25 +---------- packages/node/test/sdk/init.test.ts | 43 ------------------- packages/opentelemetry/package.json | 4 +- packages/opentelemetry/src/resource.ts | 4 +- packages/opentelemetry/src/sampler.ts | 8 ++-- packages/opentelemetry/src/spanExporter.ts | 10 ++--- packages/opentelemetry/src/tracerProvider.ts | 7 +-- packages/opentelemetry/src/utils/mapStatus.ts | 2 +- .../src/utils/parseSpanDescription.ts | 8 ++-- packages/react-router/package.json | 2 +- packages/remix/package.json | 2 +- packages/sveltekit/package.json | 2 +- packages/tanstackstart-react/package.json | 2 +- packages/vercel-edge/package.json | 2 +- yarn.lock | 8 ++-- 39 files changed, 66 insertions(+), 153 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b11a9bd3277..2ae39263a3c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,16 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +### Important Changes + +- **fix(v10/deps): Limit `@opentelemetry/api` to `<1.10.0` and the OpenTelemetry SDK to `<3.0.0` ([#25650](https://github.com/getsentry/sentry-javascript/pull/25650))** + + `@opentelemetry/api` 1.10.0 only works with OpenTelemetry SDK v3 (`@opentelemetry/core` 3.x, `@opentelemetry/instrumentation` 0.300.x). The SDK v2 packages that v10 uses only support `@opentelemetry/api` below 1.10.0, and SDK v3 needs Node.js 22.15.0 or newer, while v10 supports Node.js 18 and newer. For this reason, v10 now limits `@opentelemetry/api` to `<1.10.0`, and the `@opentelemetry/instrumentation` and `@opentelemetry/exporter-trace-otlp-http` peer dependencies of `@sentry/node-core` to `<0.300.0`. + + If your app depends on `@opentelemetry/api` directly, use a version below 1.10.0 (for example `~1.9.1`). With 1.10.0, your app and the Sentry SDK use different copies of the API, and the OpenTelemetry calls of your app do not see the tracer provider of the Sentry SDK. + + To use `@opentelemetry/api` 1.10.0 and OpenTelemetry SDK v3, upgrade to v11 of the Sentry SDK. For the upgrade steps, see the [migration guide](https://docs.sentry.io/platforms/javascript/migration/v10-to-v11/). + ## 10.77.0 ### Important Changes diff --git a/dev-packages/e2e-tests/test-applications/deno-streamed/package.json b/dev-packages/e2e-tests/test-applications/deno-streamed/package.json index acc417cd3cd0..96b7e3995eeb 100644 --- a/dev-packages/e2e-tests/test-applications/deno-streamed/package.json +++ b/dev-packages/e2e-tests/test-applications/deno-streamed/package.json @@ -11,7 +11,7 @@ }, "dependencies": { "@sentry/deno": "file:../../packed/sentry-deno-packed.tgz", - "@opentelemetry/api": "^1.9.0", + "@opentelemetry/api": ">=1.9.0 <1.10.0", "ai": "^3.0.0", "zod": "^3.22.4" }, diff --git a/dev-packages/e2e-tests/test-applications/deno/package.json b/dev-packages/e2e-tests/test-applications/deno/package.json index d408ed2816db..d39db55e71b2 100644 --- a/dev-packages/e2e-tests/test-applications/deno/package.json +++ b/dev-packages/e2e-tests/test-applications/deno/package.json @@ -11,7 +11,7 @@ }, "dependencies": { "@sentry/deno": "file:../../packed/sentry-deno-packed.tgz", - "@opentelemetry/api": "^1.9.0", + "@opentelemetry/api": ">=1.9.0 <1.10.0", "ai": "^3.0.0", "zod": "^3.22.4" }, diff --git a/dev-packages/e2e-tests/test-applications/nextjs-custom-otel/package.json b/dev-packages/e2e-tests/test-applications/nextjs-custom-otel/package.json index e443661221e2..58680875e6ea 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-custom-otel/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-custom-otel/package.json @@ -19,7 +19,7 @@ "test:assert-webpack": "pnpm test:prod && pnpm test:dev-webpack" }, "dependencies": { - "@opentelemetry/api": "^1.9.1", + "@opentelemetry/api": ">=1.9.1 <1.10.0", "@opentelemetry/exporter-metrics-otlp-http": "^0.220.0", "@opentelemetry/exporter-trace-otlp-http": "^0.220.0", "@opentelemetry/resources": "^2.9.0", diff --git a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1-custom-sampler/package.json b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1-custom-sampler/package.json index 160edce67c56..04a2b886b46c 100644 --- a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1-custom-sampler/package.json +++ b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1-custom-sampler/package.json @@ -11,7 +11,7 @@ "test:assert": "pnpm test" }, "dependencies": { - "@opentelemetry/api": "^1.9.0", + "@opentelemetry/api": ">=1.9.0 <1.10.0", "@opentelemetry/core": "^1.30.1", "@opentelemetry/instrumentation": "^0.57.1", "@opentelemetry/instrumentation-http": "^0.57.1", diff --git a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1-sdk-node/package.json b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1-sdk-node/package.json index 0ac871787ede..8b131070acb9 100644 --- a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1-sdk-node/package.json +++ b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1-sdk-node/package.json @@ -11,7 +11,7 @@ "test:assert": "pnpm test" }, "dependencies": { - "@opentelemetry/api": "^1.9.0", + "@opentelemetry/api": ">=1.9.0 <1.10.0", "@opentelemetry/core": "^1.30.1", "@opentelemetry/instrumentation": "^0.57.2", "@opentelemetry/instrumentation-http": "^0.57.2", diff --git a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1/package.json b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1/package.json index 7cfea6cc7052..8238bbe9e461 100644 --- a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1/package.json +++ b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v1/package.json @@ -13,7 +13,7 @@ "dependencies": { "@sentry/node-core": "file:../../packed/sentry-node-core-packed.tgz", "@sentry/opentelemetry": "file:../../packed/sentry-opentelemetry-packed.tgz", - "@opentelemetry/api": "^1.9.0", + "@opentelemetry/api": ">=1.9.0 <1.10.0", "@opentelemetry/core": "^1.30.1", "@opentelemetry/instrumentation": "^0.57.1", "@opentelemetry/instrumentation-http": "^0.57.1", diff --git a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-custom-sampler/package.json b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-custom-sampler/package.json index b44b3a62911e..2a7574bdfa04 100644 --- a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-custom-sampler/package.json +++ b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-custom-sampler/package.json @@ -11,7 +11,7 @@ "test:assert": "pnpm test" }, "dependencies": { - "@opentelemetry/api": "^1.9.0", + "@opentelemetry/api": ">=1.9.0 <1.10.0", "@opentelemetry/core": "^2.6.0", "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/instrumentation-http": "^0.214.0", diff --git a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-sdk-node/package.json b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-sdk-node/package.json index 8552a7990a2d..c556dfb3a425 100644 --- a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-sdk-node/package.json +++ b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-sdk-node/package.json @@ -11,7 +11,7 @@ "test:assert": "pnpm test" }, "dependencies": { - "@opentelemetry/api": "^1.9.0", + "@opentelemetry/api": ">=1.9.0 <1.10.0", "@opentelemetry/core": "^2.6.0", "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/instrumentation-http": "^0.214.0", diff --git a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2/package.json b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2/package.json index dc57fb2568f8..dd09582bf1c5 100644 --- a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2/package.json +++ b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2/package.json @@ -13,7 +13,7 @@ "dependencies": { "@sentry/node-core": "file:../../packed/sentry-node-core-packed.tgz", "@sentry/opentelemetry": "file:../../packed/sentry-opentelemetry-packed.tgz", - "@opentelemetry/api": "^1.9.0", + "@opentelemetry/api": ">=1.9.0 <1.10.0", "@opentelemetry/core": "^2.6.0", "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/instrumentation-http": "^0.214.0", diff --git a/dev-packages/e2e-tests/test-applications/node-core-light-otlp/package.json b/dev-packages/e2e-tests/test-applications/node-core-light-otlp/package.json index ac56781130c2..9ce646280df6 100644 --- a/dev-packages/e2e-tests/test-applications/node-core-light-otlp/package.json +++ b/dev-packages/e2e-tests/test-applications/node-core-light-otlp/package.json @@ -12,7 +12,7 @@ "test:assert": "pnpm test" }, "dependencies": { - "@opentelemetry/api": "^1.9.0", + "@opentelemetry/api": ">=1.9.0 <1.10.0", "@opentelemetry/exporter-trace-otlp-http": "^0.211.0", "@opentelemetry/sdk-trace-base": "^2.5.1", "@opentelemetry/sdk-trace-node": "^2.5.1", diff --git a/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/package.json b/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/package.json index b26e3981e028..5ad3480f7b73 100644 --- a/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/package.json +++ b/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/package.json @@ -11,7 +11,7 @@ "test:assert": "pnpm test" }, "dependencies": { - "@opentelemetry/api": "^1.9.0", + "@opentelemetry/api": ">=1.9.0 <1.10.0", "@opentelemetry/sdk-trace-node": "^2.6.0", "@sentry/node": "file:../../packed/sentry-node-packed.tgz", "@sentry/opentelemetry": "file:../../packed/sentry-opentelemetry-packed.tgz", diff --git a/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/package.json b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/package.json index 21beacf9fda5..04a6a80a500c 100644 --- a/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/package.json +++ b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/package.json @@ -11,7 +11,7 @@ "test:assert": "pnpm test" }, "dependencies": { - "@opentelemetry/api": "^1.10.0", + "@opentelemetry/api": "1.9.0", "@opentelemetry/sdk-trace-node": "2.6.1", "@opentelemetry/exporter-trace-otlp-http": "0.214.0", "@opentelemetry/instrumentation-undici": "0.24.0", diff --git a/dev-packages/node-core-integration-tests/package.json b/dev-packages/node-core-integration-tests/package.json index 6287c25e2f56..923b395821b7 100644 --- a/dev-packages/node-core-integration-tests/package.json +++ b/dev-packages/node-core-integration-tests/package.json @@ -26,7 +26,7 @@ "@nestjs/common": "^11", "@nestjs/core": "^11", "@nestjs/platform-express": "^11", - "@opentelemetry/api": "^1.9.1", + "@opentelemetry/api": ">=1.9.1 <1.10.0", "@opentelemetry/core": "^2.9.0", "@opentelemetry/instrumentation": "^0.220.0", "@opentelemetry/instrumentation-http": "0.220.0", diff --git a/packages/aws-serverless/package.json b/packages/aws-serverless/package.json index d5e1b00f1dd3..907ab182d19f 100644 --- a/packages/aws-serverless/package.json +++ b/packages/aws-serverless/package.json @@ -70,7 +70,7 @@ "tag": "v10" }, "dependencies": { - "@opentelemetry/api": "^1.9.1", + "@opentelemetry/api": ">=1.9.1 <1.10.0", "@opentelemetry/instrumentation": "^0.220.0", "@sentry/conventions": "^0.16.0", "@sentry/core": "10.76.2", diff --git a/packages/cloudflare/package.json b/packages/cloudflare/package.json index cf478b6d430d..a84af5dff415 100644 --- a/packages/cloudflare/package.json +++ b/packages/cloudflare/package.json @@ -70,7 +70,7 @@ "tag": "v10" }, "dependencies": { - "@opentelemetry/api": "^1.9.1", + "@opentelemetry/api": ">=1.9.1 <1.10.0", "@sentry/core": "10.76.2", "@sentry/server-utils": "10.76.2", "magic-string": "~0.30.21" diff --git a/packages/cloudflare/src/opentelemetry/tracer.ts b/packages/cloudflare/src/opentelemetry/tracer.ts index 072d6e570eca..bb83a8550588 100644 --- a/packages/cloudflare/src/opentelemetry/tracer.ts +++ b/packages/cloudflare/src/opentelemetry/tracer.ts @@ -1,7 +1,6 @@ -import type { Context, Link, Span, SpanOptions, Tracer, TracerProvider } from '@opentelemetry/api'; +import type { Context, Span, SpanOptions, Tracer, TracerProvider } from '@opentelemetry/api'; import { trace } from '@opentelemetry/api'; import { startInactiveSpan, startSpanManual } from '@sentry/core'; -import type { SpanLink } from '@sentry/core'; /** * Set up a mock OTEL tracer to allow inter-op with OpenTelemetry emitted spans. @@ -29,7 +28,6 @@ class SentryCloudflareTracer implements Tracer { public startSpan(name: string, options?: SpanOptions): Span { return startInactiveSpan({ ...options, - links: convertSpanLinkType(options?.links), name, attributes: { ...options?.attributes, @@ -59,7 +57,6 @@ class SentryCloudflareTracer implements Tracer { const spanOpts = { ...opts, - links: convertSpanLinkType(opts.links), name, attributes: { ...opts.attributes, @@ -81,14 +78,3 @@ class SentryCloudflareTracer implements Tracer { return startSpanManual(spanOpts, callback) as ReturnType; } } - -/** - * Converts OpenTelemetry span links to Sentry span links. - * - * Since `@opentelemetry/api` 1.10.0, link attribute values are typed as `unknown`. - * Sentry's `SpanLink` only accepts strings, numbers, booleans and arrays of these as attribute values, - * so OTel links do not match its type. - */ -function convertSpanLinkType(links: Link[] | undefined): SpanLink[] | undefined { - return links as SpanLink[] | undefined; -} diff --git a/packages/deno/package.json b/packages/deno/package.json index 44057e7e020c..c490e710a030 100644 --- a/packages/deno/package.json +++ b/packages/deno/package.json @@ -28,7 +28,7 @@ "/build" ], "dependencies": { - "@opentelemetry/api": "^1.9.1", + "@opentelemetry/api": ">=1.9.1 <1.10.0", "@sentry/core": "10.76.2", "@sentry/server-utils": "10.76.2" }, diff --git a/packages/deno/src/opentelemetry/tracer.ts b/packages/deno/src/opentelemetry/tracer.ts index 5b59bb3f2177..bdd86bde6a8d 100644 --- a/packages/deno/src/opentelemetry/tracer.ts +++ b/packages/deno/src/opentelemetry/tracer.ts @@ -1,4 +1,4 @@ -import type { Context, Link, Span, SpanOptions, Tracer, TracerProvider } from '@opentelemetry/api'; +import type { Context, Span, SpanOptions, Tracer, TracerProvider } from '@opentelemetry/api'; import { SpanKind, trace } from '@opentelemetry/api'; import { SEMANTIC_ATTRIBUTE_SENTRY_OP, @@ -6,7 +6,6 @@ import { startInactiveSpan, startSpanManual, } from '@sentry/core'; -import type { SpanLink } from '@sentry/core'; /** * Set up a mock OTEL tracer to allow inter-op with OpenTelemetry emitted spans. @@ -40,7 +39,6 @@ class SentryDenoTracer implements Tracer { return startInactiveSpan({ ...options, - links: convertSpanLinkType(options?.links), name, attributes: { ...options?.attributes, @@ -75,7 +73,6 @@ class SentryDenoTracer implements Tracer { const spanOpts = { ...opts, - links: convertSpanLinkType(opts.links), name, attributes: { ...opts.attributes, @@ -114,14 +111,3 @@ class SentryDenoTracer implements Tracer { } } } - -/** - * Converts OpenTelemetry span links to Sentry span links. - * - * Since `@opentelemetry/api` 1.10.0, link attribute values are typed as `unknown`. - * Sentry's `SpanLink` only accepts strings, numbers, booleans and arrays of these as attribute values, - * so OTel links do not match its type. - */ -function convertSpanLinkType(links: Link[] | undefined): SpanLink[] | undefined { - return links as SpanLink[] | undefined; -} diff --git a/packages/hono/package.json b/packages/hono/package.json index ae10a7a5fee1..b2f8c1b3d4e7 100644 --- a/packages/hono/package.json +++ b/packages/hono/package.json @@ -92,7 +92,7 @@ "tag": "v10" }, "dependencies": { - "@opentelemetry/api": "^1.9.1", + "@opentelemetry/api": ">=1.9.1 <1.10.0", "@sentry/core": "10.76.2", "@sentry/conventions": "^0.16.0" }, diff --git a/packages/nestjs/package.json b/packages/nestjs/package.json index dd91ec5d094a..14aea3b1b5d1 100644 --- a/packages/nestjs/package.json +++ b/packages/nestjs/package.json @@ -45,7 +45,7 @@ "tag": "v10" }, "dependencies": { - "@opentelemetry/api": "^1.9.1", + "@opentelemetry/api": ">=1.9.1 <1.10.0", "@opentelemetry/instrumentation": "^0.220.0", "@sentry/conventions": "^0.16.0", "@sentry/core": "10.76.2", diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index fada41ca3ab5..76e28eabcb12 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -86,7 +86,7 @@ "tag": "v10" }, "dependencies": { - "@opentelemetry/api": "^1.9.1", + "@opentelemetry/api": ">=1.9.1 <1.10.0", "@rollup/plugin-commonjs": "28.0.1", "@sentry/browser-utils": "10.76.2", "@sentry/bundler-plugins": "10.76.2", diff --git a/packages/node-core/package.json b/packages/node-core/package.json index 0028ee930bc7..f019a8c8e9eb 100644 --- a/packages/node-core/package.json +++ b/packages/node-core/package.json @@ -78,11 +78,11 @@ "tag": "v10" }, "peerDependencies": { - "@opentelemetry/api": "^1.9.0", + "@opentelemetry/api": ">=1.9.0 <1.10.0", "@opentelemetry/core": "^1.30.1 || ^2.1.0", - "@opentelemetry/instrumentation": ">=0.57.1 <1", + "@opentelemetry/instrumentation": ">=0.57.1 <0.300.0", "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0", - "@opentelemetry/exporter-trace-otlp-http": ">=0.57.0 <1" + "@opentelemetry/exporter-trace-otlp-http": ">=0.57.0 <0.300.0" }, "peerDependenciesMeta": { "@opentelemetry/api": { @@ -108,7 +108,7 @@ "import-in-the-middle": "^3.0.0" }, "devDependencies": { - "@opentelemetry/api": "^1.9.1", + "@opentelemetry/api": ">=1.9.1 <1.10.0", "@opentelemetry/core": "^2.9.0", "@opentelemetry/exporter-trace-otlp-http": "^0.220.0", "@opentelemetry/instrumentation": "^0.220.0", diff --git a/packages/node/package.json b/packages/node/package.json index 35e45952fb7f..e227dd2b0673 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -66,7 +66,7 @@ "tag": "v10" }, "dependencies": { - "@opentelemetry/api": "^1.9.1", + "@opentelemetry/api": ">=1.9.1 <1.10.0", "@opentelemetry/instrumentation": "^0.220.0", "@opentelemetry/sdk-trace-base": "^2.9.0", "@sentry/conventions": "^0.16.0", diff --git a/packages/node/src/sdk/initOtel.ts b/packages/node/src/sdk/initOtel.ts index 6b87d1f69e17..203d1c9568cb 100644 --- a/packages/node/src/sdk/initOtel.ts +++ b/packages/node/src/sdk/initOtel.ts @@ -5,7 +5,6 @@ import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; import { debug as coreDebug, hasSpanStreamingEnabled } from '@sentry/core'; import { initializeEsmLoader, - INSTRUMENTED, type NodeClient, SentryContextManager, setupOpenTelemetryLogger, @@ -83,25 +82,6 @@ function registerGlobalTracerProvider(provider: TracerProvider): boolean { return false; } -/** - * Points the instrumentations Sentry registered at the global tracer provider, which only exists - * now that Sentry registered its own. - * - * `@opentelemetry/api` >= 1.10 ships separate ESM and CJS builds, so an ESM app can load both. - * An instrumentation registered before the global provider existed keeps a tracer from the proxy - * provider of its own api copy. If Sentry registered the global provider through the other copy, - * that proxy never gets a delegate and all spans of the instrumentation are no-ops. - */ -function updateInstrumentationTracerProviders(client: NodeClient): void { - const tracerProvider = trace.getTracerProvider(); - const instrumentations = [ - ...Object.values(INSTRUMENTED), - ...(client.getOptions().openTelemetryInstrumentations || []), - ]; - - instrumentations.forEach(instrumentation => instrumentation.setTracerProvider(tracerProvider)); -} - interface AdditionalOpenTelemetryOptions { /** Additional SpanProcessor instances that should be used. */ spanProcessors?: SpanProcessor[]; @@ -194,9 +174,7 @@ export function setupOtel( }); // Register as globals - if (registerGlobalTracerProvider(provider)) { - updateInstrumentationTracerProviders(client); - } + registerGlobalTracerProvider(provider); propagation.setGlobalPropagator(new SentryPropagator()); const ctxManager = new SentryContextManager(); @@ -222,7 +200,6 @@ function setupSentryTracerProvider( // tracer provider, so setup validation doesn't skip required checks when // registration failed. setIsSetup('SentryTracerProvider'); - updateInstrumentationTracerProviders(client); propagation.setGlobalPropagator(new SentryPropagator()); diff --git a/packages/node/test/sdk/init.test.ts b/packages/node/test/sdk/init.test.ts index 25d7d43a09e2..95d9b9db1de9 100644 --- a/packages/node/test/sdk/init.test.ts +++ b/packages/node/test/sdk/init.test.ts @@ -2,7 +2,6 @@ import { trace } from '@opentelemetry/api'; import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; import type { Integration } from '@sentry/core'; import { debug, SDK_VERSION } from '@sentry/core'; -import { INSTRUMENTED } from '@sentry/node-core'; import * as SentryOpentelemetry from '@sentry/opentelemetry'; import { afterEach, beforeEach, describe, expect, it, type Mock, type MockInstance, vi } from 'vitest'; import { getClient, NodeClient, validateOpenTelemetrySetup } from '../../src/'; @@ -301,48 +300,6 @@ describe('init()', () => { global[OTEL_API_GLOBAL_KEY] = undefined; }); - it('points instrumentations registered before init at the global tracer provider', () => { - const instrumentation = { setTracerProvider: vi.fn() }; - INSTRUMENTED['Test'] = instrumentation as unknown as (typeof INSTRUMENTED)[string]; - - init({ dsn: PUBLIC_DSN }); - - expect(instrumentation.setTracerProvider).toHaveBeenCalledWith(trace.getTracerProvider()); - - delete INSTRUMENTED['Test']; - }); - - it('points `openTelemetryInstrumentations` at the global tracer provider', () => { - const instrumentation = { - setTracerProvider: vi.fn(), - setMeterProvider: vi.fn(), - setLoggerProvider: vi.fn(), - getConfig: () => ({ enabled: true }), - }; - - init({ - dsn: PUBLIC_DSN, - openTelemetryBasicTracerProvider: true, - openTelemetryInstrumentations: [instrumentation as unknown as (typeof INSTRUMENTED)[string]], - }); - - expect(instrumentation.setTracerProvider).toHaveBeenLastCalledWith(trace.getTracerProvider()); - }); - - it('does not change the tracer provider of instrumentations when global registration fails', () => { - const setGlobalSpy = vi.spyOn(trace, 'setGlobalTracerProvider').mockReturnValue(false); - vi.spyOn(debug, 'warn').mockImplementation(() => {}); - const instrumentation = { setTracerProvider: vi.fn() }; - INSTRUMENTED['Test'] = instrumentation as unknown as (typeof INSTRUMENTED)[string]; - - init({ dsn: PUBLIC_DSN }); - - expect(instrumentation.setTracerProvider).not.toHaveBeenCalled(); - - delete INSTRUMENTED['Test']; - setGlobalSpy.mockRestore(); - }); - it('does not mark SentryTracerProvider as set up when global registration fails', () => { // Simulate another OpenTelemetry tracer provider already being registered. const setGlobalSpy = vi.spyOn(trace, 'setGlobalTracerProvider').mockReturnValue(false); diff --git a/packages/opentelemetry/package.json b/packages/opentelemetry/package.json index 5bcf3d43989c..045b14625e81 100644 --- a/packages/opentelemetry/package.json +++ b/packages/opentelemetry/package.json @@ -53,12 +53,12 @@ "@sentry/core": "10.76.2" }, "peerDependencies": { - "@opentelemetry/api": "^1.9.0", + "@opentelemetry/api": ">=1.9.0 <1.10.0", "@opentelemetry/core": "^1.30.1 || ^2.1.0", "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0" }, "devDependencies": { - "@opentelemetry/api": "^1.9.1", + "@opentelemetry/api": ">=1.9.1 <1.10.0", "@opentelemetry/core": "^2.9.0", "@opentelemetry/sdk-trace-base": "^2.9.0" }, diff --git a/packages/opentelemetry/src/resource.ts b/packages/opentelemetry/src/resource.ts index 4ac3de6d309f..f93404aa9404 100644 --- a/packages/opentelemetry/src/resource.ts +++ b/packages/opentelemetry/src/resource.ts @@ -1,4 +1,4 @@ -import type { Attributes } from '@opentelemetry/api'; +import type { Attributes, AttributeValue } from '@opentelemetry/api'; import { SDK_INFO } from '@opentelemetry/core'; import { SERVICE_NAME, SERVICE_VERSION } from '@sentry/conventions/attributes'; import { SDK_VERSION } from '@sentry/core'; @@ -13,7 +13,7 @@ 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, Attributes[string]]; +type RawResourceAttribute = [string, AttributeValue | undefined]; /** * Minimal Resource implementation that satisfies the OpenTelemetry Resource interface diff --git a/packages/opentelemetry/src/sampler.ts b/packages/opentelemetry/src/sampler.ts index 96d766782f9a..0d7b2acabad1 100644 --- a/packages/opentelemetry/src/sampler.ts +++ b/packages/opentelemetry/src/sampler.ts @@ -1,5 +1,5 @@ /* eslint-disable complexity */ -import type { Attributes, Context, Span, TraceState as TraceStateInterface } from '@opentelemetry/api'; +import type { Context, Span, TraceState as TraceStateInterface } from '@opentelemetry/api'; import { isSpanContextValid, SpanKind, trace } from '@opentelemetry/api'; import { TraceState } from './utils/TraceState'; import type { Sampler, SamplingResult } from '@opentelemetry/sdk-trace-base'; @@ -277,9 +277,7 @@ export function wrapSamplingDecision({ }: { decision: SamplingDecision | undefined; context: Context; - // Typed as OTel `Attributes` so custom samplers can pass their `shouldSample` attributes through, - // whichever `@opentelemetry/api` version they use (1.10 widened attribute values to `AnyValue`). - spanAttributes: Attributes; + spanAttributes: SpanAttributes; sampleRand?: number; downstreamTraceSampleRate?: number; // flags for ignored streamed spans (only set for span streaming) @@ -321,7 +319,7 @@ export function wrapSamplingDecision({ return { decision, traceState }; } -function getBaseTraceState(context: Context, spanAttributes: Attributes): TraceStateInterface { +function getBaseTraceState(context: Context, spanAttributes: SpanAttributes): TraceStateInterface { const parentSpan = trace.getSpan(context); const parentContext = parentSpan?.spanContext(); diff --git a/packages/opentelemetry/src/spanExporter.ts b/packages/opentelemetry/src/spanExporter.ts index 5762cf5500cf..3e72d2a1cc0f 100644 --- a/packages/opentelemetry/src/spanExporter.ts +++ b/packages/opentelemetry/src/spanExporter.ts @@ -6,9 +6,7 @@ import { HTTP_RESPONSE_STATUS_CODE, HTTP_STATUS_CODE } from '@sentry/conventions import type { SpanAttributes, SpanJSON, - SpanLink, SpanOrigin, - TimedEvent, TraceContext, TransactionEvent, TransactionSource, @@ -224,7 +222,7 @@ export class SentrySpanExporter { }; } - const measurements = timedEventsToMeasurements(span.events as TimedEvent[]); + const measurements = timedEventsToMeasurements(span.events); if (measurements) { transactionEvent.measurements = measurements; } @@ -294,7 +292,7 @@ export function createTransactionForOtelSpan(span: ReadableSpan): TransactionEve origin, op, status: getStatusMessage(status), // As per protocol, span status is allowed to be undefined - links: convertSpanLinksForEnvelope(links as SpanLink[]), + links: convertSpanLinksForEnvelope(links), }; const statusCode = attributes[HTTP_RESPONSE_STATUS_CODE]; @@ -380,8 +378,8 @@ function createAndFinishSpanForOtelSpan(node: SpanNode, spans: SpanJSON[], sentS status: getStatusMessage(status), // As per protocol, span status is allowed to be undefined op, origin, - measurements: timedEventsToMeasurements(span.events as TimedEvent[]), - links: convertSpanLinksForEnvelope(links as SpanLink[]), + measurements: timedEventsToMeasurements(span.events), + links: convertSpanLinksForEnvelope(links), }; spans.push(spanJSON); diff --git a/packages/opentelemetry/src/tracerProvider.ts b/packages/opentelemetry/src/tracerProvider.ts index 69db7985161a..e86edd5af68a 100644 --- a/packages/opentelemetry/src/tracerProvider.ts +++ b/packages/opentelemetry/src/tracerProvider.ts @@ -1,15 +1,16 @@ -import type { Attributes, Tracer, TracerOptions, TracerProvider } from '@opentelemetry/api'; +import type { Tracer, TracerOptions, TracerProvider } from '@opentelemetry/api'; +import type { SpanAttributes } from '@sentry/core'; import { SentryTracer } from './tracer'; /** * A minimal OpenTelemetry TracerProvider which creates native Sentry spans. */ export class SentryTracerProvider implements TracerProvider { - public readonly resource?: { attributes: Attributes }; + public readonly resource?: { attributes: SpanAttributes }; private readonly _tracers = new Map(); - public constructor(options: { resource?: { attributes: Attributes } } = {}) { + public constructor(options: { resource?: { attributes: SpanAttributes } } = {}) { this.resource = options.resource; } diff --git a/packages/opentelemetry/src/utils/mapStatus.ts b/packages/opentelemetry/src/utils/mapStatus.ts index 45608984948c..5ebd31c912c9 100644 --- a/packages/opentelemetry/src/utils/mapStatus.ts +++ b/packages/opentelemetry/src/utils/mapStatus.ts @@ -33,7 +33,7 @@ export const isStatusErrorMessageValid = (message: string): boolean => { * Get a Sentry span status from an otel span. */ export function mapStatus(span: AbstractSpan): SpanStatus { - const attributes = (spanHasAttributes(span) ? span.attributes : {}) as SpanAttributes; + const attributes = spanHasAttributes(span) ? span.attributes : {}; const status = spanHasStatus(span) ? span.status : undefined; if (status) { diff --git a/packages/opentelemetry/src/utils/parseSpanDescription.ts b/packages/opentelemetry/src/utils/parseSpanDescription.ts index 20c5d8d657b4..478a2f2ee482 100644 --- a/packages/opentelemetry/src/utils/parseSpanDescription.ts +++ b/packages/opentelemetry/src/utils/parseSpanDescription.ts @@ -1,4 +1,4 @@ -import type { Attributes } from '@opentelemetry/api'; +import type { Attributes, AttributeValue } from '@opentelemetry/api'; import { SpanKind } from '@opentelemetry/api'; import { DB_STATEMENT, @@ -123,7 +123,7 @@ export function parseSpanDescription(span: AbstractSpan): SpanDescription { } const kind = getSpanKind(span); - return inferSpanData(name, attributes as SpanAttributes, kind); + return inferSpanData(name, attributes, kind); } function descriptionForDbSystem({ attributes, name }: { attributes: Attributes; name: string }): SpanDescription { @@ -154,7 +154,7 @@ function descriptionForDbSystem({ attributes, name }: { attributes: Attributes; /** Only exported for tests. */ export function descriptionForHttpMethod( { name, kind, attributes }: { name: string; attributes: Attributes; kind: SpanKind }, - httpMethod: NonNullable, + httpMethod: AttributeValue, ): SpanDescription { const opParts = ['http']; @@ -238,7 +238,7 @@ export function descriptionForHttpMethod( }; } -function getGraphqlOperationNamesFromAttribute(attr: NonNullable): string { +function getGraphqlOperationNamesFromAttribute(attr: AttributeValue): string { if (Array.isArray(attr)) { // oxlint-disable-next-line typescript/require-array-sort-compare const sorted = attr.slice().sort(); diff --git a/packages/react-router/package.json b/packages/react-router/package.json index 0b2070ec7446..7b8fde4cf97d 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -46,7 +46,7 @@ "tag": "v10" }, "dependencies": { - "@opentelemetry/api": "^1.9.1", + "@opentelemetry/api": ">=1.9.1 <1.10.0", "@opentelemetry/instrumentation": "^0.220.0", "@sentry/browser": "10.76.2", "@sentry/cli": "^2.58.6", diff --git a/packages/remix/package.json b/packages/remix/package.json index 5a63db343900..9d14943ec359 100644 --- a/packages/remix/package.json +++ b/packages/remix/package.json @@ -65,7 +65,7 @@ "tag": "v10" }, "dependencies": { - "@opentelemetry/api": "^1.9.1", + "@opentelemetry/api": ">=1.9.1 <1.10.0", "@opentelemetry/instrumentation": "^0.220.0", "@remix-run/router": "^1.23.3", "@sentry/cli": "^2.58.6", diff --git a/packages/sveltekit/package.json b/packages/sveltekit/package.json index 3996d11b2d48..3c38fd4ed9c7 100644 --- a/packages/sveltekit/package.json +++ b/packages/sveltekit/package.json @@ -62,7 +62,7 @@ } }, "dependencies": { - "@opentelemetry/api": "^1.9.1", + "@opentelemetry/api": ">=1.9.1 <1.10.0", "@sentry/cloudflare": "10.76.2", "@sentry/core": "10.76.2", "@sentry/conventions": "^0.16.0", diff --git a/packages/tanstackstart-react/package.json b/packages/tanstackstart-react/package.json index f9dba8bd28ea..358f588eec8f 100644 --- a/packages/tanstackstart-react/package.json +++ b/packages/tanstackstart-react/package.json @@ -64,7 +64,7 @@ "tag": "v10" }, "dependencies": { - "@opentelemetry/api": "^1.9.1", + "@opentelemetry/api": ">=1.9.1 <1.10.0", "@sentry/browser-utils": "10.76.2", "@sentry/conventions": "^0.16.0", "@sentry/core": "10.76.2", diff --git a/packages/vercel-edge/package.json b/packages/vercel-edge/package.json index 447bd62458d3..7264a8a96840 100644 --- a/packages/vercel-edge/package.json +++ b/packages/vercel-edge/package.json @@ -40,7 +40,7 @@ "tag": "v10" }, "dependencies": { - "@opentelemetry/api": "^1.9.1", + "@opentelemetry/api": ">=1.9.1 <1.10.0", "@sentry/core": "10.76.2" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index 4010e78d64c8..05db0a36ef66 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5906,10 +5906,10 @@ resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.9.0.tgz#d03eba68273dc0f7509e2a3d5cba21eae10379fe" integrity sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg== -"@opentelemetry/api@^1.3.0", "@opentelemetry/api@^1.9.0", "@opentelemetry/api@^1.9.1": - version "1.10.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.10.0.tgz#31d2e3d36ffc6cdd5ce165251e7fa13bbad0fd8a" - integrity sha512-lMkyes8iHrG+mk6WH7X70MdCLI2RS0FwNBY1PyQ8UemaijE2m5mv5A0Niy3HtbYTFrgkUEQjFMXmSAk2hQDuPQ== +"@opentelemetry/api@>=1.9.1 <1.10.0", "@opentelemetry/api@^1.3.0", "@opentelemetry/api@^1.9.0": + version "1.9.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.9.1.tgz#c1b0346de336ba55af2d5a7970882037baedec05" + integrity sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q== "@opentelemetry/core@2.9.0", "@opentelemetry/core@^2.9.0": version "2.9.0"