Skip to content

Commit f38f181

Browse files
committed
feat(aws): Support automatic wrapping in ESM
1 parent 05af8d0 commit f38f181

File tree

18 files changed

+711
-131
lines changed

18 files changed

+711
-131
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const handler = async () => {
2+
throw new Error('test esm');
3+
};

dev-packages/e2e-tests/test-applications/aws-serverless/src/lambda-functions-layer/TracingEsm/index.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as Sentry from '@sentry/aws-serverless';
22

33
import * as http from 'node:http';
44

5-
export const handler = Sentry.wrapHandler(async () => {
5+
export const handler = async () => {
66
await Sentry.startSpan({ name: 'manual-span', op: 'test' }, async () => {
77
await new Promise(resolve => {
88
http.get('http://example.com', res => {
@@ -16,4 +16,4 @@ export const handler = Sentry.wrapHandler(async () => {
1616
});
1717
});
1818
});
19-
});
19+
};

dev-packages/e2e-tests/test-applications/aws-serverless/src/lambda-functions-npm/TracingCjs/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const http = require('http');
22
const Sentry = require('@sentry/aws-serverless');
33

4-
exports.handler = Sentry.wrapHandler(async () => {
4+
exports.handler = async () => {
55
await new Promise(resolve => {
66
const req = http.request(
77
{
@@ -21,4 +21,4 @@ exports.handler = Sentry.wrapHandler(async () => {
2121
});
2222

2323
Sentry.startSpan({ name: 'manual-span', op: 'manual' }, () => {});
24-
});
24+
};

dev-packages/e2e-tests/test-applications/aws-serverless/src/lambda-functions-npm/TracingEsm/index.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as http from 'node:http';
22
import * as Sentry from '@sentry/aws-serverless';
33

4-
export const handler = Sentry.wrapHandler(async () => {
4+
export const handler = async () => {
55
await new Promise(resolve => {
66
const req = http.request(
77
{
@@ -21,4 +21,4 @@ export const handler = Sentry.wrapHandler(async () => {
2121
});
2222

2323
Sentry.startSpan({ name: 'manual-span', op: 'manual' }, () => {});
24-
});
24+
};

dev-packages/e2e-tests/test-applications/aws-serverless/tests/layer.test.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,35 @@ test.describe('Lambda layer', () => {
160160
type: 'Error',
161161
value: 'test',
162162
mechanism: {
163-
type: 'auto.function.aws-serverless.handler',
163+
type: 'auto.function.aws-serverless.otel',
164+
handled: false,
165+
},
166+
}),
167+
);
168+
});
169+
170+
test('capturing errors works in ESM', async ({ lambdaClient }) => {
171+
const errorEventPromise = waitForError('aws-serverless-lambda-sam', errorEvent => {
172+
return errorEvent?.exception?.values?.[0]?.value === 'test esm';
173+
});
174+
175+
await lambdaClient.send(
176+
new InvokeCommand({
177+
FunctionName: 'LayerErrorEsm',
178+
Payload: JSON.stringify({}),
179+
}),
180+
);
181+
182+
const errorEvent = await errorEventPromise;
183+
184+
// shows the SDK sent an error event
185+
expect(errorEvent.exception?.values).toHaveLength(1);
186+
expect(errorEvent.exception?.values?.[0]).toEqual(
187+
expect.objectContaining({
188+
type: 'Error',
189+
value: 'test esm',
190+
mechanism: {
191+
type: 'auto.function.aws-serverless.otel',
164192
handled: false,
165193
},
166194
}),

packages/aws-serverless/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@
6767
"dependencies": {
6868
"@opentelemetry/api": "^1.9.0",
6969
"@opentelemetry/instrumentation": "^0.203.0",
70-
"@opentelemetry/instrumentation-aws-lambda": "0.54.0",
7170
"@opentelemetry/instrumentation-aws-sdk": "0.56.0",
71+
"@opentelemetry/semantic-conventions": "^1.36.0",
7272
"@sentry/core": "10.5.0",
7373
"@sentry/node": "10.5.0",
7474
"@types/aws-lambda": "^8.10.62"

packages/aws-serverless/src/awslambda-auto.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,6 @@ if (lambdaTaskRoot) {
2222
: {},
2323
),
2424
});
25-
26-
if (typeof require !== 'undefined') {
27-
Sentry.tryPatchHandler(lambdaTaskRoot, handlerString);
28-
}
2925
} else {
3026
throw Error('LAMBDA_TASK_ROOT environment variable is not set');
3127
}

packages/aws-serverless/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,5 +147,6 @@ export {
147147
export { awsIntegration } from './integration/aws';
148148
export { awsLambdaIntegration } from './integration/awslambda';
149149

150-
export { getDefaultIntegrations, init, tryPatchHandler, wrapHandler } from './sdk';
150+
export { getDefaultIntegrations, init } from './init';
151+
export { wrapHandler } from './sdk';
151152
export type { WrapperOptions } from './sdk';

packages/aws-serverless/src/init.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import type { Integration, Options } from '@sentry/core';
2+
import { applySdkMetadata, getSDKSource } from '@sentry/core';
3+
import type { NodeClient, NodeOptions } from '@sentry/node';
4+
import { getDefaultIntegrationsWithoutPerformance, initWithoutDefaultIntegrations } from '@sentry/node';
5+
import { awsIntegration } from './integration/aws';
6+
import { awsLambdaIntegration } from './integration/awslambda';
7+
8+
/**
9+
* Get the default integrations for the AWSLambda SDK.
10+
*/
11+
// NOTE: in awslambda-auto.ts, we also call the original `getDefaultIntegrations` from `@sentry/node` to load performance integrations.
12+
// 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.
13+
export function getDefaultIntegrations(_options: Options): Integration[] {
14+
return [...getDefaultIntegrationsWithoutPerformance(), awsIntegration(), awsLambdaIntegration()];
15+
}
16+
17+
/**
18+
* Initializes the Sentry AWS Lambda SDK.
19+
*
20+
* @param options Configuration options for the SDK, @see {@link AWSLambdaOptions}.
21+
*/
22+
export function init(options: NodeOptions = {}): NodeClient | undefined {
23+
const opts = {
24+
defaultIntegrations: getDefaultIntegrations(options),
25+
...options,
26+
};
27+
28+
applySdkMetadata(opts, 'aws-serverless', ['aws-serverless'], getSDKSource());
29+
30+
return initWithoutDefaultIntegrations(opts);
31+
}

packages/aws-serverless/src/integration/awslambda.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { AwsLambdaInstrumentation } from '@opentelemetry/instrumentation-aws-lambda';
21
import type { IntegrationFn } from '@sentry/core';
32
import { defineIntegration, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core';
4-
import { generateInstrumentOnce } from '@sentry/node';
5-
import { eventContextExtractor } from '../utils';
3+
import { captureException, generateInstrumentOnce } from '@sentry/node';
4+
import { eventContextExtractor, markEventUnhandled } from '../utils';
5+
import { AwsLambdaInstrumentation } from './instrumentation-aws-lambda/instrumentation';
66

77
interface AwsLambdaOptions {
88
/**
@@ -27,6 +27,11 @@ export const instrumentAwsLambda = generateInstrumentOnce(
2727
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, 'auto.otel.aws-lambda');
2828
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'function.aws.lambda');
2929
},
30+
responseHook(_span, { err }) {
31+
if (err) {
32+
captureException(err, scope => markEventUnhandled(scope, 'auto.function.aws-serverless.otel'));
33+
}
34+
},
3035
};
3136
},
3237
);

0 commit comments

Comments
 (0)