diff --git a/MIGRATION.md b/MIGRATION.md index 06106a60e230..3c6f5561b25e 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -482,7 +482,8 @@ Sentry.init({ - `getTraceContextForScope` was removed. Scope-to-trace-context resolution now goes through the shared core implementation. - `OpenTelemetryServerRuntimeOptions` was removed. - The `@opentelemetry/core` peer dependency was removed; its APIs are now vendored internally. -- OpenTelemetry resources are no longer collected, and `contexts.otel.resource` was dropped from events. +- `getSentryResource` was removed. +- OpenTelemetry resources are no longer collected, and `contexts.otel.resource` was dropped from events. As a result, the `OTEL_SERVICE_NAME` and `OTEL_RESOURCE_ATTRIBUTES` environment variables are no longer read by the SDK. ### `@sentry/core` span attributes diff --git a/dev-packages/e2e-tests/test-applications/astro-4/tests/tracing.dynamic.test.ts b/dev-packages/e2e-tests/test-applications/astro-4/tests/tracing.dynamic.test.ts index 6eff51ea9829..6c84e505f023 100644 --- a/dev-packages/e2e-tests/test-applications/astro-4/tests/tracing.dynamic.test.ts +++ b/dev-packages/e2e-tests/test-applications/astro-4/tests/tracing.dynamic.test.ts @@ -70,7 +70,6 @@ test.describe('tracing in dynamically rendered (ssr) routes', () => { culture: expect.any(Object), device: expect.any(Object), os: expect.any(Object), - otel: expect.any(Object), runtime: expect.any(Object), trace: { data: { diff --git a/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.dynamic.test.ts b/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.dynamic.test.ts index 2b9d7b27750f..26dbcb223683 100644 --- a/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.dynamic.test.ts +++ b/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.dynamic.test.ts @@ -71,7 +71,6 @@ test.describe('tracing in dynamically rendered (ssr) routes', () => { culture: expect.any(Object), device: expect.any(Object), os: expect.any(Object), - otel: expect.any(Object), runtime: expect.any(Object), trace: { data: { diff --git a/dev-packages/e2e-tests/test-applications/astro-6/tests/tracing.dynamic.test.ts b/dev-packages/e2e-tests/test-applications/astro-6/tests/tracing.dynamic.test.ts index f0bf78387f78..a8411e716a27 100644 --- a/dev-packages/e2e-tests/test-applications/astro-6/tests/tracing.dynamic.test.ts +++ b/dev-packages/e2e-tests/test-applications/astro-6/tests/tracing.dynamic.test.ts @@ -71,7 +71,6 @@ test.describe('tracing in dynamically rendered (ssr) routes', () => { culture: expect.any(Object), device: expect.any(Object), os: expect.any(Object), - otel: expect.any(Object), runtime: expect.any(Object), trace: { data: { diff --git a/dev-packages/e2e-tests/test-applications/astro-7/tests/tracing.dynamic.test.ts b/dev-packages/e2e-tests/test-applications/astro-7/tests/tracing.dynamic.test.ts index 97a451094932..0865b6afe17b 100644 --- a/dev-packages/e2e-tests/test-applications/astro-7/tests/tracing.dynamic.test.ts +++ b/dev-packages/e2e-tests/test-applications/astro-7/tests/tracing.dynamic.test.ts @@ -68,7 +68,6 @@ test.describe('tracing in dynamically rendered (ssr) routes', () => { culture: expect.any(Object), device: expect.any(Object), os: expect.any(Object), - otel: expect.any(Object), runtime: expect.any(Object), trace: { data: { diff --git a/packages/node/src/sdk/initOtel.ts b/packages/node/src/sdk/initOtel.ts index 3107449582c3..5213fc37bbab 100644 --- a/packages/node/src/sdk/initOtel.ts +++ b/packages/node/src/sdk/initOtel.ts @@ -6,7 +6,6 @@ import type { NodeClient } from './client'; import { applyOtelSpanData, backfillStreamedSpanDataFromOtel, - getSentryResource, SentryPropagator, SentryTracerProvider, } from '@sentry/opentelemetry'; @@ -123,7 +122,7 @@ function getPreloadMethods(integrationNames?: string[]): ((() => void) & { id: s /** Just exported for tests. */ export function setupOtel(client: NodeClient): SentryTracerProvider | undefined { - const provider = new SentryTracerProvider({ resource: getSentryResource('node') }); + const provider = new SentryTracerProvider(); if (!registerGlobalTracerProvider(provider)) { DEBUG_BUILD && @@ -143,19 +142,5 @@ export function setupOtel(client: NodeClient): SentryTracerProvider | undefined client.on('preprocessSpan', backfillStreamedSpanDataFromOtel); } - client.on('preprocessEvent', event => { - if (event.type !== 'transaction') { - return; - } - - event.contexts = { - ...event.contexts, - otel: { - resource: provider.resource?.attributes, - ...event.contexts?.otel, - }, - }; - }); - return provider; } diff --git a/packages/node/test/integration/transactions.test.ts b/packages/node/test/integration/transactions.test.ts index 572132fb6244..40feb7cc96ec 100644 --- a/packages/node/test/integration/transactions.test.ts +++ b/packages/node/test/integration/transactions.test.ts @@ -72,17 +72,6 @@ describe('Integration | Transactions', () => { { message: 'test breadcrumb 3', timestamp: 123456 }, ]); - expect(transaction.contexts?.otel).toEqual({ - resource: { - 'service.name': 'node', - 'service.namespace': 'sentry', - 'service.version': expect.any(String), - 'telemetry.sdk.language': 'nodejs', - 'telemetry.sdk.name': 'opentelemetry', - 'telemetry.sdk.version': expect.any(String), - }, - }); - expect(transaction.contexts?.trace).toEqual({ data: { 'sentry.op': 'test op', diff --git a/packages/opentelemetry/src/index.ts b/packages/opentelemetry/src/index.ts index f17239a2e3b3..4a29e295a8f1 100644 --- a/packages/opentelemetry/src/index.ts +++ b/packages/opentelemetry/src/index.ts @@ -7,8 +7,6 @@ export { applyOtelSpanData } from './applyOtelSpanData'; export { backfillStreamedSpanDataFromOtel } from './utils/backfillStreamedSpanData'; export { SentryTracerProvider } from './tracerProvider'; -export { getSentryResource } from './resource'; - export { type AsyncLocalStorageLookup } from './asyncLocalStorageContextManager'; export { setOpenTelemetryContextAsyncContextStrategy } from './asyncContextStrategy'; diff --git a/packages/opentelemetry/src/resource.ts b/packages/opentelemetry/src/resource.ts deleted file mode 100644 index 6500528502b0..000000000000 --- a/packages/opentelemetry/src/resource.ts +++ /dev/null @@ -1,101 +0,0 @@ -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'; - -// These resource attributes are not 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. -// This is OTEL-specific and not relevant for Sentry, and will eventually go away. -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]; - -/** - * Minimal Resource implementation that satisfies the OpenTelemetry Resource interface. - */ -class SentryResource { - private _attributes: Attributes; - - public constructor(attributes: Attributes) { - this._attributes = attributes; - } - - public get attributes(): Attributes { - return this._attributes; - } - - public merge(other: SentryResource | null): SentryResource { - if (!other) { - return this; - } - return new SentryResource({ ...this._attributes, ...other.attributes }); - } - - public getRawAttributes(): RawResourceAttribute[] { - return Object.entries(this._attributes); - } -} - -/** - * Parses `OTEL_RESOURCE_ATTRIBUTES` env var (comma-separated `key=value` pairs). - * Values are URL-decoded per the OTel spec. - */ -function parseOtelResourceAttributes(raw: string | undefined): Attributes { - if (!raw) { - return {}; - } - const result: Attributes = {}; - for (const pair of raw.split(',')) { - const eq = pair.indexOf('='); - if (eq === -1) { - continue; - } - const key = pair.substring(0, eq).trim(); - const value = pair.substring(eq + 1).trim(); - if (key) { - try { - result[key] = decodeURIComponent(value); - } catch { - result[key] = value; - } - } - } - return result; -} - -/** - * Returns a Resource for use in Sentry's OpenTelemetry TracerProvider setup. - * - * Combines the default OTel SDK telemetry attributes with Sentry-specific - * service attributes, equivalent to what was previously done via: - * `defaultResource().merge(resourceFromAttributes({ ... }))` - * - * Respects OTEL_SERVICE_NAME and OTEL_RESOURCE_ATTRIBUTES environment variables - * per the OpenTelemetry specification. - */ -export function getSentryResource(serviceNameFallback: string): SentryResource { - const env = typeof process !== 'undefined' ? process.env : {}; - const otelServiceName = env.OTEL_SERVICE_NAME; - const otelResourceAttrs = parseOtelResourceAttributes(env.OTEL_RESOURCE_ATTRIBUTES); - - return new SentryResource({ - // Lowest priority: Sentry defaults - // eslint-disable-next-line typescript/no-deprecated - [SEMRESATTRS_SERVICE_NAMESPACE]: 'sentry', - [SERVICE_NAME]: serviceNameFallback, - // OTEL_RESOURCE_ATTRIBUTES overrides defaults (including service.name and service.namespace) - ...otelResourceAttrs, - // OTEL_SERVICE_NAME explicitly overrides service.name - ...(otelServiceName ? { [SERVICE_NAME]: otelServiceName } : {}), - // Highest priority: Sentry SDK telemetry attrs (cannot be overridden by env vars) - [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/tracerProvider.ts b/packages/opentelemetry/src/tracerProvider.ts index e86edd5af68a..ae5d853c2732 100644 --- a/packages/opentelemetry/src/tracerProvider.ts +++ b/packages/opentelemetry/src/tracerProvider.ts @@ -1,19 +1,12 @@ 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: SpanAttributes }; - private readonly _tracers = new Map(); - public constructor(options: { resource?: { attributes: SpanAttributes } } = {}) { - this.resource = options.resource; - } - /** @inheritdoc */ public getTracer(name: string, version?: string, options?: TracerOptions): Tracer { const key = JSON.stringify([name, version, options]); diff --git a/packages/opentelemetry/test/helpers/initOtel.ts b/packages/opentelemetry/test/helpers/initOtel.ts index 39a0ee237eb2..c6bd477cc573 100644 --- a/packages/opentelemetry/test/helpers/initOtel.ts +++ b/packages/opentelemetry/test/helpers/initOtel.ts @@ -2,7 +2,6 @@ import { diag, DiagLogLevel, propagation, trace } from '@opentelemetry/api'; import { debug, getClient } from '@sentry/core'; import { DEBUG_BUILD } from '../../src/debug-build'; import { SentryPropagator } from '../../src/propagator'; -import { getSentryResource } from '../../src/resource'; import { setupEventContextTrace } from '../../src/setupEventContextTrace'; import type { TestClient } from './TestClient'; import { SentryTracerProvider } from '../../src/tracerProvider'; @@ -38,7 +37,7 @@ export function initOtel(): void { setupEventContextTrace(client); - const provider = new SentryTracerProvider({ resource: getSentryResource('node') }); + const provider = new SentryTracerProvider(); trace.setGlobalTracerProvider(provider); propagation.setGlobalPropagator(new SentryPropagator()); diff --git a/packages/opentelemetry/test/resource.test.ts b/packages/opentelemetry/test/resource.test.ts deleted file mode 100644 index bf870eaec524..000000000000 --- a/packages/opentelemetry/test/resource.test.ts +++ /dev/null @@ -1,131 +0,0 @@ -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; - - beforeEach(() => { - // Clone env so mutations are isolated - process.env = { ...originalEnv }; - delete process.env['OTEL_SERVICE_NAME']; - delete process.env['OTEL_RESOURCE_ATTRIBUTES']; - }); - - afterEach(() => { - process.env = originalEnv; - vi.restoreAllMocks(); - }); - - it('uses serviceNameFallback when no env vars are set', () => { - const resource = getSentryResource('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[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[SERVICE_NAME]).toBe('node'); - }); - - it('includes OTEL_RESOURCE_ATTRIBUTES key=value pairs', () => { - process.env['OTEL_RESOURCE_ATTRIBUTES'] = 'custom.key=custom-value,another.key=another-value'; - const resource = getSentryResource('node'); - expect(resource.attributes['custom.key']).toBe('custom-value'); - expect(resource.attributes['another.key']).toBe('another-value'); - }); - - 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[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[SERVICE_NAME]).toBe('from-service-name'); - }); - - it('OTEL_RESOURCE_ATTRIBUTES can override service.namespace', () => { - process.env['OTEL_RESOURCE_ATTRIBUTES'] = 'service.namespace=my-namespace'; - const resource = getSentryResource('node'); - // eslint-disable-next-line typescript/no-deprecated - expect(resource.attributes[SEMRESATTRS_SERVICE_NAMESPACE]).toBe('my-namespace'); - }); - - it('Sentry SDK telemetry attrs cannot be overridden by OTEL_RESOURCE_ATTRIBUTES', () => { - process.env['OTEL_RESOURCE_ATTRIBUTES'] = - 'telemetry.sdk.name=evil,telemetry.sdk.language=evil,telemetry.sdk.version=0.0.0'; - const resource = getSentryResource('node'); - // not evil or 0.0.0 - expect(resource.attributes[ATTR_TELEMETRY_SDK_NAME]).toBe(SDK_INFO[ATTR_TELEMETRY_SDK_NAME]); - expect(resource.attributes[ATTR_TELEMETRY_SDK_LANGUAGE]).toBe(SDK_INFO[ATTR_TELEMETRY_SDK_LANGUAGE]); - expect(resource.attributes[ATTR_TELEMETRY_SDK_VERSION]).toBe(SDK_INFO[ATTR_TELEMETRY_SDK_VERSION]); - }); - - 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[SERVICE_VERSION]).toBe(SDK_VERSION); - }); - - it('always includes Sentry SDK telemetry attributes', () => { - const resource = getSentryResource('node'); - 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[SERVICE_VERSION]).toBe(SDK_VERSION); - }); - - it('always sets service.namespace to sentry by default', () => { - const resource = getSentryResource('node'); - // eslint-disable-next-line typescript/no-deprecated - expect(resource.attributes[SEMRESATTRS_SERVICE_NAMESPACE]).toBe('sentry'); - }); - - it('URL-decodes values in OTEL_RESOURCE_ATTRIBUTES', () => { - process.env['OTEL_RESOURCE_ATTRIBUTES'] = 'custom.key=hello%20world'; - const resource = getSentryResource('node'); - expect(resource.attributes['custom.key']).toBe('hello world'); - }); - - it('handles malformed OTEL_RESOURCE_ATTRIBUTES gracefully (no = sign)', () => { - process.env['OTEL_RESOURCE_ATTRIBUTES'] = 'badentry,custom.key=value'; - expect(() => getSentryResource('node')).not.toThrow(); - const resource = getSentryResource('node'); - expect(resource.attributes['custom.key']).toBe('value'); - }); - - it('handles empty OTEL_RESOURCE_ATTRIBUTES gracefully', () => { - process.env['OTEL_RESOURCE_ATTRIBUTES'] = ''; - expect(() => getSentryResource('node')).not.toThrow(); - }); - - it('does not crash when process is undefined', () => { - const saved = global.process; - // @ts-expect-error — simulating edge runtime where process may be undefined - global.process = undefined; - try { - expect(() => getSentryResource('node')).not.toThrow(); - } finally { - global.process = saved; - } - }); -}); diff --git a/packages/vercel-edge/src/sdk.ts b/packages/vercel-edge/src/sdk.ts index 6346c55ea35a..7674aeef39fa 100644 --- a/packages/vercel-edge/src/sdk.ts +++ b/packages/vercel-edge/src/sdk.ts @@ -17,7 +17,6 @@ import { stackParserFromStackParserOptions, } from '@sentry/core'; import { - getSentryResource, SentryPropagator, SentryTracerProvider, setOpenTelemetryContextAsyncContextStrategy, @@ -114,7 +113,7 @@ export function setupOtel(client: VercelEdgeClient): void { setupOpenTelemetryLogger(); } - const provider = new SentryTracerProvider({ resource: getSentryResource('edge') }); + const provider = new SentryTracerProvider(); trace.setGlobalTracerProvider(provider); propagation.setGlobalPropagator(new SentryPropagator());