fix(lambda): handle missing context for some lambda functions#7445
fix(lambda): handle missing context for some lambda functions#7445purple4reina merged 6 commits intomasterfrom
Conversation
Lambda Authorizers and some other Lambda handler types do not receive a context object - they only receive an event parameter. Previously, the extractContext() function would throw "Could not extract context" when it couldn't find a context object with getRemainingTimeInMillis. This change makes extractContext() return undefined instead of throwing, and updates the datadog() wrapper to skip timeout checking when context is not available. This allows Lambda Authorizers to be instrumented without crashing. Fixes: DataDog/datadog-lambda-js#721 Co-Authored-By: Claude Opus 4.5 <[email protected]>
Overall package sizeSelf size: 4.58 MB Dependency sizes| name | version | self size | total size | |------|---------|-----------|------------| | import-in-the-middle | 2.0.6 | 81.92 kB | 813.08 kB | | dc-polyfill | 0.1.10 | 26.73 kB | 26.73 kB |🤖 This report was automatically generated by heaviest-objects-in-the-universe |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #7445 +/- ##
==========================================
- Coverage 80.42% 80.31% -0.12%
==========================================
Files 732 731 -1
Lines 31055 31134 +79
==========================================
+ Hits 24975 25004 +29
- Misses 6080 6130 +50
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
BenchmarksBenchmark execution time: 2026-02-10 18:49:29 Comparing candidate commit b4f3039 in PR branch Found 0 performance improvements and 0 performance regressions! Performance is the same for 231 metrics, 29 unstable metrics. |
This comment has been minimized.
This comment has been minimized.
| } | ||
| let context = args.length > 1 ? args[1] : null | ||
| if (context === null || context.getRemainingTimeInMillis === undefined) { | ||
| context = args.length > 2 ? args[2] : null |
There was a problem hiding this comment.
I think we missed the second check where we ensure that the second context has the things we care about? Does it matter?
There was a problem hiding this comment.
I believe you are correct. Means there's a hole in our testing too. I'll fix.
Co-authored-by: Ruben Bridgewater <[email protected]>
7188456 to
77cfede
Compare
BridgeAR
left a comment
There was a problem hiding this comment.
The logic change is LGTM. Our linter is just unhappy 😅
I would be nice to abstract the tests in index.spec.js further instead of adding the context.spec.js tests.
| try { | ||
| await datadog(app.authorizerErrorHandler)(_event) | ||
| assert.fail('Expected error to be thrown') | ||
| } catch (e) { | ||
| assert.strictEqual(e.name, 'AuthorizationError') | ||
| assert.strictEqual(e.message, 'Unauthorized') | ||
| } |
There was a problem hiding this comment.
| try { | |
| await datadog(app.authorizerErrorHandler)(_event) | |
| assert.fail('Expected error to be thrown') | |
| } catch (e) { | |
| assert.strictEqual(e.name, 'AuthorizationError') | |
| assert.strictEqual(e.message, 'Unauthorized') | |
| } | |
| await assert.rejects( | |
| datadog(app.authorizerErrorHandler)(_event), | |
| { name: 'AuthorizationError', message: 'Unauthorized' } | |
| ) |
| const checkTraces = agent.assertSomeTraces((_traces) => { | ||
| const traces = _traces[0] | ||
| assert.strictEqual(traces.length, 1) | ||
| traces.forEach((trace) => { | ||
| assert.strictEqual(trace.error, 1) | ||
| }) | ||
| }) | ||
| await checkTraces |
There was a problem hiding this comment.
Nit
| const checkTraces = agent.assertSomeTraces((_traces) => { | |
| const traces = _traces[0] | |
| assert.strictEqual(traces.length, 1) | |
| traces.forEach((trace) => { | |
| assert.strictEqual(trace.error, 1) | |
| }) | |
| }) | |
| await checkTraces | |
| await agent.assertSomeTraces(([traces]) => { | |
| assert.strictEqual(traces.length, 1) | |
| assert.strictEqual(traces[0].error, 1) | |
| }) |
| @@ -0,0 +1,40 @@ | |||
| const assert = require('node:assert/strict') | |||
| const { afterEach, beforeEach, describe, it } = require('mocha') | |||
| const { extractContext } = require('../../src/lambda/context') | |||
There was a problem hiding this comment.
I understand that this creates a higher coverage for the particular internal method.
If I could choose, I would just rather always solely test public APIs or entry points as we do with the other tests. That way it is easier to refactor internals without having to change tests as well as having a stronger safe guard how the user would ever reach such state. Testing internal methods could at some point not be used anymore while still showing full coverage by being tested directly.
| // Set the desired handler to patch | ||
| process.env.DD_LAMBDA_HANDLER = 'handler.authorizerErrorHandler' | ||
| // Load the agent and re-register hook for patching. | ||
| await loadAgent() | ||
|
|
||
| const _event = { | ||
| type: 'REQUEST', | ||
| methodArn: 'arn:aws:execute-api:us-east-1:123456789012:api-id/stage/GET/resource', | ||
| } | ||
|
|
||
| // Mock `datadog-lambda` handler resolve and import. | ||
| const _handlerPath = path.resolve(__dirname, './fixtures/handler.js') | ||
| const app = require(_handlerPath) | ||
| datadog = require('./fixtures/datadog-lambda') |
There was a problem hiding this comment.
These lines are identical in all tests new created tests here besides for the environment variable. What about abstracting the tests here to something like:
const tests = [{ name: 'should do foo', env: 'FOO', error: 1, fn() { ... } }, ...]
for (const test of tests) {
it(test.name, async () => {
process.env.DD_LAMBDA_HANDLER = test.env
await loadAgent()
...
const assertionPromise = agent.assertSomeTraces(([traces]) => {
assert.strictEqual(traces.length, 1)
assert.strictEqual(traces[0].error, test.error)
})
await Promise.all([fn(), assertionPromise])
})
}That way creating new test cases is not a lot of code and we could likely add enough test cases here instead of using the new context.spec.js tests.
* fix(lambda): handle missing context for Lambda Authorizers Lambda Authorizers and some other Lambda handler types do not receive a context object - they only receive an event parameter. Previously, the extractContext() function would throw "Could not extract context" when it couldn't find a context object with getRemainingTimeInMillis. This change makes extractContext() return undefined instead of throwing, and updates the datadog() wrapper to skip timeout checking when context is not available. This allows Lambda Authorizers to be instrumented without crashing. Fixes: DataDog/datadog-lambda-js#721 Co-Authored-By: Claude Opus 4.5 <[email protected]> * Undefined cleanup. * Add getRemainingTimeInMillis check back. * Update packages/dd-trace/src/lambda/handler.js Co-authored-by: Ruben Bridgewater <[email protected]> * Move extractContext to separate file and add more tesets. * Linting. --------- Co-authored-by: Claude Opus 4.5 <[email protected]> Co-authored-by: Rey Abolofia <[email protected]> Co-authored-by: Rey Abolofia <[email protected]> Co-authored-by: Ruben Bridgewater <[email protected]>
* fix(lambda): handle missing context for Lambda Authorizers Lambda Authorizers and some other Lambda handler types do not receive a context object - they only receive an event parameter. Previously, the extractContext() function would throw "Could not extract context" when it couldn't find a context object with getRemainingTimeInMillis. This change makes extractContext() return undefined instead of throwing, and updates the datadog() wrapper to skip timeout checking when context is not available. This allows Lambda Authorizers to be instrumented without crashing. Fixes: DataDog/datadog-lambda-js#721 Co-Authored-By: Claude Opus 4.5 <[email protected]> * Undefined cleanup. * Add getRemainingTimeInMillis check back. * Update packages/dd-trace/src/lambda/handler.js Co-authored-by: Ruben Bridgewater <[email protected]> * Move extractContext to separate file and add more tesets. * Linting. --------- Co-authored-by: Claude Opus 4.5 <[email protected]> Co-authored-by: Rey Abolofia <[email protected]> Co-authored-by: Rey Abolofia <[email protected]> Co-authored-by: Ruben Bridgewater <[email protected]>
* fix(lambda): handle missing context for Lambda Authorizers Lambda Authorizers and some other Lambda handler types do not receive a context object - they only receive an event parameter. Previously, the extractContext() function would throw "Could not extract context" when it couldn't find a context object with getRemainingTimeInMillis. This change makes extractContext() return undefined instead of throwing, and updates the datadog() wrapper to skip timeout checking when context is not available. This allows Lambda Authorizers to be instrumented without crashing. Fixes: DataDog/datadog-lambda-js#721 Co-Authored-By: Claude Opus 4.5 <[email protected]> * Undefined cleanup. * Add getRemainingTimeInMillis check back. * Update packages/dd-trace/src/lambda/handler.js Co-authored-by: Ruben Bridgewater <[email protected]> * Move extractContext to separate file and add more tesets. * Linting. --------- Co-authored-by: Claude Opus 4.5 <[email protected]> Co-authored-by: Rey Abolofia <[email protected]> Co-authored-by: Rey Abolofia <[email protected]> Co-authored-by: Ruben Bridgewater <[email protected]>
It is possible to create a lambda function without including the context in the method signature - they only receive an event parameter. Previously, the
extractContext()function would throw "Could not extract context" when it couldn't find a context object withgetRemainingTimeInMillis.This change makes
extractContext()returnnullinstead of throwing, and updates thedatadog()wrapper to skip timeout checking when context is not available. This allows these types of functions to be instrumented without crashing.Fixes: DataDog/datadog-lambda-js#721
Please make sure your changes are properly tested!
What does this PR do?
Motivation
Additional Notes