-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
feat(aws): Add experimental AWS Lambda extension for tunnelling events #17525
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
16 changes: 16 additions & 0 deletions
...st-applications/aws-serverless/src/lambda-functions-layer/ExperimentalExtension/index.mjs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import * as Sentry from '@sentry/aws-serverless'; | ||
|
||
Sentry.init({ | ||
dsn: process.env.SENTRY_DSN, | ||
tracesSampleRate: 1, | ||
debug: true, | ||
_experiments: { | ||
enableLambdaExtension: true, | ||
}, | ||
}); | ||
|
||
export const handler = async (event, context) => { | ||
Sentry.startSpan({ name: 'manual-span', op: 'test' }, async () => { | ||
return 'Hello, world!'; | ||
}); | ||
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -53,7 +53,7 @@ export function makeBaseBundleConfig(options) { | |
}, | ||
}, | ||
context: 'window', | ||
plugins: [rrwebBuildPlugin, markAsBrowserBuildPlugin], | ||
plugins: [rrwebBuildPlugin, markAsBrowserBuildPlugin, licensePlugin], | ||
}; | ||
|
||
// used by `@sentry/wasm` & pluggable integrations from core/browser (bundles which need to be combined with a stand-alone SDK bundle) | ||
|
@@ -87,14 +87,23 @@ export function makeBaseBundleConfig(options) { | |
// code to add after the CJS wrapper | ||
footer: '}(window));', | ||
}, | ||
plugins: [rrwebBuildPlugin, markAsBrowserBuildPlugin], | ||
plugins: [rrwebBuildPlugin, markAsBrowserBuildPlugin, licensePlugin], | ||
}; | ||
|
||
const workerBundleConfig = { | ||
output: { | ||
format: 'esm', | ||
}, | ||
plugins: [commonJSPlugin, makeTerserPlugin()], | ||
plugins: [commonJSPlugin, makeTerserPlugin(), licensePlugin], | ||
// Don't bundle any of Node's core modules | ||
external: builtinModules, | ||
}; | ||
|
||
const awsLambdaExtensionBundleConfig = { | ||
output: { | ||
format: 'esm', | ||
}, | ||
plugins: [commonJSPlugin, makeIsDebugBuildPlugin(true), makeTerserPlugin()], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Missing License Plugin in Lambda Extension ConfigThe |
||
// Don't bundle any of Node's core modules | ||
external: builtinModules, | ||
}; | ||
|
@@ -110,14 +119,15 @@ export function makeBaseBundleConfig(options) { | |
strict: false, | ||
esModule: false, | ||
}, | ||
plugins: [sucrasePlugin, nodeResolvePlugin, cleanupPlugin, licensePlugin], | ||
plugins: [sucrasePlugin, nodeResolvePlugin, cleanupPlugin], | ||
treeshake: 'smallest', | ||
}; | ||
|
||
const bundleTypeConfigMap = { | ||
standalone: standAloneBundleConfig, | ||
addon: addOnBundleConfig, | ||
'node-worker': workerBundleConfig, | ||
'lambda-extension': awsLambdaExtensionBundleConfig, | ||
}; | ||
|
||
return deepMerge.all([sharedBundleConfig, bundleTypeConfigMap[bundleType], packageSpecificConfig || {}], { | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
15 changes: 15 additions & 0 deletions
packages/aws-serverless/rollup.lambda-extension.config.mjs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { makeBaseBundleConfig } from '@sentry-internal/rollup-utils'; | ||
|
||
export default [ | ||
makeBaseBundleConfig({ | ||
bundleType: 'lambda-extension', | ||
entrypoints: ['src/lambda-extension/index.ts'], | ||
outputFileBase: 'index.mjs', | ||
packageSpecificConfig: { | ||
output: { | ||
dir: 'build/aws/dist-serverless/sentry-extension', | ||
sourcemap: false, | ||
}, | ||
}, | ||
}), | ||
]; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
145 changes: 145 additions & 0 deletions
packages/aws-serverless/src/lambda-extension/aws-lambda-extension.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
import * as http from 'node:http'; | ||
import { buffer } from 'node:stream/consumers'; | ||
import { debug, dsnFromString, getEnvelopeEndpointWithUrlEncodedAuth } from '@sentry/core'; | ||
import { DEBUG_BUILD } from './debug-build'; | ||
|
||
/** | ||
* The Extension API Client. | ||
*/ | ||
export class AwsLambdaExtension { | ||
private readonly _baseUrl: string; | ||
private _extensionId: string | null; | ||
|
||
public constructor() { | ||
this._baseUrl = `http://${process.env.AWS_LAMBDA_RUNTIME_API}/2020-01-01/extension`; | ||
this._extensionId = null; | ||
} | ||
|
||
/** | ||
* Register this extension as an external extension with AWS. | ||
*/ | ||
public async register(): Promise<void> { | ||
const res = await fetch(`${this._baseUrl}/register`, { | ||
method: 'POST', | ||
body: JSON.stringify({ | ||
events: ['INVOKE', 'SHUTDOWN'], | ||
}), | ||
headers: { | ||
'Content-Type': 'application/json', | ||
'Lambda-Extension-Name': 'sentry-extension', | ||
}, | ||
}); | ||
|
||
if (!res.ok) { | ||
throw new Error(`Failed to register with the extension API: ${await res.text()}`); | ||
} | ||
|
||
this._extensionId = res.headers.get('lambda-extension-identifier'); | ||
} | ||
|
||
/** | ||
* Advances the extension to the next event. | ||
*/ | ||
public async next(): Promise<void> { | ||
if (!this._extensionId) { | ||
throw new Error('Extension ID is not set'); | ||
} | ||
|
||
const res = await fetch(`${this._baseUrl}/event/next`, { | ||
headers: { | ||
'Lambda-Extension-Identifier': this._extensionId, | ||
'Content-Type': 'application/json', | ||
}, | ||
}); | ||
|
||
if (!res.ok) { | ||
throw new Error(`Failed to advance to next event: ${await res.text()}`); | ||
} | ||
} | ||
|
||
/** | ||
* Reports an error to the extension API. | ||
* @param phase The phase of the extension. | ||
* @param err The error to report. | ||
*/ | ||
public async error(phase: 'init' | 'exit', err: Error): Promise<never> { | ||
if (!this._extensionId) { | ||
throw new Error('Extension ID is not set'); | ||
} | ||
|
||
const errorType = `Extension.${err.name || 'UnknownError'}`; | ||
|
||
const res = await fetch(`${this._baseUrl}/${phase}/error`, { | ||
method: 'POST', | ||
body: JSON.stringify({ | ||
errorMessage: err.message || err.toString(), | ||
errorType, | ||
stackTrace: [err.stack], | ||
}), | ||
headers: { | ||
'Content-Type': 'application/json', | ||
'Lambda-Extension-Identifier': this._extensionId, | ||
'Lambda-Extension-Function-Error': errorType, | ||
}, | ||
}); | ||
|
||
if (!res.ok) { | ||
DEBUG_BUILD && debug.error(`Failed to report error: ${await res.text()}`); | ||
} | ||
|
||
throw err; | ||
} | ||
|
||
/** | ||
* Starts the Sentry tunnel. | ||
*/ | ||
public startSentryTunnel(): void { | ||
const server = http.createServer(async (req, res) => { | ||
if (req.method === 'POST' && req.url?.startsWith('/envelope')) { | ||
try { | ||
const buf = await buffer(req); | ||
// Extract the actual bytes from the Buffer by slicing its underlying ArrayBuffer | ||
// This ensures we get only the data portion without any padding or offset | ||
const envelopeBytes = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength); | ||
const envelope = new TextDecoder().decode(envelopeBytes); | ||
const piece = envelope.split('\n')[0]; | ||
const header = JSON.parse(piece || '{}') as { dsn?: string }; | ||
if (!header.dsn) { | ||
throw new Error('DSN is not set'); | ||
} | ||
const dsn = dsnFromString(header.dsn); | ||
if (!dsn) { | ||
throw new Error('Invalid DSN'); | ||
} | ||
const upstreamSentryUrl = getEnvelopeEndpointWithUrlEncodedAuth(dsn); | ||
|
||
fetch(upstreamSentryUrl, { | ||
method: 'POST', | ||
body: envelopeBytes, | ||
}).catch(err => { | ||
DEBUG_BUILD && debug.error('Error sending envelope to Sentry', err); | ||
}); | ||
|
||
res.writeHead(200, { 'Content-Type': 'application/json' }); | ||
res.end(JSON.stringify({})); | ||
} catch (e) { | ||
DEBUG_BUILD && debug.error('Error tunneling to Sentry', e); | ||
res.writeHead(500, { 'Content-Type': 'application/json' }); | ||
res.end(JSON.stringify({ error: 'Error tunneling to Sentry' })); | ||
} | ||
} else { | ||
res.writeHead(404, { 'Content-Type': 'application/json' }); | ||
res.end(JSON.stringify({ error: 'Not found' })); | ||
} | ||
}); | ||
cursor[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
server.listen(9000, () => { | ||
DEBUG_BUILD && debug.log('Sentry proxy listening on port 9000'); | ||
msonnb marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}); | ||
|
||
server.on('error', err => { | ||
DEBUG_BUILD && debug.error('Error starting Sentry proxy', err); | ||
process.exit(1); | ||
}); | ||
} | ||
} |
8 changes: 8 additions & 0 deletions
packages/aws-serverless/src/lambda-extension/debug-build.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
declare const __DEBUG_BUILD__: boolean; | ||
|
||
/** | ||
* This serves as a build time flag that will be true by default, but false in non-debug builds or if users replace `__SENTRY_DEBUG__` in their generated code. | ||
* | ||
* ATTENTION: This constant must never cross package boundaries (i.e. be exported) to guarantee that it can be used for tree shaking. | ||
*/ | ||
export const DEBUG_BUILD = __DEBUG_BUILD__; |
21 changes: 21 additions & 0 deletions
packages/aws-serverless/src/lambda-extension/index.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
#!/usr/bin/env node | ||
import { debug } from '@sentry/core'; | ||
import { AwsLambdaExtension } from './aws-lambda-extension'; | ||
import { DEBUG_BUILD } from './debug-build'; | ||
|
||
async function main(): Promise<void> { | ||
const extension = new AwsLambdaExtension(); | ||
|
||
await extension.register(); | ||
|
||
extension.startSentryTunnel(); | ||
|
||
// eslint-disable-next-line no-constant-condition | ||
while (true) { | ||
await extension.next(); | ||
} | ||
} | ||
|
||
main().catch(err => { | ||
DEBUG_BUILD && debug.error('Error in Lambda Extension', err); | ||
}); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.