diff --git a/packages/core/src/baseclient.ts b/packages/core/src/baseclient.ts index 52c4301f94ee..e2afccdb0d97 100644 --- a/packages/core/src/baseclient.ts +++ b/packages/core/src/baseclient.ts @@ -388,6 +388,9 @@ export abstract class BaseClient implements Client { /** @inheritdoc */ public on(hook: 'createDsc', callback: (dsc: DynamicSamplingContext) => void): void; + /** @inheritdoc */ + public on(hook: 'otelSpanEnd', callback: (otelSpan: unknown, mutableOptions: { drop: boolean }) => void): void; + /** @inheritdoc */ public on(hook: string, callback: unknown): void { if (!this._hooks[hook]) { @@ -413,6 +416,9 @@ export abstract class BaseClient implements Client { /** @inheritdoc */ public emit(hook: 'createDsc', dsc: DynamicSamplingContext): void; + /** @inheritdoc */ + public emit(hook: 'otelSpanEnd', otelSpan: unknown, mutableOptions: { drop: boolean }): void; + /** @inheritdoc */ public emit(hook: string, ...rest: unknown[]): void { if (this._hooks[hook]) { diff --git a/packages/node-experimental/src/integrations/http.ts b/packages/node-experimental/src/integrations/http.ts index 382ca80d4879..1a01c782053c 100644 --- a/packages/node-experimental/src/integrations/http.ts +++ b/packages/node-experimental/src/integrations/http.ts @@ -105,7 +105,7 @@ export class Http implements Integration { // eslint-disable-next-line deprecation/deprecation this._shouldCreateSpanForRequest || clientOptions?.shouldCreateSpanForRequest; - client?.otelHooks.on('spanEnd', this._onSpanEnd); + client?.on?.('otelSpanEnd', this._onSpanEnd); } /** @@ -113,24 +113,26 @@ export class Http implements Integration { */ public unregister(): void { this._unload?.(); - - const client = getCurrentHub().getClient(); - client?.otelHooks.off('spanEnd', this._onSpanEnd); } - private _onSpanEnd: (otelSpan: OtelSpan) => void | false = (otelSpan: OtelSpan) => { + private _onSpanEnd: (otelSpan: unknown, mutableOptions: { drop: boolean }) => void = ( + otelSpan: unknown, + mutableOptions: { drop: boolean }, + ) => { if (!this._shouldCreateSpans) { - return false; + mutableOptions.drop = true; + return; } if (this._shouldCreateSpanForRequest) { - const url = getHttpUrl(otelSpan.attributes); + const url = getHttpUrl((otelSpan as OtelSpan).attributes); if (url && !this._shouldCreateSpanForRequest(url)) { - return false; + mutableOptions.drop = true; + return; } } - return undefined; + return; }; /** Handle an emitted span from the HTTP instrumentation. */ diff --git a/packages/node-experimental/src/sdk/client.ts b/packages/node-experimental/src/sdk/client.ts index 337c50aa481f..8d7bddad07f6 100644 --- a/packages/node-experimental/src/sdk/client.ts +++ b/packages/node-experimental/src/sdk/client.ts @@ -1,7 +1,6 @@ import type { Tracer } from '@opentelemetry/api'; import { trace } from '@opentelemetry/api'; import { NodeClient, SDK_VERSION } from '@sentry/node'; -import { OtelHooks } from '@sentry/opentelemetry-node'; import type { NodeExperimentalClientOptions } from '../types'; @@ -9,8 +8,6 @@ import type { NodeExperimentalClientOptions } from '../types'; * A client built on top of the NodeClient, which provides some otel-specific things on top. */ export class NodeExperimentalClient extends NodeClient { - public otelHooks: OtelHooks; - private _tracer: Tracer | undefined; public constructor(options: ConstructorParameters[0]) { @@ -27,8 +24,6 @@ export class NodeExperimentalClient extends NodeClient { }; super(options); - - this.otelHooks = new OtelHooks(); } /** Get the OTEL tracer. */ diff --git a/packages/node-experimental/src/sdk/initOtel.ts b/packages/node-experimental/src/sdk/initOtel.ts index 1b8d875b655c..cd2b27b79c05 100644 --- a/packages/node-experimental/src/sdk/initOtel.ts +++ b/packages/node-experimental/src/sdk/initOtel.ts @@ -16,14 +16,11 @@ export function initOtel(): () => void { diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG); } - // We use the custom otelHooks from the client to communicate with @sentry/opentelemetry-node - const hooks = client?.otelHooks; - // Create and configure NodeTracerProvider const provider = new NodeTracerProvider({ sampler: new AlwaysOnSampler(), }); - provider.addSpanProcessor(new SentrySpanProcessor({ hooks })); + provider.addSpanProcessor(new SentrySpanProcessor()); // Initialize the provider provider.register({ diff --git a/packages/opentelemetry-node/src/hooks.ts b/packages/opentelemetry-node/src/hooks.ts deleted file mode 100644 index db7511a2a43f..000000000000 --- a/packages/opentelemetry-node/src/hooks.ts +++ /dev/null @@ -1,69 +0,0 @@ -import type { Span as OtelSpan } from '@opentelemetry/sdk-trace-base'; -import type { Span } from '@sentry/types'; - -/** - * Keep some otel hooks separate from the default client hooks. - */ -export class OtelHooks { - // eslint-disable-next-line @typescript-eslint/ban-types - private _hooks: Record = {}; - - /** - * Fires when an Otel Span is ended. - * Receives the Otel Span as argument. - * Return `false` from the callback to skip this span in Sentry. - */ - public on(hook: 'spanEnd', callback: (otelSpan: OtelSpan, sentrySpan: Span) => void | false): void; - - /** - * @inheritdoc - */ - public on(hook: string, callback: unknown): void { - if (!this._hooks[hook]) { - this._hooks[hook] = []; - } - - // @ts-ignore We assue the types are correct - this._hooks[hook].push(callback); - } - - /** - * Unregister the hook. - */ - public off(hook: 'spanEnd', callback: (otelSpan: OtelSpan, sentrySpan: Span) => void | false): void; - - /** - * @inheritdoc - */ - public off(hook: string, callback: unknown): void { - if (!this._hooks[hook]) { - return; - } - - // @ts-ignore We assue the types are correct - const index = this._hooks[hook].indexOf(callback); - if (index > -1) { - this._hooks[hook].splice(index, 1); - } - } - - /** - * Emit the otel span end hook. - * Returns `false` if the span should not be sent to Sentry. - */ - public emit(hook: 'spanEnd', otelSpan: OtelSpan, sentrySpan: Span): void | false; - - /** - * @inheritdoc - */ - public emit(hook: string, ...rest: unknown[]): void | false { - if (this._hooks[hook]) { - for (const callback of this._hooks[hook]) { - const cancel = callback(...rest); - if (cancel === false) { - return false; - } - } - } - } -} diff --git a/packages/opentelemetry-node/src/index.ts b/packages/opentelemetry-node/src/index.ts index 1ef86c354008..4f68820d23a2 100644 --- a/packages/opentelemetry-node/src/index.ts +++ b/packages/opentelemetry-node/src/index.ts @@ -1,4 +1,3 @@ export { SentrySpanProcessor } from './spanprocessor'; export { SentryPropagator } from './propagator'; -export { OtelHooks } from './hooks'; export * from './utils/spanData'; diff --git a/packages/opentelemetry-node/src/spanprocessor.ts b/packages/opentelemetry-node/src/spanprocessor.ts index dbcda429953b..b2dc2beebf71 100644 --- a/packages/opentelemetry-node/src/spanprocessor.ts +++ b/packages/opentelemetry-node/src/spanprocessor.ts @@ -7,7 +7,6 @@ import type { DynamicSamplingContext, Span as SentrySpan, TraceparentData, Trans import { isString, logger } from '@sentry/utils'; import { SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY, SENTRY_TRACE_PARENT_CONTEXT_KEY } from './constants'; -import type { OtelHooks } from './hooks'; import { isSentryRequestSpan } from './utils/isSentryRequest'; import { mapOtelStatus } from './utils/mapOtelStatus'; import { parseSpanDescription } from './utils/parseOtelSpanDescription'; @@ -29,11 +28,7 @@ function clearSpan(otelSpanId: string): void { * the Sentry SDK. */ export class SentrySpanProcessor implements OtelSpanProcessor { - private _hooks?: OtelHooks; - - public constructor({ hooks }: { hooks?: OtelHooks } = {}) { - this._hooks = hooks; - + public constructor() { addTracingExtensions(); addGlobalEventProcessor(event => { @@ -114,11 +109,14 @@ export class SentrySpanProcessor implements OtelSpanProcessor { return; } - if (this._hooks) { - if (this._hooks.emit('spanEnd', otelSpan, sentrySpan) === false) { - clearSpan(otelSpanId); - return; - } + const client = getCurrentHub().getClient(); + + const mutableOptions = { drop: false }; + client && client.emit && client?.emit('otelSpanEnd', otelSpan, mutableOptions); + + if (mutableOptions.drop) { + clearSpan(otelSpanId); + return; } otelSpan.events.forEach(event => { diff --git a/packages/types/src/client.ts b/packages/types/src/client.ts index ac779fc3058e..3b9c5835f8b8 100644 --- a/packages/types/src/client.ts +++ b/packages/types/src/client.ts @@ -190,10 +190,16 @@ export interface Client { on?(hook: 'beforeAddBreadcrumb', callback: (breadcrumb: Breadcrumb, hint?: BreadcrumbHint) => void): void; /** - * Register a callback whena DSC (Dynamic Sampling Context) is created. + * Register a callback when a DSC (Dynamic Sampling Context) is created. */ on?(hook: 'createDsc', callback: (dsc: DynamicSamplingContext) => void): void; + /** + * Register a callback when an OpenTelemetry span is ended (in @sentry/opentelemetry-node). + * The option argument may be mutated to drop the span. + */ + on?(hook: 'otelSpanEnd', callback: (otelSpan: unknown, mutableOptions: { drop: boolean }) => void): void; + /** * Fire a hook event for transaction start and finish. Expects to be given a transaction as the * second argument. @@ -221,4 +227,11 @@ export interface Client { * Fire a hook for when a DSC (Dynamic Sampling Context) is created. Expects the DSC as second argument. */ emit?(hook: 'createDsc', dsc: DynamicSamplingContext): void; + + /** + * Fire a hook for when an OpenTelemetry span is ended (in @sentry/opentelemetry-node). + * Expects the OTEL span & as second argument, and an option object as third argument. + * The option argument may be mutated to drop the span. + */ + emit?(hook: 'otelSpanEnd', otelSpan: unknown, mutableOptions: { drop: boolean }): void; }