Skip to content

Commit 8e9513b

Browse files
committed
feat(instrumentation-aws-lambda): remove callback-based handler support
BREAKING CHANGE: Removed support for callback-based Lambda handlers. Only Promise-based handlers (async/await or functions returning Promises) are now supported. This aligns with AWS Lambda's deprecation of callbacks in Node.js 24 runtime. Changes: - Removed Callback import and _wrapCallback method from instrumentation - Updated patchedHandler to only accept (event, context) parameters - Converted all test handlers from callback-based to Promise-based - Updated all tests to use Promise-based handlers directly - Added migration guide in README.md - Documented breaking change in CHANGELOG.md All 50 tests passing.
1 parent cf80246 commit 8e9513b

File tree

6 files changed

+72
-235
lines changed

6 files changed

+72
-235
lines changed

packages/instrumentation-aws-lambda/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
<!-- markdownlint-disable MD007 MD034 -->
22
# Changelog
33

4+
## [Unreleased]
5+
6+
### Breaking Changes
7+
8+
* **instrumentation-aws-lambda:** Removed support for callback-based Lambda handlers. Only Promise-based handlers (async/await or functions returning Promises) are now supported. This aligns with AWS Lambda's deprecation of callbacks in Node.js 24 runtime. Users must migrate their handlers to Promise-based format before upgrading.
9+
410
## [0.60.1](https://github.com/open-telemetry/opentelemetry-js-contrib/compare/instrumentation-aws-lambda-v0.60.0...instrumentation-aws-lambda-v0.60.1) (2025-11-24)
511

612

packages/instrumentation-aws-lambda/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,25 @@ npm install --save @opentelemetry/instrumentation-aws-lambda
2323

2424
- This package will instrument the lambda execution regardless of versions.
2525

26+
## Important Notes
27+
28+
### Callback-Based Handlers Deprecated
29+
30+
As of AWS Lambda Node.js 24 runtime, callback-based handlers are deprecated. This instrumentation now only supports Promise-based handlers (async/await or functions returning Promises). If you're using callback-based handlers, you must migrate to Promise-based handlers before upgrading to Node.js 24 runtime.
31+
32+
Example migration:
33+
```js
34+
// ❌ Deprecated callback-based handler
35+
exports.handler = function(event, context, callback) {
36+
callback(null, 'ok');
37+
};
38+
39+
// ✅ Promise-based handler
40+
exports.handler = async function(event, context) {
41+
return 'ok';
42+
};
43+
```
44+
2645
## Usage
2746

2847
Create a file to initialize the instrumentation, such as `lambda-wrapper.js`.

packages/instrumentation-aws-lambda/src/instrumentation.ts

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ import { ATTR_FAAS_EXECUTION, ATTR_FAAS_ID } from './semconv-obsolete';
4545

4646
import {
4747
APIGatewayProxyEventHeaders,
48-
Callback,
4948
Context,
5049
Handler,
5150
StreamifyHandler,
@@ -277,8 +276,7 @@ export class AwsLambdaInstrumentation extends InstrumentationBase<AwsLambdaInstr
277276
// The event can be a user type, it truly is any.
278277
// eslint-disable-next-line @typescript-eslint/no-explicit-any
279278
event: any,
280-
context: Context,
281-
callback: Callback
279+
context: Context
282280
) {
283281
_onRequest();
284282

@@ -294,16 +292,13 @@ export class AwsLambdaInstrumentation extends InstrumentationBase<AwsLambdaInstr
294292
plugin._applyRequestHook(span, event, context);
295293

296294
return otelContext.with(trace.setSpan(parent, span), () => {
297-
// Lambda seems to pass a callback even if handler is of Promise form, so we wrap all the time before calling
298-
// the handler and see if the result is a Promise or not. In such a case, the callback is usually ignored. If
299-
// the handler happened to both call the callback and complete a returned Promise, whichever happens first will
300-
// win and the latter will be ignored.
301-
const wrappedCallback = plugin._wrapCallback(callback, span);
295+
// Type assertion: Handler type from aws-lambda still includes callback parameter,
296+
// but we only support Promise-based handlers now (Node.js 24+)
302297
const maybePromise = safeExecuteInTheMiddle(
303-
() => original.apply(this, [event, context, wrappedCallback]),
298+
() => (original as (event: any, context: Context) => Promise<any> | any).apply(this, [event, context]),
304299
error => {
305300
if (error != null) {
306-
// Exception thrown synchronously before resolving callback / promise.
301+
// Exception thrown synchronously before resolving promise.
307302
plugin._applyResponseHook(span, error);
308303
plugin._endSpan(span, error, () => {});
309304
}
@@ -440,19 +435,6 @@ export class AwsLambdaInstrumentation extends InstrumentationBase<AwsLambdaInstr
440435
return undefined;
441436
}
442437

443-
private _wrapCallback(original: Callback, span: Span): Callback {
444-
const plugin = this;
445-
return function wrappedCallback(this: never, err, res) {
446-
diag.debug('executing wrapped lookup callback function');
447-
plugin._applyResponseHook(span, err, res);
448-
449-
plugin._endSpan(span, err, () => {
450-
diag.debug('executing original lookup callback function');
451-
return original.apply(this, [err, res]);
452-
});
453-
};
454-
}
455-
456438
private _endSpan(
457439
span: Span,
458440
err: string | Error | null | undefined,
@@ -482,14 +464,14 @@ export class AwsLambdaInstrumentation extends InstrumentationBase<AwsLambdaInstr
482464
flushers.push(this._traceForceFlusher());
483465
} else {
484466
diag.debug(
485-
'Spans may not be exported for the lambda function because we are not force flushing before callback.'
467+
'Spans may not be exported for the lambda function because we are not force flushing before handler completion.'
486468
);
487469
}
488470
if (this._metricForceFlusher) {
489471
flushers.push(this._metricForceFlusher());
490472
} else {
491473
diag.debug(
492-
'Metrics may not be exported for the lambda function because we are not force flushing before callback.'
474+
'Metrics may not be exported for the lambda function because we are not force flushing before handler completion.'
493475
);
494476
}
495477

packages/instrumentation-aws-lambda/test/integrations/lambda-handler.force-flush.test.ts

Lines changed: 5 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -100,19 +100,7 @@ describe('force flush', () => {
100100
provider.forceFlush = forceFlush;
101101
initializeHandlerTracing('lambda-test/sync.handler', provider);
102102

103-
await new Promise((resolve, reject) => {
104-
lambdaRequire('lambda-test/sync').handler(
105-
'arg',
106-
ctx,
107-
(err: Error, res: any) => {
108-
if (err) {
109-
reject(err);
110-
} else {
111-
resolve(res);
112-
}
113-
}
114-
);
115-
});
103+
await lambdaRequire('lambda-test/sync').handler('arg', ctx);
116104

117105
assert.strictEqual(forceFlushed, true);
118106
});
@@ -133,19 +121,7 @@ describe('force flush', () => {
133121
nodeTracerProvider.forceFlush = forceFlush;
134122
initializeHandlerTracing('lambda-test/sync.handler', provider);
135123

136-
await new Promise((resolve, reject) => {
137-
lambdaRequire('lambda-test/sync').handler(
138-
'arg',
139-
ctx,
140-
(err: Error, res: any) => {
141-
if (err) {
142-
reject(err);
143-
} else {
144-
resolve(res);
145-
}
146-
}
147-
);
148-
});
124+
await lambdaRequire('lambda-test/sync').handler('arg', ctx);
149125

150126
assert.strictEqual(forceFlushed, true);
151127
});
@@ -165,24 +141,12 @@ describe('force flush', () => {
165141
provider.forceFlush = forceFlush;
166142
initializeHandlerMetrics('lambda-test/sync.handler', provider);
167143

168-
await new Promise((resolve, reject) => {
169-
lambdaRequire('lambda-test/sync').handler(
170-
'arg',
171-
ctx,
172-
(err: Error, res: any) => {
173-
if (err) {
174-
reject(err);
175-
} else {
176-
resolve(res);
177-
}
178-
}
179-
);
180-
});
144+
await lambdaRequire('lambda-test/sync').handler('arg', ctx);
181145

182146
assert.strictEqual(forceFlushed, true);
183147
});
184148

185-
it('should callback once after force flush providers', async () => {
149+
it('should complete handler after force flush providers', async () => {
186150
const nodeTracerProvider = new NodeTracerProvider({
187151
spanProcessors: [new BatchSpanProcessor(traceMemoryExporter)],
188152
});
@@ -216,24 +180,9 @@ describe('force flush', () => {
216180
instrumentation.setTracerProvider(tracerProvider);
217181
instrumentation.setMeterProvider(meterProvider);
218182

219-
let callbackCount = 0;
220-
await new Promise((resolve, reject) => {
221-
lambdaRequire('lambda-test/sync').handler(
222-
'arg',
223-
ctx,
224-
(err: Error, res: any) => {
225-
callbackCount++;
226-
if (err) {
227-
reject(err);
228-
} else {
229-
resolve(res);
230-
}
231-
}
232-
);
233-
});
183+
await lambdaRequire('lambda-test/sync').handler('arg', ctx);
234184

235185
assert.strictEqual(tracerForceFlushed, true);
236186
assert.strictEqual(meterForceFlushed, true);
237-
assert.strictEqual(callbackCount, 1);
238187
});
239188
});

0 commit comments

Comments
 (0)