Skip to content

Commit 126ea96

Browse files
authored
feat(aws-serverless): Use orchestrion aws-sdk integration under diagnostics-channel opt-in (#22143)
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 (latest + legacy `3.1041.0`) and ESM/CJS. Only the expected origin is parametrized. Part of #20946
1 parent 9646b10 commit 126ea96

7 files changed

Lines changed: 76 additions & 25 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
@@ -31,6 +31,9 @@ const NODE_EXPORTS_IGNORE = [
3131
'diagnosticsChannelInjectionIntegrations',
3232
// Companion to the above two, same reasoning (Next.js re-exports it via `export * from '@sentry/node'`)
3333
'isDiagnosticsChannelInjectionEnabled',
34+
// Helper for SDKs that build their own default-integration set (e.g. aws-serverless)
35+
// to apply the diagnostics-channel integration swap; not surfaced elsewhere.
36+
'applyDiagnosticsChannelInjectionIntegrations',
3437
// Internal helper only needed within integrations (e.g. bunRuntimeMetricsIntegration)
3538
'_INTERNAL_normalizeCollectionInterval',
3639
];

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

Lines changed: 7 additions & 5 deletions
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' },
@@ -221,10 +226,7 @@ describe('awsIntegration (streamed)', () => {
221226
await createTestRunner().ignore('event').expect({ span: assertAwsServiceSpans }).start().completed();
222227
});
223228
},
224-
// The orchestrion aws-sdk channel integration has no service extensions yet (empty registry),
225-
// so it can't emit the service-specific attributes asserted here. Stay on the OTel path until
226-
// the service extensions land in a follow-up.
227-
{ additionalDependencies, injectOrchestrion: false },
229+
{ additionalDependencies },
228230
);
229231
});
230232
});

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

Lines changed: 20 additions & 17 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',
@@ -216,10 +222,7 @@ describe('awsIntegration', () => {
216222
await createTestRunner().ignore('event').expect({ transaction: assertAwsServiceSpans }).start().completed();
217223
});
218224
},
219-
// The orchestrion aws-sdk channel integration has no service extensions yet (empty registry),
220-
// so it can't emit the service-specific attributes asserted here. Stay on the OTel path until
221-
// the service extensions land in a follow-up.
222-
{ additionalDependencies, injectOrchestrion: false },
225+
{ additionalDependencies },
223226
);
224227
});
225228
});

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: 12 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,13 @@ 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, the OTel `Aws` integration is swapped for
59+
// its channel-based equivalent AND the full channel-integration set is appended (mysql, postgres,
60+
// express, ...), giving opted-in apps performance coverage this SDK's defaults otherwise omit.
61+
// No-op otherwise.
62+
return applyDiagnosticsChannelInjectionIntegrations(integrations, options);
5463
}
5564

5665
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: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,37 @@ export function getDefaultIntegrations(options: Options): Integration[] {
4040
];
4141
}
4242

43+
/**
44+
* When the app opted into diagnostics-channel injection (via
45+
* `experimentalUseDiagnosticsChannelInjection()`) AND span recording is enabled, drop the OTel
46+
* integrations that have a channel-based replacement and append the FULL channel-integration set,
47+
* so the two never both instrument the same library. Otherwise returns `integrations` unchanged.
48+
*
49+
* `_init` applies the same swap to `defaultIntegrations`, but SDKs that seed their integrations
50+
* through the user `integrations` option instead (e.g. the `@sentry/aws-serverless` Lambda layer
51+
* entry) never hit that path, so they call this directly from their own `getDefaultIntegrations`.
52+
*
53+
* Note the asymmetry: appended channel integrations are not limited to ones whose OTel counterpart
54+
* was in `integrations`. For `@sentry/node` that makes no difference (the incoming list carries the
55+
* whole OTel performance set), but a caller with a narrower list (e.g. `@sentry/aws-serverless`)
56+
* gains channel coverage for libraries it never shipped OTel integrations for. Channel integrations
57+
* produce nothing but spans, so this is gated on span recording. Exported so SDKs that build their
58+
* own default-integration set can apply the same logic instead of duplicating it.
59+
*/
60+
export function applyDiagnosticsChannelInjectionIntegrations(
61+
integrations: Integration[],
62+
options: Options,
63+
): Integration[] {
64+
if (isDiagnosticsChannelInjectionEnabled() && hasSpansEnabled(options)) {
65+
const diagnosticsChannelInjection = resolveDiagnosticsChannelInjection();
66+
if (diagnosticsChannelInjection) {
67+
const replaced = new Set(diagnosticsChannelInjection.replacedOtelIntegrationNames);
68+
return [...integrations.filter(i => !replaced.has(i.name)), ...diagnosticsChannelInjection.integrations];
69+
}
70+
}
71+
return integrations;
72+
}
73+
4374
/**
4475
* Initialize Sentry for Node.
4576
*/

0 commit comments

Comments
 (0)