Skip to content

Commit a3978fa

Browse files
andreiborzaclaude
andcommitted
feat(aws-serverless): Use orchestrion aws-sdk integration under diagnostics-channel opt-in
Wires the orchestrion aws-sdk channel integration into `@sentry/aws-serverless`, where the OTel `Aws` integration ships as a default today. `@sentry/node` exposes a reusable `applyDiagnosticsChannelInjectionIntegrations` helper (extracted from its `getDefaultIntegrations`), and `@sentry/aws-serverless` uses it to swap the OTel `Aws` integration for the channel version when the app opts in via `experimentalUseDiagnosticsChannelInjection()` (now re-exported from the aws-serverless SDK). No change when the opt-in isn't used. The existing `aws-integration` node-integration-tests assert the swapped origin via `isOrchestrionEnabled()`, so both the OTel and diagnostics-channel paths are covered by the same suites across both smithy stacks and ESM/CJS. Part of #20946 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 430affd commit a3978fa

7 files changed

Lines changed: 59 additions & 26 deletions

File tree

dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ const NODE_EXPORTS_IGNORE = [
2929
// factories for that same Node-runtime-only opt-in, so it isn't surfaced
3030
// through the framework / serverless SDKs either.
3131
'diagnosticsChannelInjectionIntegrations',
32+
// Helper for SDKs that build their own default-integration set (e.g. aws-serverless)
33+
// to apply the diagnostics-channel integration swap; not surfaced elsewhere.
34+
'applyDiagnosticsChannelInjectionIntegrations',
3235
// Internal helper only needed within integrations (e.g. bunRuntimeMetricsIntegration)
3336
'_INTERNAL_normalizeCollectionInterval',
3437
];

dev-packages/node-integration-tests/suites/aws-serverless/aws-integration-streamed/test.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import type { SerializedStreamedSpanContainer } from '@sentry/core';
22
import { afterAll, describe, expect } from 'vitest';
3+
import { isOrchestrionEnabled } from '../../../utils';
34
import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runner';
45

6+
// See the non-streamed `aws-integration` suite: only the origin differs between the OTel and
7+
// orchestrion diagnostics-channel runs.
8+
const ORIGIN = isOrchestrionEnabled() ? 'auto.aws.orchestrion.aws-sdk' : 'auto.otel.aws';
9+
510
// The aws-sdk instrumentation creates spans by patching the underlying smithy middleware stack. The
611
// patch target differs between aws-sdk versions, so we run the exact same assertions against both:
712
// - 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):
4954
name: 'S3.PutObject',
5055
status: 'ok',
5156
attributes: expect.objectContaining({
52-
'sentry.origin': { value: 'auto.otel.aws', type: 'string' },
57+
'sentry.origin': { value: ORIGIN, type: 'string' },
5358
'sentry.op': { value: 'rpc', type: 'string' },
5459
'rpc.system': { value: 'aws-api', type: 'string' },
5560
'rpc.method': { value: 'PutObject', type: 'string' },

dev-packages/node-integration-tests/suites/aws-serverless/aws-integration/test.ts

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import type { TransactionEvent } from '@sentry/core';
22
import { afterAll, describe, expect } from 'vitest';
3+
import { isOrchestrionEnabled } from '../../../utils';
34
import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runner';
45

6+
// The suite runs twice on CI: once with the OTel `Aws` integration (default) and once with the
7+
// orchestrion diagnostics-channel integration auto-injected (`INJECT_ORCHESTRION`). Both emit the
8+
// same spans; only the origin differs.
9+
const ORIGIN = isOrchestrionEnabled() ? 'auto.aws.orchestrion.aws-sdk' : 'auto.otel.aws';
10+
511
// The aws-sdk instrumentation creates spans by patching the underlying smithy middleware stack. The
612
// patch target differs between aws-sdk versions, so we run the exact same assertions against both:
713
// - 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 {
4046
expectSpan('S3.PutObject', {
4147
description: 'S3.PutObject',
4248
op: 'rpc',
43-
origin: 'auto.otel.aws',
49+
origin: ORIGIN,
4450
status: 'ok',
4551
data: expect.objectContaining({
46-
'sentry.origin': 'auto.otel.aws',
52+
'sentry.origin': ORIGIN,
4753
'sentry.op': 'rpc',
4854
'rpc.system': 'aws-api',
4955
'rpc.method': 'PutObject',
@@ -58,7 +64,7 @@ function assertAwsServiceSpans(transaction: TransactionEvent): void {
5864
expectSpan('S3.GetObject (success)', {
5965
description: 'S3.GetObject',
6066
op: 'rpc',
61-
origin: 'auto.otel.aws',
67+
origin: ORIGIN,
6268
status: 'ok',
6369
data: expect.objectContaining({ 'rpc.method': 'GetObject', 'rpc.service': 'S3', 'aws.s3.bucket': 'ot-demo-test' }),
6470
});
@@ -67,7 +73,7 @@ function assertAwsServiceSpans(transaction: TransactionEvent): void {
6773
expectSpan('S3.GetObject (error)', {
6874
description: 'S3.GetObject',
6975
op: 'rpc',
70-
origin: 'auto.otel.aws',
76+
origin: ORIGIN,
7177
status: 'internal_error',
7278
data: expect.objectContaining({ 'rpc.method': 'GetObject', 'rpc.service': 'S3' }),
7379
});
@@ -76,7 +82,7 @@ function assertAwsServiceSpans(transaction: TransactionEvent): void {
7682
expectSpan('DynamoDB.PutItem', {
7783
description: 'DynamoDB.PutItem',
7884
op: 'db',
79-
origin: 'auto.otel.aws',
85+
origin: ORIGIN,
8086
data: expect.objectContaining({
8187
'sentry.op': 'db',
8288
'rpc.method': 'PutItem',
@@ -92,7 +98,7 @@ function assertAwsServiceSpans(transaction: TransactionEvent): void {
9298
expectSpan('DynamoDB.Query', {
9399
description: 'DynamoDB.Query',
94100
op: 'db',
95-
origin: 'auto.otel.aws',
101+
origin: ORIGIN,
96102
data: expect.objectContaining({
97103
'rpc.method': 'Query',
98104
'db.operation': 'Query',
@@ -105,7 +111,7 @@ function assertAwsServiceSpans(transaction: TransactionEvent): void {
105111
expectSpan('SQS SendMessage', {
106112
description: 'my-queue send',
107113
op: 'rpc',
108-
origin: 'auto.otel.aws',
114+
origin: ORIGIN,
109115
data: expect.objectContaining({
110116
'rpc.method': 'SendMessage',
111117
'rpc.service': 'SQS',
@@ -121,7 +127,7 @@ function assertAwsServiceSpans(transaction: TransactionEvent): void {
121127
expectSpan('SQS ReceiveMessage', {
122128
description: 'my-queue receive',
123129
op: 'rpc',
124-
origin: 'auto.otel.aws',
130+
origin: ORIGIN,
125131
data: expect.objectContaining({
126132
'rpc.method': 'ReceiveMessage',
127133
'messaging.system': 'aws_sqs',
@@ -135,7 +141,7 @@ function assertAwsServiceSpans(transaction: TransactionEvent): void {
135141
expectSpan('SNS Publish', {
136142
description: 'my-topic send',
137143
op: 'rpc',
138-
origin: 'auto.otel.aws',
144+
origin: ORIGIN,
139145
data: expect.objectContaining({
140146
'rpc.method': 'Publish',
141147
'rpc.service': 'SNS',
@@ -150,7 +156,7 @@ function assertAwsServiceSpans(transaction: TransactionEvent): void {
150156
expectSpan('Lambda Invoke', {
151157
description: 'my-function Invoke',
152158
op: 'rpc',
153-
origin: 'auto.otel.aws',
159+
origin: ORIGIN,
154160
data: expect.objectContaining({
155161
'rpc.method': 'Invoke',
156162
'rpc.service': 'Lambda',
@@ -164,7 +170,7 @@ function assertAwsServiceSpans(transaction: TransactionEvent): void {
164170
expectSpan('Kinesis.PutRecord', {
165171
description: 'Kinesis.PutRecord',
166172
op: 'rpc',
167-
origin: 'auto.otel.aws',
173+
origin: ORIGIN,
168174
status: 'ok',
169175
data: expect.objectContaining({
170176
'rpc.method': 'PutRecord',
@@ -177,7 +183,7 @@ function assertAwsServiceSpans(transaction: TransactionEvent): void {
177183
expectSpan('SecretsManager.GetSecretValue', {
178184
description: 'SecretsManager.GetSecretValue',
179185
op: 'rpc',
180-
origin: 'auto.otel.aws',
186+
origin: ORIGIN,
181187
data: expect.objectContaining({
182188
'rpc.method': 'GetSecretValue',
183189
'rpc.service': 'SecretsManager',
@@ -189,7 +195,7 @@ function assertAwsServiceSpans(transaction: TransactionEvent): void {
189195
expectSpan('StepFunctions.StartExecution', {
190196
description: 'SFN.StartExecution',
191197
op: 'rpc',
192-
origin: 'auto.otel.aws',
198+
origin: ORIGIN,
193199
data: expect.objectContaining({
194200
'rpc.method': 'StartExecution',
195201
'rpc.service': 'SFN',

packages/aws-serverless/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,8 @@ export {
167167
metrics,
168168
spanStreamingIntegration,
169169
withStreamedSpan,
170+
experimentalUseDiagnosticsChannelInjection,
171+
diagnosticsChannelInjectionIntegrations,
170172
} from '@sentry/node';
171173

172174
export {

packages/aws-serverless/src/init.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import type { Integration, Options } from '@sentry/core';
22
import { applySdkMetadata, debug, getSDKSource } from '@sentry/core';
33
import type { NodeClient, NodeOptions } from '@sentry/node';
4-
import { getDefaultIntegrationsWithoutPerformance, initWithoutDefaultIntegrations } from '@sentry/node';
4+
import {
5+
applyDiagnosticsChannelInjectionIntegrations,
6+
getDefaultIntegrationsWithoutPerformance,
7+
initWithoutDefaultIntegrations,
8+
} from '@sentry/node';
59
import { envToBool } from '@sentry/node-core';
610
import { DEBUG_BUILD } from './debug-build';
711
import { awsIntegration } from './integration/aws';
@@ -49,8 +53,11 @@ function shouldDisableLayerExtensionForProxy(): boolean {
4953
*/
5054
// NOTE: in awslambda-auto.ts, we also call the original `getDefaultIntegrations` from `@sentry/node` to load performance integrations.
5155
// 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.
52-
export function getDefaultIntegrations(_options: Options): Integration[] {
53-
return [...getDefaultIntegrationsWithoutPerformance(), awsIntegration(), awsLambdaIntegration()];
56+
export function getDefaultIntegrations(options: Options): Integration[] {
57+
const integrations = [...getDefaultIntegrationsWithoutPerformance(), awsIntegration(), awsLambdaIntegration()];
58+
// If the app opted into diagnostics-channel injection, swap the OTel `Aws` integration for its
59+
// channel-based equivalent (registered in `@sentry/server-utils`). No-op otherwise.
60+
return applyDiagnosticsChannelInjectionIntegrations(integrations, options);
5461
}
5562

5663
export interface AwsServerlessOptions extends NodeOptions {

packages/node/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export {
4646
getDefaultIntegrations,
4747
getDefaultIntegrationsWithoutPerformance,
4848
initWithoutDefaultIntegrations,
49+
applyDiagnosticsChannelInjectionIntegrations,
4950
} from './sdk';
5051
export {
5152
experimentalUseDiagnosticsChannelInjection,

packages/node/src/sdk/index.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,24 @@ export function getDefaultIntegrations(options: Options): Integration[] {
3939
...(hasSpansEnabled(options) ? getAutoPerformanceIntegrations() : []),
4040
];
4141

42-
// When the app opted into diagnostics-channel injection (via
43-
// `experimentalUseDiagnosticsChannelInjection()`) AND span recording is
44-
// enabled, swap the channel-based integrations in place of OTel equivalents
45-
// so the two don't both instrument the same library.
46-
//
47-
// Every channel-based integration we ship today is a 1:1 replacement for an
48-
// OTel performance/tracing integration and produces nothing but spans (those
49-
// only come from `getAutoPerformanceIntegrations()` above), so it's gated on
50-
// span recording.
42+
return applyDiagnosticsChannelInjectionIntegrations(integrations, options);
43+
}
44+
45+
/**
46+
* When the app opted into diagnostics-channel injection (via
47+
* `experimentalUseDiagnosticsChannelInjection()`) AND span recording is enabled, swap the
48+
* channel-based integrations in place of their OTel equivalents so the two don't both instrument the
49+
* same library. Otherwise returns `integrations` unchanged.
50+
*
51+
* Every channel-based integration we ship today is a 1:1 replacement for an OTel performance/tracing
52+
* integration and produces nothing but spans, so this is gated on span recording. Exported so SDKs
53+
* that build their own default-integration set (e.g. `@sentry/aws-serverless`) can apply the same
54+
* swap instead of duplicating this logic.
55+
*/
56+
export function applyDiagnosticsChannelInjectionIntegrations(
57+
integrations: Integration[],
58+
options: Options,
59+
): Integration[] {
5160
if (isDiagnosticsChannelInjectionEnabled() && hasSpansEnabled(options)) {
5261
const diagnosticsChannelInjection = resolveDiagnosticsChannelInjection();
5362
if (diagnosticsChannelInjection) {

0 commit comments

Comments
 (0)