diff --git a/packages/opentelemetry/src/custom/client.ts b/packages/opentelemetry/src/custom/client.ts deleted file mode 100644 index 15a4e84bf5b2..000000000000 --- a/packages/opentelemetry/src/custom/client.ts +++ /dev/null @@ -1,58 +0,0 @@ -import type { Tracer } from '@opentelemetry/api'; -import { trace } from '@opentelemetry/api'; -import type { Client } from '@sentry/core'; -import { SDK_VERSION } from '@sentry/core'; -import type { OpenTelemetryClient as OpenTelemetryClientInterface, OpenTelemetryTracerProvider } from '../types'; - -// Typescript complains if we do not use `...args: any[]` for the mixin, with: -// A mixin class must have a constructor with a single rest parameter of type 'any[]'.ts(2545) -/* eslint-disable @typescript-eslint/no-explicit-any */ - -/** - * Wrap an Client class with things we need for OpenTelemetry support. - * Make sure that the Client class passed in is non-abstract! - * - * Usage: - * const OpenTelemetryClient = getWrappedClientClass(NodeClient); - * const client = new OpenTelemetryClient(options); - */ -export function wrapClientClass< - ClassConstructor extends new (...args: any[]) => Client, - WrappedClassConstructor extends new (...args: any[]) => Client & OpenTelemetryClientInterface, ->(ClientClass: ClassConstructor): WrappedClassConstructor { - // @ts-expect-error We just assume that this is non-abstract, if you pass in an abstract class this would make it non-abstract - class OpenTelemetryClient extends ClientClass implements OpenTelemetryClientInterface { - public traceProvider: OpenTelemetryTracerProvider | undefined; - private _tracer: Tracer | undefined; - - public constructor(...args: any[]) { - super(...args); - } - - /** Get the OTEL tracer. */ - public get tracer(): Tracer { - if (this._tracer) { - return this._tracer; - } - - const name = '@sentry/opentelemetry'; - const version = SDK_VERSION; - const tracer = trace.getTracer(name, version); - this._tracer = tracer; - - return tracer; - } - - /** - * @inheritDoc - */ - public async flush(timeout?: number): Promise { - const provider = this.traceProvider; - await provider?.forceFlush(); - return super.flush(timeout); - } - } - - return OpenTelemetryClient as unknown as WrappedClassConstructor; -} -/* eslint-enable @typescript-eslint/no-explicit-any */ diff --git a/packages/opentelemetry/src/exports.ts b/packages/opentelemetry/src/exports.ts index 7c3d992dae92..7816f437351c 100644 --- a/packages/opentelemetry/src/exports.ts +++ b/packages/opentelemetry/src/exports.ts @@ -2,9 +2,6 @@ export { SEMANTIC_ATTRIBUTE_SENTRY_GRAPHQL_OPERATION } from './semanticAttribute export { getRequestSpanData } from './utils/getRequestSpanData'; -export type { OpenTelemetryClient } from './types'; -export { wrapClientClass } from './custom/client'; - export { getScopesFromContext } from './utils/contextData'; export { diff --git a/packages/opentelemetry/src/trace.ts b/packages/opentelemetry/src/trace.ts index 4c031cf6d5e7..c1b49bb35094 100644 --- a/packages/opentelemetry/src/trace.ts +++ b/packages/opentelemetry/src/trace.ts @@ -27,7 +27,7 @@ import { spanToTraceContext, } from '@sentry/core'; import { continueTraceAsRemoteSpan } from './propagator'; -import type { OpenTelemetryClient, OpenTelemetrySpanContext } from './types'; +import type { OpenTelemetrySpanContext } from './types'; import { getContextFromScope } from './utils/contextData'; import { getSamplingDecision } from './utils/getSamplingDecision'; import { makeTraceState } from './utils/makeTraceState'; @@ -189,7 +189,8 @@ export function withActiveSpan(span: Span | null, callback: (scope: Scope) => } function getTracer(): Tracer { - const client = getClient(); + // The node client has a `tracer` property, we use this if it exists, or else we use the global tracer + const client = getClient(); return client?.tracer || trace.getTracer('@sentry/opentelemetry', SDK_VERSION); } diff --git a/packages/opentelemetry/src/types.ts b/packages/opentelemetry/src/types.ts index 1061d7e00730..1319c8b9fcab 100644 --- a/packages/opentelemetry/src/types.ts +++ b/packages/opentelemetry/src/types.ts @@ -1,5 +1,5 @@ -import type { Span as WriteableSpan, SpanKind, Tracer, TracerProvider } from '@opentelemetry/api'; -import type { BasicTracerProvider, ReadableSpan } from '@opentelemetry/sdk-trace-base'; +import type { Span as WriteableSpan, SpanKind, TracerProvider } from '@opentelemetry/api'; +import type { ReadableSpan } from '@opentelemetry/sdk-trace-base'; import type { Scope, Span, StartSpanOptions } from '@sentry/core'; export interface OpenTelemetryTracerProvider extends TracerProvider { @@ -7,11 +7,6 @@ export interface OpenTelemetryTracerProvider extends TracerProvider { shutdown(): Promise; } -export interface OpenTelemetryClient { - tracer: Tracer; - traceProvider: BasicTracerProvider | OpenTelemetryTracerProvider | undefined; -} - export interface OpenTelemetrySpanContext extends StartSpanOptions { // Additional otel-only option, for now...? kind?: SpanKind; diff --git a/packages/opentelemetry/test/helpers/TestClient.ts b/packages/opentelemetry/test/helpers/TestClient.ts index 7dc5a7f315a2..51bbba4928fd 100644 --- a/packages/opentelemetry/test/helpers/TestClient.ts +++ b/packages/opentelemetry/test/helpers/TestClient.ts @@ -1,13 +1,41 @@ +import { trace, type Tracer } from '@opentelemetry/api'; import type { ClientOptions, Event, Options, SeverityLevel } from '@sentry/core'; -import { Client, createTransport, getCurrentScope, resolvedSyncPromise } from '@sentry/core'; -import { wrapClientClass } from '../../src/custom/client'; -import type { OpenTelemetryClient } from '../../src/types'; +import { Client, createTransport, getCurrentScope, resolvedSyncPromise, SDK_VERSION } from '@sentry/core'; +import type { SentrySpanProcessor } from '../../src/spanProcessor'; +import type { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; + +export class TestClient extends Client { + public traceProvider: BasicTracerProvider | undefined; + public spanProcessor: SentrySpanProcessor | undefined; + private _tracer: Tracer | undefined; -class BaseTestClient extends Client { public constructor(options: ClientOptions) { super(options); } + /** Get the OTEL tracer. */ + public get tracer(): Tracer { + if (this._tracer) { + return this._tracer; + } + + const name = '@sentry/opentelemetry'; + const version = SDK_VERSION; + const tracer = (this.traceProvider || trace)?.getTracer(name, version); + this._tracer = tracer; + + return tracer; + } + + /** + * @inheritDoc + */ + public async flush(timeout?: number): Promise { + const provider = this.traceProvider; + await provider?.forceFlush(); + return super.flush(timeout); + } + public eventFromException(exception: any): PromiseLike { return resolvedSyncPromise({ exception: { @@ -26,16 +54,13 @@ class BaseTestClient extends Client { } } -export const TestClient = wrapClientClass(BaseTestClient); - -export type TestClientInterface = Client & OpenTelemetryClient; - -export function init(options: Partial = {}): void { +export function init(options: Partial = {}): Client { const client = new TestClient(getDefaultTestClientOptions({ tracesSampleRate: 1, ...options })); // The client is on the current scope, from where it generally is inherited getCurrentScope().setClient(client); client.init(); + return client; } export function getDefaultTestClientOptions(options: Partial = {}): ClientOptions { diff --git a/packages/opentelemetry/test/helpers/initOtel.ts b/packages/opentelemetry/test/helpers/initOtel.ts index a080679c1e78..959d5a454319 100644 --- a/packages/opentelemetry/test/helpers/initOtel.ts +++ b/packages/opentelemetry/test/helpers/initOtel.ts @@ -1,5 +1,6 @@ import { context, diag, DiagLogLevel, propagation, trace } from '@opentelemetry/api'; import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; +import type { Client } from '@sentry/core'; import { debug, getClient } from '@sentry/core'; import { SentryAsyncLocalStorageContextManager } from '../../src/asyncLocalStorageContextManager'; import { DEBUG_BUILD } from '../../src/debug-build'; @@ -9,13 +10,13 @@ import { SentrySampler } from '../../src/sampler'; import { setupEventContextTrace } from '../../src/setupEventContextTrace'; import { SentrySpanProcessor } from '../../src/spanProcessor'; import { enhanceDscWithOpenTelemetryRootSpanName } from '../../src/utils/enhanceDscWithOpenTelemetryRootSpanName'; -import type { TestClientInterface } from './TestClient'; +import type { TestClient } from './TestClient'; /** * Initialize OpenTelemetry for Node. */ export function initOtel(): void { - const client = getClient(); + const client = getClient(); if (!client) { DEBUG_BUILD && @@ -49,7 +50,7 @@ export function initOtel(): void { } /** Just exported for tests. */ -export function setupOtel(client: TestClientInterface): [BasicTracerProvider, SentrySpanProcessor] { +export function setupOtel(client: Client): [BasicTracerProvider, SentrySpanProcessor] { const spanProcessor = new SentrySpanProcessor(); // Create and configure NodeTracerProvider const provider = new BasicTracerProvider({ diff --git a/packages/opentelemetry/test/helpers/mockSdkInit.ts b/packages/opentelemetry/test/helpers/mockSdkInit.ts index 91b1369eb928..4c267e0045c9 100644 --- a/packages/opentelemetry/test/helpers/mockSdkInit.ts +++ b/packages/opentelemetry/test/helpers/mockSdkInit.ts @@ -4,9 +4,9 @@ import type { ClientOptions, Options } from '@sentry/core'; import { flush, getClient, getCurrentScope, getGlobalScope, getIsolationScope } from '@sentry/core'; import { setOpenTelemetryContextAsyncContextStrategy } from '../../src/asyncContextStrategy'; import { SentrySpanProcessor } from '../../src/spanProcessor'; -import type { OpenTelemetryClient } from '../../src/types'; import { clearOpenTelemetrySetupCheck } from '../../src/utils/setupCheck'; import { initOtel } from './initOtel'; +import type { TestClient } from './TestClient'; import { init as initTestClient } from './TestClient'; const PUBLIC_DSN = 'https://username@domain/123'; @@ -14,10 +14,11 @@ const PUBLIC_DSN = 'https://username@domain/123'; /** * Initialize Sentry for Node. */ -function init(options: Partial | undefined = {}): void { +function init(options: Partial | undefined = {}): TestClient { setOpenTelemetryContextAsyncContextStrategy(); - initTestClient(options); + const client = initTestClient(options); initOtel(); + return client; } function resetGlobals(): void { @@ -28,10 +29,10 @@ function resetGlobals(): void { delete (global as any).__SENTRY__; } -export function mockSdkInit(options?: Partial) { +export function mockSdkInit(options?: Partial): TestClient { resetGlobals(); - init({ dsn: PUBLIC_DSN, ...options }); + return init({ dsn: PUBLIC_DSN, ...options })!; } export async function cleanupOtel(_provider?: BasicTracerProvider): Promise { @@ -53,7 +54,7 @@ export async function cleanupOtel(_provider?: BasicTracerProvider): Promise(); + const client = getClient(); if (!client) { return undefined; } diff --git a/packages/opentelemetry/test/integration/breadcrumbs.test.ts b/packages/opentelemetry/test/integration/breadcrumbs.test.ts index 5375d3acbdf9..ee6a6b944424 100644 --- a/packages/opentelemetry/test/integration/breadcrumbs.test.ts +++ b/packages/opentelemetry/test/integration/breadcrumbs.test.ts @@ -1,8 +1,7 @@ -import { addBreadcrumb, captureException, getClient, withIsolationScope, withScope } from '@sentry/core'; +import { addBreadcrumb, captureException, withIsolationScope, withScope } from '@sentry/core'; import { afterEach, describe, expect, it, vi } from 'vitest'; import { startSpan } from '../../src/trace'; import { cleanupOtel, mockSdkInit } from '../helpers/mockSdkInit'; -import type { TestClientInterface } from '../helpers/TestClient'; describe('Integration | breadcrumbs', () => { const beforeSendTransaction = vi.fn(() => null); @@ -16,9 +15,7 @@ describe('Integration | breadcrumbs', () => { const beforeSend = vi.fn(() => null); const beforeBreadcrumb = vi.fn(breadcrumb => breadcrumb); - mockSdkInit({ beforeSend, beforeBreadcrumb }); - - const client = getClient() as TestClientInterface; + const client = mockSdkInit({ beforeSend, beforeBreadcrumb }); addBreadcrumb({ timestamp: 123456, message: 'test1' }); addBreadcrumb({ timestamp: 123457, message: 'test2', data: { nested: 'yes' } }); @@ -52,9 +49,7 @@ describe('Integration | breadcrumbs', () => { const beforeSend = vi.fn(() => null); const beforeBreadcrumb = vi.fn(breadcrumb => breadcrumb); - mockSdkInit({ beforeSend, beforeBreadcrumb }); - - const client = getClient() as TestClientInterface; + const client = mockSdkInit({ beforeSend, beforeBreadcrumb }); const error = new Error('test'); @@ -98,9 +93,7 @@ describe('Integration | breadcrumbs', () => { const beforeSend = vi.fn(() => null); const beforeBreadcrumb = vi.fn(breadcrumb => breadcrumb); - mockSdkInit({ beforeSend, beforeBreadcrumb, beforeSendTransaction, tracesSampleRate: 1 }); - - const client = getClient() as TestClientInterface; + const client = mockSdkInit({ beforeSend, beforeBreadcrumb, beforeSendTransaction, tracesSampleRate: 1 }); const error = new Error('test'); @@ -143,9 +136,7 @@ describe('Integration | breadcrumbs', () => { const beforeSend = vi.fn(() => null); const beforeBreadcrumb = vi.fn(breadcrumb => breadcrumb); - mockSdkInit({ beforeSend, beforeBreadcrumb, beforeSendTransaction, tracesSampleRate: 1 }); - - const client = getClient() as TestClientInterface; + const client = mockSdkInit({ beforeSend, beforeBreadcrumb, beforeSendTransaction, tracesSampleRate: 1 }); const error = new Error('test'); @@ -195,9 +186,7 @@ describe('Integration | breadcrumbs', () => { const beforeSend = vi.fn(() => null); const beforeBreadcrumb = vi.fn(breadcrumb => breadcrumb); - mockSdkInit({ beforeSend, beforeBreadcrumb, beforeSendTransaction, tracesSampleRate: 1 }); - - const client = getClient() as TestClientInterface; + const client = mockSdkInit({ beforeSend, beforeBreadcrumb, beforeSendTransaction, tracesSampleRate: 1 }); const error = new Error('test'); @@ -236,9 +225,7 @@ describe('Integration | breadcrumbs', () => { const beforeSend = vi.fn(() => null); const beforeBreadcrumb = vi.fn(breadcrumb => breadcrumb); - mockSdkInit({ beforeSend, beforeBreadcrumb, beforeSendTransaction, tracesSampleRate: 1 }); - - const client = getClient() as TestClientInterface; + const client = mockSdkInit({ beforeSend, beforeBreadcrumb, beforeSendTransaction, tracesSampleRate: 1 }); const error = new Error('test'); @@ -294,9 +281,7 @@ describe('Integration | breadcrumbs', () => { const beforeSend = vi.fn(() => null); const beforeBreadcrumb = vi.fn(breadcrumb => breadcrumb); - mockSdkInit({ beforeSend, beforeBreadcrumb, beforeSendTransaction, tracesSampleRate: 1 }); - - const client = getClient() as TestClientInterface; + const client = mockSdkInit({ beforeSend, beforeBreadcrumb, beforeSendTransaction, tracesSampleRate: 1 }); const error = new Error('test'); diff --git a/packages/opentelemetry/test/integration/scope.test.ts b/packages/opentelemetry/test/integration/scope.test.ts index 56acffb7921e..3d7e492f3590 100644 --- a/packages/opentelemetry/test/integration/scope.test.ts +++ b/packages/opentelemetry/test/integration/scope.test.ts @@ -1,7 +1,6 @@ import { captureException, getCapturedScopesOnSpan, - getClient, getCurrentScope, getIsolationScope, setTag, @@ -11,7 +10,6 @@ import { import { afterEach, describe, expect, it, vi } from 'vitest'; import { startSpan } from '../../src/trace'; import { cleanupOtel, mockSdkInit } from '../helpers/mockSdkInit'; -import type { TestClientInterface } from '../helpers/TestClient'; describe('Integration | Scope', () => { afterEach(async () => { @@ -26,14 +24,12 @@ describe('Integration | Scope', () => { const beforeSend = vi.fn(() => null); const beforeSendTransaction = vi.fn(() => null); - mockSdkInit({ + const client = mockSdkInit({ tracesSampleRate: tracingEnabled ? 1 : 0, beforeSend, beforeSendTransaction, }); - const client = getClient() as TestClientInterface; - const rootScope = getCurrentScope(); const error = new Error('test error'); @@ -144,9 +140,7 @@ describe('Integration | Scope', () => { const beforeSend = vi.fn(() => null); const beforeSendTransaction = vi.fn(() => null); - mockSdkInit({ tracesSampleRate: tracingEnabled ? 1 : 0, beforeSend, beforeSendTransaction }); - - const client = getClient() as TestClientInterface; + const client = mockSdkInit({ tracesSampleRate: tracingEnabled ? 1 : 0, beforeSend, beforeSendTransaction }); const rootScope = getCurrentScope(); const error1 = new Error('test error 1'); @@ -262,9 +256,8 @@ describe('Integration | Scope', () => { const beforeSend = vi.fn(() => null); const beforeSendTransaction = vi.fn(() => null); - mockSdkInit({ tracesSampleRate: tracingEnabled ? 1 : 0, beforeSend, beforeSendTransaction }); + const client = mockSdkInit({ tracesSampleRate: tracingEnabled ? 1 : 0, beforeSend, beforeSendTransaction }); - const client = getClient() as TestClientInterface; const rootScope = getCurrentScope(); const error1 = new Error('test error 1'); diff --git a/packages/opentelemetry/test/integration/transactions.test.ts b/packages/opentelemetry/test/integration/transactions.test.ts index 7280bc4d9b0c..ef193b4dc896 100644 --- a/packages/opentelemetry/test/integration/transactions.test.ts +++ b/packages/opentelemetry/test/integration/transactions.test.ts @@ -5,7 +5,6 @@ import type { Event, TransactionEvent } from '@sentry/core'; import { addBreadcrumb, debug, - getClient, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, setTag, @@ -17,7 +16,6 @@ import { SENTRY_TRACE_STATE_DSC } from '../../src/constants'; import { startInactiveSpan, startSpan } from '../../src/trace'; import { makeTraceState } from '../../src/utils/makeTraceState'; import { cleanupOtel, getSpanProcessor, mockSdkInit } from '../helpers/mockSdkInit'; -import type { TestClientInterface } from '../helpers/TestClient'; describe('Integration | Transactions', () => { afterEach(async () => { @@ -33,14 +31,12 @@ describe('Integration | Transactions', () => { return null; }); - mockSdkInit({ + const client = mockSdkInit({ tracesSampleRate: 1, beforeSendTransaction, release: '8.0.0', }); - const client = getClient() as TestClientInterface; - addBreadcrumb({ message: 'test breadcrumb 1', timestamp: 123456 }); setTag('outer.tag', 'test value'); @@ -176,9 +172,7 @@ describe('Integration | Transactions', () => { it('correctly creates concurrent transaction & spans', async () => { const beforeSendTransaction = vi.fn(() => null); - mockSdkInit({ tracesSampleRate: 1, beforeSendTransaction }); - - const client = getClient() as TestClientInterface; + const client = mockSdkInit({ tracesSampleRate: 1, beforeSendTransaction }); addBreadcrumb({ message: 'test breadcrumb 1', timestamp: 123456 }); @@ -337,9 +331,7 @@ describe('Integration | Transactions', () => { traceState, }; - mockSdkInit({ tracesSampleRate: 1, beforeSendTransaction }); - - const client = getClient() as TestClientInterface; + const client = mockSdkInit({ tracesSampleRate: 1, beforeSendTransaction }); // We simulate the correct context we'd normally get from the SentryPropagator context.with(trace.setSpanContext(ROOT_CONTEXT, spanContext), () => { @@ -745,14 +737,12 @@ describe('Integration | Transactions', () => { traceState: new TraceState().set(SENTRY_TRACE_STATE_DSC, dscString), }; - mockSdkInit({ + const client = mockSdkInit({ tracesSampleRate: 1, beforeSendTransaction, release: '7.0.0', }); - const client = getClient() as TestClientInterface; - // We simulate the correct context we'd normally get from the SentryPropagator context.with(trace.setSpanContext(ROOT_CONTEXT, spanContext), () => { startSpan( diff --git a/packages/opentelemetry/test/utils/setupEventContextTrace.test.ts b/packages/opentelemetry/test/utils/setupEventContextTrace.test.ts index 19c8e178c160..ea7c122efe32 100644 --- a/packages/opentelemetry/test/utils/setupEventContextTrace.test.ts +++ b/packages/opentelemetry/test/utils/setupEventContextTrace.test.ts @@ -4,14 +4,13 @@ import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vites import { setupEventContextTrace } from '../../src/setupEventContextTrace'; import { setupOtel } from '../helpers/initOtel'; import { cleanupOtel } from '../helpers/mockSdkInit'; -import type { TestClientInterface } from '../helpers/TestClient'; import { getDefaultTestClientOptions, TestClient } from '../helpers/TestClient'; const PUBLIC_DSN = 'https://username@domain/123'; describe('setupEventContextTrace', () => { const beforeSend = vi.fn(() => null); - let client: TestClientInterface; + let client: TestClient; let provider: BasicTracerProvider | undefined; beforeEach(() => { @@ -71,11 +70,11 @@ describe('setupEventContextTrace', () => { let innerId: string | undefined; let traceId: string | undefined; - client.tracer.startActiveSpan('outer', outerSpan => { + client.tracer?.startActiveSpan('outer', outerSpan => { outerId = outerSpan.spanContext().spanId; traceId = outerSpan.spanContext().traceId; - client.tracer.startActiveSpan('inner', innerSpan => { + client.tracer?.startActiveSpan('inner', innerSpan => { innerId = innerSpan.spanContext().spanId; captureException(error); });