diff --git a/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts b/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts index 14dedc28363d..7e0873355c65 100644 --- a/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts +++ b/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts @@ -31,6 +31,9 @@ const NODE_EXPORTS_IGNORE = [ 'diagnosticsChannelInjectionIntegrations', // Companion to the above two, same reasoning (Next.js re-exports it via `export * from '@sentry/node'`) 'isDiagnosticsChannelInjectionEnabled', + // Helper for SDKs that build their own default-integration set (e.g. aws-serverless) + // to apply the diagnostics-channel integration swap; not surfaced elsewhere. + 'applyDiagnosticsChannelInjectionIntegrations', // Internal helper only needed within integrations (e.g. bunRuntimeMetricsIntegration) '_INTERNAL_normalizeCollectionInterval', ]; diff --git a/dev-packages/node-integration-tests/suites/aws-serverless/aws-integration-streamed/test.ts b/dev-packages/node-integration-tests/suites/aws-serverless/aws-integration-streamed/test.ts index 7491894494eb..a5e4ea13011a 100644 --- a/dev-packages/node-integration-tests/suites/aws-serverless/aws-integration-streamed/test.ts +++ b/dev-packages/node-integration-tests/suites/aws-serverless/aws-integration-streamed/test.ts @@ -1,7 +1,12 @@ import type { SerializedStreamedSpanContainer } from '@sentry/core'; import { afterAll, describe, expect } from 'vitest'; +import { isOrchestrionEnabled } from '../../../utils'; import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runner'; +// See the non-streamed `aws-integration` suite: only the origin differs between the OTel and +// orchestrion diagnostics-channel runs. +const ORIGIN = isOrchestrionEnabled() ? 'auto.aws.orchestrion.aws_sdk' : 'auto.otel.aws'; + // The aws-sdk instrumentation creates spans by patching the underlying smithy middleware stack. The // patch target differs between aws-sdk versions, so we run the exact same assertions against both: // - the current aws-sdk (default, resolved from the workspace) which routes through `@smithy/core` >= 3.24.0 @@ -49,7 +54,7 @@ function assertAwsServiceSpans(spanCcontainer: SerializedStreamedSpanContainer): name: 'S3.PutObject', status: 'ok', attributes: expect.objectContaining({ - 'sentry.origin': { value: 'auto.otel.aws', type: 'string' }, + 'sentry.origin': { value: ORIGIN, type: 'string' }, 'sentry.op': { value: 'rpc', type: 'string' }, 'rpc.system': { value: 'aws-api', type: 'string' }, 'rpc.method': { value: 'PutObject', type: 'string' }, diff --git a/dev-packages/node-integration-tests/suites/aws-serverless/aws-integration/test.ts b/dev-packages/node-integration-tests/suites/aws-serverless/aws-integration/test.ts index a27b5c1a88ff..22d84e5ffb3a 100644 --- a/dev-packages/node-integration-tests/suites/aws-serverless/aws-integration/test.ts +++ b/dev-packages/node-integration-tests/suites/aws-serverless/aws-integration/test.ts @@ -1,7 +1,13 @@ import type { TransactionEvent } from '@sentry/core'; import { afterAll, describe, expect } from 'vitest'; +import { isOrchestrionEnabled } from '../../../utils'; import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runner'; +// The suite runs twice on CI: once with the OTel `Aws` integration (default) and once with the +// orchestrion diagnostics-channel integration auto-injected (`INJECT_ORCHESTRION`). Both emit the +// same spans; only the origin differs. +const ORIGIN = isOrchestrionEnabled() ? 'auto.aws.orchestrion.aws_sdk' : 'auto.otel.aws'; + // The aws-sdk instrumentation creates spans by patching the underlying smithy middleware stack. The // patch target differs between aws-sdk versions, so we run the exact same assertions against both: // - the current aws-sdk (default, resolved from the workspace) which routes through `@smithy/core` >= 3.24.0 @@ -40,10 +46,10 @@ function assertAwsServiceSpans(transaction: TransactionEvent): void { expectSpan('S3.PutObject', { description: 'S3.PutObject', op: 'rpc', - origin: 'auto.otel.aws', + origin: ORIGIN, status: 'ok', data: expect.objectContaining({ - 'sentry.origin': 'auto.otel.aws', + 'sentry.origin': ORIGIN, 'sentry.op': 'rpc', 'rpc.system': 'aws-api', 'rpc.method': 'PutObject', @@ -58,7 +64,7 @@ function assertAwsServiceSpans(transaction: TransactionEvent): void { expectSpan('S3.GetObject (success)', { description: 'S3.GetObject', op: 'rpc', - origin: 'auto.otel.aws', + origin: ORIGIN, status: 'ok', data: expect.objectContaining({ 'rpc.method': 'GetObject', 'rpc.service': 'S3', 'aws.s3.bucket': 'ot-demo-test' }), }); @@ -67,7 +73,7 @@ function assertAwsServiceSpans(transaction: TransactionEvent): void { expectSpan('S3.GetObject (error)', { description: 'S3.GetObject', op: 'rpc', - origin: 'auto.otel.aws', + origin: ORIGIN, status: 'internal_error', data: expect.objectContaining({ 'rpc.method': 'GetObject', 'rpc.service': 'S3' }), }); @@ -76,7 +82,7 @@ function assertAwsServiceSpans(transaction: TransactionEvent): void { expectSpan('DynamoDB.PutItem', { description: 'DynamoDB.PutItem', op: 'db', - origin: 'auto.otel.aws', + origin: ORIGIN, data: expect.objectContaining({ 'sentry.op': 'db', 'rpc.method': 'PutItem', @@ -92,7 +98,7 @@ function assertAwsServiceSpans(transaction: TransactionEvent): void { expectSpan('DynamoDB.Query', { description: 'DynamoDB.Query', op: 'db', - origin: 'auto.otel.aws', + origin: ORIGIN, data: expect.objectContaining({ 'rpc.method': 'Query', 'db.operation': 'Query', @@ -105,7 +111,7 @@ function assertAwsServiceSpans(transaction: TransactionEvent): void { expectSpan('SQS SendMessage', { description: 'my-queue send', op: 'rpc', - origin: 'auto.otel.aws', + origin: ORIGIN, data: expect.objectContaining({ 'rpc.method': 'SendMessage', 'rpc.service': 'SQS', @@ -121,7 +127,7 @@ function assertAwsServiceSpans(transaction: TransactionEvent): void { expectSpan('SQS ReceiveMessage', { description: 'my-queue receive', op: 'rpc', - origin: 'auto.otel.aws', + origin: ORIGIN, data: expect.objectContaining({ 'rpc.method': 'ReceiveMessage', 'messaging.system': 'aws_sqs', @@ -135,7 +141,7 @@ function assertAwsServiceSpans(transaction: TransactionEvent): void { expectSpan('SNS Publish', { description: 'my-topic send', op: 'rpc', - origin: 'auto.otel.aws', + origin: ORIGIN, data: expect.objectContaining({ 'rpc.method': 'Publish', 'rpc.service': 'SNS', @@ -150,7 +156,7 @@ function assertAwsServiceSpans(transaction: TransactionEvent): void { expectSpan('Lambda Invoke', { description: 'my-function Invoke', op: 'rpc', - origin: 'auto.otel.aws', + origin: ORIGIN, data: expect.objectContaining({ 'rpc.method': 'Invoke', 'rpc.service': 'Lambda', @@ -164,7 +170,7 @@ function assertAwsServiceSpans(transaction: TransactionEvent): void { expectSpan('Kinesis.PutRecord', { description: 'Kinesis.PutRecord', op: 'rpc', - origin: 'auto.otel.aws', + origin: ORIGIN, status: 'ok', data: expect.objectContaining({ 'rpc.method': 'PutRecord', @@ -177,7 +183,7 @@ function assertAwsServiceSpans(transaction: TransactionEvent): void { expectSpan('SecretsManager.GetSecretValue', { description: 'SecretsManager.GetSecretValue', op: 'rpc', - origin: 'auto.otel.aws', + origin: ORIGIN, data: expect.objectContaining({ 'rpc.method': 'GetSecretValue', 'rpc.service': 'SecretsManager', @@ -189,7 +195,7 @@ function assertAwsServiceSpans(transaction: TransactionEvent): void { expectSpan('StepFunctions.StartExecution', { description: 'SFN.StartExecution', op: 'rpc', - origin: 'auto.otel.aws', + origin: ORIGIN, data: expect.objectContaining({ 'rpc.method': 'StartExecution', 'rpc.service': 'SFN', diff --git a/packages/aws-serverless/src/index.ts b/packages/aws-serverless/src/index.ts index 71222eb0a6a6..2078044d64a8 100644 --- a/packages/aws-serverless/src/index.ts +++ b/packages/aws-serverless/src/index.ts @@ -167,6 +167,8 @@ export { metrics, spanStreamingIntegration, withStreamedSpan, + experimentalUseDiagnosticsChannelInjection, + diagnosticsChannelInjectionIntegrations, } from '@sentry/node'; export { diff --git a/packages/aws-serverless/src/init.ts b/packages/aws-serverless/src/init.ts index fe069d7ff6f5..afdbf3a6c1dd 100644 --- a/packages/aws-serverless/src/init.ts +++ b/packages/aws-serverless/src/init.ts @@ -1,7 +1,11 @@ import type { Integration, Options } from '@sentry/core'; import { applySdkMetadata, debug, getSDKSource } from '@sentry/core'; import type { NodeClient, NodeOptions } from '@sentry/node'; -import { getDefaultIntegrationsWithoutPerformance, initWithoutDefaultIntegrations } from '@sentry/node'; +import { + applyDiagnosticsChannelInjectionIntegrations, + getDefaultIntegrationsWithoutPerformance, + initWithoutDefaultIntegrations, +} from '@sentry/node'; import { envToBool } from '@sentry/node-core'; import { DEBUG_BUILD } from './debug-build'; import { awsIntegration } from './integration/aws'; @@ -49,8 +53,11 @@ function shouldDisableLayerExtensionForProxy(): boolean { */ // NOTE: in awslambda-auto.ts, we also call the original `getDefaultIntegrations` from `@sentry/node` to load performance integrations. // If at some point we need to filter a node integration out for good, we need to make sure to also filter it out there. -export function getDefaultIntegrations(_options: Options): Integration[] { - return [...getDefaultIntegrationsWithoutPerformance(), awsIntegration(), awsLambdaIntegration()]; +export function getDefaultIntegrations(options: Options): Integration[] { + const integrations = [...getDefaultIntegrationsWithoutPerformance(), awsIntegration(), awsLambdaIntegration()]; + // If the app opted into diagnostics-channel injection, swap the OTel `Aws` integration for its + // channel-based equivalent (registered in `@sentry/server-utils`). No-op otherwise. + return applyDiagnosticsChannelInjectionIntegrations(integrations, options); } export interface AwsServerlessOptions extends NodeOptions { diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 907adfa6b012..88be176415f5 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -46,6 +46,7 @@ export { getDefaultIntegrations, getDefaultIntegrationsWithoutPerformance, initWithoutDefaultIntegrations, + applyDiagnosticsChannelInjectionIntegrations, } from './sdk'; export { experimentalUseDiagnosticsChannelInjection, diff --git a/packages/node/src/sdk/index.ts b/packages/node/src/sdk/index.ts index 8c8d2e887541..5c89737d9166 100644 --- a/packages/node/src/sdk/index.ts +++ b/packages/node/src/sdk/index.ts @@ -39,15 +39,24 @@ export function getDefaultIntegrations(options: Options): Integration[] { ...(hasSpansEnabled(options) ? getAutoPerformanceIntegrations() : []), ]; - // When the app opted into diagnostics-channel injection (via - // `experimentalUseDiagnosticsChannelInjection()`) AND span recording is - // enabled, swap the channel-based integrations in place of OTel equivalents - // so the two don't both instrument the same library. - // - // Every channel-based integration we ship today is a 1:1 replacement for an - // OTel performance/tracing integration and produces nothing but spans (those - // only come from `getAutoPerformanceIntegrations()` above), so it's gated on - // span recording. + return applyDiagnosticsChannelInjectionIntegrations(integrations, options); +} + +/** + * When the app opted into diagnostics-channel injection (via + * `experimentalUseDiagnosticsChannelInjection()`) AND span recording is enabled, swap the + * channel-based integrations in place of their OTel equivalents so the two don't both instrument the + * same library. Otherwise returns `integrations` unchanged. + * + * Every channel-based integration we ship today is a 1:1 replacement for an OTel performance/tracing + * integration and produces nothing but spans, so this is gated on span recording. Exported so SDKs + * that build their own default-integration set (e.g. `@sentry/aws-serverless`) can apply the same + * swap instead of duplicating this logic. + */ +export function applyDiagnosticsChannelInjectionIntegrations( + integrations: Integration[], + options: Options, +): Integration[] { if (isDiagnosticsChannelInjectionEnabled() && hasSpansEnabled(options)) { const diagnosticsChannelInjection = resolveDiagnosticsChannelInjection(); if (diagnosticsChannelInjection) {