Skip to content

Commit 723a8d6

Browse files
committed
more
1 parent b5cc6cb commit 723a8d6

11 files changed

Lines changed: 191 additions & 224 deletions

File tree

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import * as Sentry from '@sentry/aws-serverless';
2+
3+
Sentry.init({
4+
dsn: process.env.SENTRY_DSN,
5+
tracesSampleRate: 1,
6+
debug: true,
7+
_experiments: {
8+
enableLambdaExtension: true,
9+
},
10+
});
11+
12+
export const handler = async (event, context) => {
13+
Sentry.startSpan({ name: 'manual-span', op: 'test' }, async () => {
14+
return 'Hello, world!';
15+
});
16+
};

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

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,4 +242,52 @@ test.describe('Lambda layer', () => {
242242
}),
243243
);
244244
});
245+
246+
test('experimental extension works', async ({ lambdaClient }) => {
247+
const transactionEventPromise = waitForTransaction('aws-serverless-lambda-sam', transactionEvent => {
248+
return transactionEvent?.transaction === 'LayerExperimentalExtension';
249+
});
250+
251+
await lambdaClient.send(
252+
new InvokeCommand({
253+
FunctionName: 'LayerExperimentalExtension',
254+
Payload: JSON.stringify({}),
255+
}),
256+
);
257+
258+
const transactionEvent = await transactionEventPromise;
259+
260+
expect(transactionEvent.transaction).toEqual('LayerExperimentalExtension');
261+
expect(transactionEvent.contexts?.trace).toEqual({
262+
data: {
263+
'sentry.sample_rate': 1,
264+
'sentry.source': 'custom',
265+
'sentry.origin': 'auto.otel.aws-lambda',
266+
'sentry.op': 'function.aws.lambda',
267+
'cloud.account.id': '012345678912',
268+
'faas.execution': expect.any(String),
269+
'faas.id': 'arn:aws:lambda:us-east-1:012345678912:function:LayerExperimentalExtension',
270+
'faas.coldstart': true,
271+
'otel.kind': 'SERVER',
272+
},
273+
op: 'function.aws.lambda',
274+
origin: 'auto.otel.aws-lambda',
275+
span_id: expect.stringMatching(/[a-f0-9]{16}/),
276+
status: 'ok',
277+
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
278+
});
279+
280+
expect(transactionEvent.spans).toHaveLength(1);
281+
282+
expect(transactionEvent.spans).toContainEqual(
283+
expect.objectContaining({
284+
data: expect.objectContaining({
285+
'sentry.op': 'test',
286+
'sentry.origin': 'manual',
287+
}),
288+
description: 'manual-span',
289+
op: 'test',
290+
}),
291+
);
292+
});
245293
});

dev-packages/rollup-utils/bundleHelpers.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ export function makeBaseBundleConfig(options) {
104104
output: {
105105
format: 'esm',
106106
},
107-
plugins: [commonJSPlugin, makePackageNodeEsm()],
107+
plugins: [commonJSPlugin],
108108
// Don't bundle any of Node's core modules
109109
external: builtinModules,
110110
};
Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
1-
import { makeBaseBundleConfig, makeBundleConfigVariants } from '@sentry-internal/rollup-utils';
1+
import { makeBaseBundleConfig } from '@sentry-internal/rollup-utils';
22

33
export default [
4-
...makeBundleConfigVariants(
5-
makeBaseBundleConfig({
6-
bundleType: 'lambda-extension',
7-
entrypoints: ['src/lambda-extension/index.ts'],
8-
outputFileBase: () => 'index',
9-
packageSpecificConfig: {
10-
output: {
11-
dir: 'build/aws/dist-serverless/sentry-extension',
12-
sourcemap: false,
13-
},
4+
makeBaseBundleConfig({
5+
bundleType: 'lambda-extension',
6+
entrypoints: ['src/lambda-extension/index.ts'],
7+
outputFileBase: 'index.mjs',
8+
packageSpecificConfig: {
9+
output: {
10+
dir: 'build/aws/dist-serverless/sentry-extension',
11+
sourcemap: false,
1412
},
15-
}),
16-
{ variants: ['.js'] },
17-
),
13+
},
14+
}),
1815
];

packages/aws-serverless/scripts/buildLambdaLayer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ async function buildLambdaLayer(): Promise<void> {
4848
fsForceMkdirSync('./build/aws/dist-serverless/extensions');
4949
fs.copyFileSync('./src/lambda-extension/sentry-extension', './build/aws/dist-serverless/extensions/sentry-extension');
5050
fs.chmodSync('./build/aws/dist-serverless/extensions/sentry-extension', 0o755);
51-
fs.chmodSync('./build/aws/dist-serverless/sentry-extension/index.js', 0o755);
51+
fs.chmodSync('./build/aws/dist-serverless/sentry-extension/index.mjs', 0o755);
5252

5353
const zipFilename = `sentry-node-serverless-${version}.zip`;
5454
console.log(`Creating final layer zip file ${zipFilename}.`);

packages/aws-serverless/src/init.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import type { Integration, Options } from '@sentry/core';
2-
import { applySdkMetadata, getSDKSource } from '@sentry/core';
2+
import { applySdkMetadata, debug, getSDKSource } from '@sentry/core';
33
import type { NodeClient, NodeOptions } from '@sentry/node';
44
import { getDefaultIntegrationsWithoutPerformance, initWithoutDefaultIntegrations } from '@sentry/node';
5+
import { DEBUG_BUILD } from './debug-build';
56
import { awsIntegration } from './integration/aws';
67
import { awsLambdaIntegration } from './integration/awslambda';
7-
88
/**
99
* Get the default integrations for the AWSLambda SDK.
1010
*/
@@ -14,20 +14,30 @@ export function getDefaultIntegrations(_options: Options): Integration[] {
1414
return [...getDefaultIntegrationsWithoutPerformance(), awsIntegration(), awsLambdaIntegration()];
1515
}
1616

17+
export interface AwsServerlessOptions extends NodeOptions {
18+
_experiments?: NodeOptions['_experiments'] & {
19+
/**
20+
* If proxying Sentry events through the Sentry Lambda extension should be enabled.
21+
*/
22+
enableLambdaExtension?: boolean;
23+
};
24+
}
25+
1726
/**
1827
* Initializes the Sentry AWS Lambda SDK.
1928
*
2029
* @param options Configuration options for the SDK, @see {@link AWSLambdaOptions}.
2130
*/
22-
export function init(options: NodeOptions = {}): NodeClient | undefined {
31+
export function init(options: AwsServerlessOptions = {}): NodeClient | undefined {
2332
const opts = {
2433
defaultIntegrations: getDefaultIntegrations(options),
2534
...options,
2635
};
2736

2837
const sdkSource = getSDKSource();
2938

30-
if (sdkSource === 'aws-lambda-layer' && !opts.tunnel) {
39+
if (opts._experiments?.enableLambdaExtension && sdkSource === 'aws-lambda-layer' && !opts.tunnel) {
40+
DEBUG_BUILD && debug.log('Proxying Sentry events through the Sentry Lambda extension');
3141
opts.tunnel = 'http://localhost:9000/envelope';
3242
}
3343

packages/aws-serverless/src/lambda-extension/aws-lambda-extension.ts

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as http from 'node:http';
22
import { buffer } from 'node:stream/consumers';
3-
import { dsnStringToIngestEndpoint } from './utils';
3+
import { debug, dsnFromString, getEnvelopeEndpointWithUrlEncodedAuth } from '@sentry/core';
44

55
/**
66
* The Extension API Client.
@@ -16,9 +16,8 @@ export class AwsLambdaExtension {
1616

1717
/**
1818
* Register this extension as an external extension with AWS.
19-
* @returns The extension identifier, or null if the extension was not registered.
2019
*/
21-
public async register(): Promise<string | null> {
20+
public async register(): Promise<void> {
2221
const res = await fetch(`${this._baseUrl}/register`, {
2322
method: 'POST',
2423
body: JSON.stringify({
@@ -35,13 +34,10 @@ export class AwsLambdaExtension {
3534
}
3635

3736
this._extensionId = res.headers.get('lambda-extension-identifier');
38-
return res.headers.get('lambda-extension-identifier');
3937
}
4038

4139
/**
4240
* Advances the extension to the next event.
43-
* @param extensionId The extension identifier.
44-
* @returns The event data, or null if the extension is not ready to process events.
4541
*/
4642
public async next(): Promise<void> {
4743
if (!this._extensionId) {
@@ -59,14 +55,15 @@ export class AwsLambdaExtension {
5955
throw new Error(`Failed to advance to next event: ${await res.text()}`);
6056
}
6157

62-
const event = await res.json();
58+
const event = (await res.json()) as { eventType: string };
6359

64-
console.log('EXTENSION EVENT', event);
60+
if (event.eventType === 'SHUTDOWN') {
61+
await new Promise(resolve => setTimeout(resolve, 1000));
62+
}
6563
}
6664

6765
/**
6866
* Reports an error to the extension API.
69-
* @param extensionId The extension identifier.
7067
* @param phase The phase of the extension.
7168
* @param err The error to report.
7269
*/
@@ -92,7 +89,7 @@ export class AwsLambdaExtension {
9289
});
9390

9491
if (!res.ok) {
95-
throw new Error(`Failed to report error: ${await res.text()}`);
92+
debug.error(`Failed to report error: ${await res.text()}`);
9693
}
9794

9895
throw err;
@@ -110,34 +107,32 @@ export class AwsLambdaExtension {
110107
const envelope = new TextDecoder().decode(envelopeBytes);
111108
const piece = envelope.split('\n')[0];
112109
const header = JSON.parse(piece ?? '{}') as { dsn?: string };
113-
const upstreamSentryUrl = dsnStringToIngestEndpoint(header.dsn || '');
114-
115-
console.log('tunneling to sentry', { upstreamSentryUrl });
116-
console.log('headers', req.headers);
117-
118-
fetch(upstreamSentryUrl, {
110+
if (!header.dsn) {
111+
throw new Error('DSN is not set');
112+
}
113+
const dsn = dsnFromString(header.dsn);
114+
if (!dsn) {
115+
throw new Error('Invalid DSN');
116+
}
117+
const upstreamSentryUrl = getEnvelopeEndpointWithUrlEncodedAuth(dsn);
118+
119+
await fetch(upstreamSentryUrl, {
119120
method: 'POST',
120121
body: envelopeBytes,
121-
})
122-
.then(() => {
123-
console.log('tunneled to sentry');
124-
})
125-
.catch(err => {
126-
console.error('error tunneling to sentry', err);
127-
});
122+
});
128123

129124
res.writeHead(200, { 'Content-Type': 'application/json' });
130125
res.end(JSON.stringify({}));
131126
} catch (e) {
132-
console.error('error tunneling to sentry', e);
127+
debug.error('Error tunneling to Sentry', e);
133128
res.writeHead(500, { 'Content-Type': 'application/json' });
134-
res.end(JSON.stringify({ error: 'error tunneling to sentry' }));
129+
res.end(JSON.stringify({ error: 'Error tunneling to Sentry' }));
135130
}
136131
}
137132
});
138133

139134
server.listen(9000, () => {
140-
console.log('server listening on port 9000');
135+
debug.log('Sentry proxy listening on port 9000');
141136
});
142137
}
143138
}
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#!/usr/bin/env node
2+
import { debug } from '@sentry/core';
23
import { AwsLambdaExtension } from './aws-lambda-extension';
34

45
async function main(): Promise<void> {
@@ -8,12 +9,12 @@ async function main(): Promise<void> {
89

910
extension.startSentryTunnel();
1011

12+
// eslint-disable-next-line no-constant-condition
1113
while (true) {
1214
await extension.next();
1315
}
1416
}
1517

1618
main().catch(err => {
17-
console.error('Fatal error:', err);
18-
// process.exit(1);
19+
debug.error('Error in Lambda Extension', err);
1920
});

packages/aws-serverless/src/lambda-extension/sentry-extension

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ OWN_FILENAME="$(basename $0)"
55
LAMBDA_EXTENSION_NAME="$OWN_FILENAME" # (external) extension name has to match the filename
66

77
unset NODE_OPTIONS
8-
exec "/opt/${LAMBDA_EXTENSION_NAME}/index.js"
8+
exec "/opt/${LAMBDA_EXTENSION_NAME}/index.mjs"

0 commit comments

Comments
 (0)