diff --git a/packages/server-utils/src/integrations/tracing-channel/aws-sdk/index.ts b/packages/server-utils/src/integrations/tracing-channel/aws-sdk/index.ts index 4b3d85700653..4ed048a9de1f 100644 --- a/packages/server-utils/src/integrations/tracing-channel/aws-sdk/index.ts +++ b/packages/server-utils/src/integrations/tracing-channel/aws-sdk/index.ts @@ -17,7 +17,7 @@ import { import { DEBUG_BUILD } from '../../../debug-build'; import { CHANNELS } from '../../../orchestrion/channels'; import type { TracingChannelLifeCycleOptions } from '../../../tracing-channel'; -import { bindTracingChannelToSpan } from '../../../tracing-channel'; +import { bindTracingChannelToSpan, safeChannelCallback } from '../../../tracing-channel'; import { AWS_SDK_ORIGIN } from './constants'; import { ServicesExtensions } from './services'; import type { NormalizedRequest, NormalizedResponse, RequestMetadata } from './types'; @@ -49,16 +49,6 @@ interface AwsV3Command { constructor?: { name?: string }; } -/** Runs a span-building callback so a throw inside it can never break the user's aws-sdk call. */ -function safe(fn: () => T): T | undefined { - try { - return fn(); - } catch (error) { - DEBUG_BUILD && debug.warn('[orchestrion:aws-sdk] error building span', error); - return undefined; - } -} - // `metadata` is smithy's `ResponseMetadata`, read off the untyped channel result/error (`any` for the // same reason as `CommandInput`, see types.ts). function setMetadataAttributes(span: Span, metadata: Record | undefined): void { @@ -91,7 +81,7 @@ const _awsChannelIntegration = (() => { } const getSpan = (data: AwsSendChannelContext): Span | undefined => - safe(() => { + safeChannelCallback(() => { const command = data.arguments[0] as AwsV3Command | undefined; const commandName = command?.constructor?.name; if (!command || !commandName) { @@ -140,7 +130,7 @@ const _awsChannelIntegration = (() => { // so `cloud.region` cannot be lost when `send` settles first (e.g. an early failure). // // The provider call is guarded separately: the span is already started, so a synchronous - // throw bubbling into the enclosing `safe` would discard it without ending it (a leaked + // throw bubbling into the enclosing `safeChannelCallback` would discard it without ending it (a leaked // open span). let regionResult: string | Promise | undefined; try { @@ -169,7 +159,7 @@ const _awsChannelIntegration = (() => { // Inject trace-propagation headers into outgoing messages (SQS/SNS/Lambda). Runs before // `send` proceeds, so the mutated `commandInput` is used to build the request. - safe(() => servicesExtensions.requestPostSpanHook(normalizedRequest, span)); + safeChannelCallback(() => servicesExtensions.requestPostSpanHook(normalizedRequest, span)); return span; }); @@ -186,7 +176,7 @@ const _awsChannelIntegration = (() => { // The channel `result`/`error` are untyped; the `$metadata` casts below name smithy's // `ResponseMetadata` shape (`any`-valued, see `setMetadataAttributes`). - safe(() => { + safeChannelCallback(() => { if (failed) { const err = data.error as | { $metadata?: Record; RequestId?: string; extendedRequestId?: string } diff --git a/packages/server-utils/src/integrations/tracing-channel/firebase/instrumentation.ts b/packages/server-utils/src/integrations/tracing-channel/firebase/instrumentation.ts index 2d59a03ae8e2..6a122bca4ff2 100644 --- a/packages/server-utils/src/integrations/tracing-channel/firebase/instrumentation.ts +++ b/packages/server-utils/src/integrations/tracing-channel/firebase/instrumentation.ts @@ -1,8 +1,6 @@ import * as diagnosticsChannel from 'node:diagnostics_channel'; -import { debug } from '@sentry/core'; -import { DEBUG_BUILD } from '../../../debug-build'; import { CHANNELS } from '../../../orchestrion/channels'; -import { bindTracingChannelToSpan } from '../../../tracing-channel'; +import { bindTracingChannelToSpan, safeChannelCallback } from '../../../tracing-channel'; import type { FirestoreReference } from './firestore-types'; import { startFirestoreSpan } from './firestore'; import { wrapFunctionsRegistration } from './functions'; @@ -43,24 +41,10 @@ const FUNCTIONS_TRIGGERS: Array<{ channel: string; triggerType: string }> = [ const NOOP = (): void => {}; -/** - * Runs a span-building callback so a throw inside it can never break the user's firebase call: these run - * inside the `tracingChannel(...)` machinery wrapping the real function, where an unguarded throw would - * propagate into the traced call. - */ -function safe(fn: () => T): T | undefined { - try { - return fn(); - } catch (error) { - DEBUG_BUILD && debug.warn('[orchestrion:firebase] error handling channel event', error); - return undefined; - } -} - export function instrumentFirebase() { for (const { channel, spanName, useParent } of FIRESTORE_OPERATIONS) { bindTracingChannelToSpan(diagnosticsChannel.tracingChannel(channel), data => - safe(() => { + safeChannelCallback(() => { const reference = data.arguments[0] as FirestoreReference | undefined; if (!reference) { return undefined; @@ -76,7 +60,8 @@ export function instrumentFirebase() { // registration call, so we only rewrap the handler argument here (in `start`) and open the // span inside that wrapper. The other lifecycle events are irrelevant, so no-op them. diagnosticsChannel.tracingChannel(channel).subscribe({ - start: data => void safe(() => wrapFunctionsRegistration(data as { arguments: unknown[] }, triggerType)), + start: data => + void safeChannelCallback(() => wrapFunctionsRegistration(data as { arguments: unknown[] }, triggerType)), end: NOOP, asyncStart: NOOP, asyncEnd: NOOP, diff --git a/packages/server-utils/src/integrations/tracing-channel/graphql/index.ts b/packages/server-utils/src/integrations/tracing-channel/graphql/index.ts index 2a3baa669e16..ecfd91c2785b 100644 --- a/packages/server-utils/src/integrations/tracing-channel/graphql/index.ts +++ b/packages/server-utils/src/integrations/tracing-channel/graphql/index.ts @@ -1,11 +1,10 @@ import * as diagnosticsChannel from 'node:diagnostics_channel'; import type { IntegrationFn } from '@sentry/core'; -import { debug, defineIntegration, extendIntegration, waitForTracingChannelBinding } from '@sentry/core'; -import { DEBUG_BUILD } from '../../../debug-build'; +import { defineIntegration, extendIntegration, waitForTracingChannelBinding } from '@sentry/core'; import { graphqlIntegration as graphqlNativeIntegration } from '../../../graphql'; import type { GraphqlDiagnosticChannelsOptions } from '../../../graphql/graphql-dc-subscriber'; import { CHANNELS } from '../../../orchestrion/channels'; -import { bindTracingChannelToSpan } from '../../../tracing-channel'; +import { bindTracingChannelToSpan, safeChannelCallback } from '../../../tracing-channel'; import { finalizeExecuteSpan, finalizeValidateSpan, @@ -35,20 +34,6 @@ function getOptionsWithDefaults(options: GraphqlDiagnosticChannelsOptions): Grap }; } -/** - * Runs a span-building callback so a throw inside it can never break the user's graphql call: these - * run inside the `tracingChannel(...).trace*` machinery wrapping the real function (as the `getSpan` - * producer / `beforeSpanEnd` handler), where an unguarded throw would propagate into the traced call. - */ -function safe(fn: () => T): T | undefined { - try { - return fn(); - } catch (error) { - DEBUG_BUILD && debug.warn('[orchestrion:graphql] error building span', error); - return undefined; - } -} - const _graphqlChannelIntegration = ((options: GraphqlDiagnosticChannelsOptions = {}) => { const config = getOptionsWithDefaults(options); const getConfig = (): GraphqlResolvedConfig => config; @@ -62,19 +47,19 @@ const _graphqlChannelIntegration = ((options: GraphqlDiagnosticChannelsOptions = waitForTracingChannelBinding(() => { bindTracingChannelToSpan(diagnosticsChannel.tracingChannel(CHANNELS.GRAPHQL_PARSE), () => - safe(() => startParseSpan()), + safeChannelCallback(() => startParseSpan()), ); bindTracingChannelToSpan( diagnosticsChannel.tracingChannel(CHANNELS.GRAPHQL_VALIDATE), - data => safe(() => startValidateSpan(data.arguments[1])), - { beforeSpanEnd: (span, data) => void safe(() => finalizeValidateSpan(span, data.result)) }, + data => safeChannelCallback(() => startValidateSpan(data.arguments[1])), + { beforeSpanEnd: (span, data) => void safeChannelCallback(() => finalizeValidateSpan(span, data.result)) }, ); bindTracingChannelToSpan( diagnosticsChannel.tracingChannel(CHANNELS.GRAPHQL_EXECUTE), - data => safe(() => startExecuteSpan(data.arguments, data.self, config, getConfig)), - { beforeSpanEnd: (span, data) => void safe(() => finalizeExecuteSpan(span, data.result)) }, + data => safeChannelCallback(() => startExecuteSpan(data.arguments, data.self, config, getConfig)), + { beforeSpanEnd: (span, data) => void safeChannelCallback(() => finalizeExecuteSpan(span, data.result)) }, ); }); }, diff --git a/packages/server-utils/src/tracing-channel.ts b/packages/server-utils/src/tracing-channel.ts index 097a3da6018a..c44faf7ccb8c 100644 --- a/packages/server-utils/src/tracing-channel.ts +++ b/packages/server-utils/src/tracing-channel.ts @@ -90,6 +90,16 @@ export interface TracingChannelBindingHandle { const NOOP = (): void => {}; +/** Runs a span-building callback so a throw inside it can never break the user's traced call. */ +export function safeChannelCallback(fn: () => T): T | undefined { + try { + return fn(); + } catch (error) { + DEBUG_BUILD && debug.warn('[orchestrion] error handling channel event', error); + return undefined; + } +} + /** * Bind a span and its lifecycle to a tracing channel so the span becomes the active async context * for the traced operation and is ended when the operation completes.