Skip to content

Commit 6e91082

Browse files
Merge pull request #247 from andrestoll/242-executor-error-message
Show error message and type on function invocation errors
2 parents cf03cf2 + 28f75e2 commit 6e91082

File tree

4 files changed

+103
-26
lines changed

4 files changed

+103
-26
lines changed

lambda/executor.js

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -140,11 +140,8 @@ const runInParallel = async({num, lambdaARN, lambdaAlias, payloads, preARN, post
140140
const {invocationResults, actualPayload} = await utils.invokeLambdaWithProcessors(lambdaARN, lambdaAlias, payloads[i], preARN, postARN, disablePayloadLogs);
141141
// invocation errors return 200 and contain FunctionError and Payload
142142
if (invocationResults.FunctionError) {
143-
let errorMessage = `Invocation error (running in parallel): ${invocationResults.Payload}`;
144-
if (!disablePayloadLogs) {
145-
errorMessage += ` with payload ${JSON.stringify(actualPayload)}`;
146-
}
147-
throw new Error(errorMessage);
143+
let errorMessage = 'Invocation error (running in parallel)';
144+
utils.handleLambdaInvocationError(errorMessage, invocationResults, actualPayload, disablePayloadLogs);
148145
}
149146
results.push(invocationResults);
150147
});
@@ -160,11 +157,8 @@ const runInSeries = async({num, lambdaARN, lambdaAlias, payloads, preARN, postAR
160157
const {invocationResults, actualPayload} = await utils.invokeLambdaWithProcessors(lambdaARN, lambdaAlias, payloads[i], preARN, postARN, disablePayloadLogs);
161158
// invocation errors return 200 and contain FunctionError and Payload
162159
if (invocationResults.FunctionError) {
163-
let errorMessage = `Invocation error (running in series): ${invocationResults.Payload}`;
164-
if (!disablePayloadLogs) {
165-
errorMessage += ` with payload ${JSON.stringify(actualPayload)}`;
166-
}
167-
throw new Error(errorMessage);
160+
let errorMessage = 'Invocation error (running in series)';
161+
utils.handleLambdaInvocationError(errorMessage, invocationResults, actualPayload, disablePayloadLogs);
168162
}
169163
if (sleepBetweenRunsMs > 0) {
170164
await utils.sleep(sleepBetweenRunsMs);

lambda/utils.js

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -254,11 +254,8 @@ module.exports.deleteLambdaAlias = (lambdaARN, alias) => {
254254
module.exports.invokeLambdaProcessor = async(processorARN, payload, preOrPost = 'Pre', disablePayloadLogs = false) => {
255255
const processorData = await utils.invokeLambda(processorARN, null, payload, disablePayloadLogs);
256256
if (processorData.FunctionError) {
257-
let errorMessage = `${preOrPost}Processor ${processorARN} failed with error ${processorData.Payload}`;
258-
if (!disablePayloadLogs) {
259-
errorMessage += ` and payload ${JSON.stringify(payload)}`;
260-
}
261-
throw new Error(errorMessage);
257+
let errorMessage = `${preOrPost}Processor ${processorARN} failed`;
258+
utils.handleLambdaInvocationError(errorMessage, processorData, payload, disablePayloadLogs);
262259
}
263260
return processorData.Payload;
264261
};
@@ -315,6 +312,20 @@ module.exports.invokeLambda = (lambdaARN, alias, payload, disablePayloadLogs) =>
315312
return lambda.send(new InvokeCommand(params));
316313
};
317314

315+
/**
316+
* Handle a Lambda invocation error and generate an error message containing original error type, message and trace.
317+
*/
318+
module.exports.handleLambdaInvocationError = (errorMessageToDisplay, invocationResults, actualPayload, disablePayloadLogs) => {
319+
const parsedResults = JSON.parse(Buffer.from(invocationResults.Payload));
320+
if (!disablePayloadLogs) {
321+
errorMessageToDisplay += ` with payload ${JSON.stringify(actualPayload)}`;
322+
}
323+
errorMessageToDisplay += ` - original error type: "${parsedResults.errorType}", ` +
324+
`original error message: "${parsedResults.errorMessage}",` +
325+
`trace: "${JSON.stringify(parsedResults.stackTrace)}"`;
326+
throw new Error(errorMessageToDisplay);
327+
};
328+
318329
/**
319330
* Fetch the body of an S3 object, given an S3 path such as s3://BUCKET/KEY
320331
*/

test/unit/test-lambda.js

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ const invokeForFailure = async(handler, event) => {
6565

6666
};
6767

68+
// utility to create a UInt8Array from a string
69+
const toByteArray = (inputString) => {
70+
const textEncoder = new TextEncoder();
71+
return textEncoder.encode(inputString);
72+
};
73+
6874
// Stub stuff
6975
const sandBox = sinon.createSandbox();
7076
var getLambdaAliasStub,
@@ -775,7 +781,9 @@ describe('Lambda Functions', async() => {
775781
.callsFake(async(_arn, _alias, payload) => {
776782
return {
777783
FunctionError: 'Unhandled',
778-
Payload: '{"errorType": "MemoryError", "stackTrace": [["/var/task/lambda_function.py", 11, "lambda_handler", "blabla"], ["/var/task/lambda_function.py", 7, "blabla]]}',
784+
Payload: toByteArray('{"errorMessage": "Exception raised during execution.", ' +
785+
'"errorType": "Exception", "requestId": "c9e545c9-373c-402b-827f-e1c19af39e99", ' +
786+
'"stackTrace": ["File \\"/var/task/lambda_function.py\\", line 9, in lambda_handler, raise Exception(\\"Exception raised during execution.\\")"]}'),
779787
};
780788
});
781789
await invokeForFailure(handler, {
@@ -796,7 +804,9 @@ describe('Lambda Functions', async() => {
796804
.callsFake(async(_arn, _alias, payload) => {
797805
return {
798806
FunctionError: 'Unhandled',
799-
Payload: '{"errorType": "MemoryError", "stackTrace": [["/var/task/lambda_function.py", 11, "lambda_handler", "blabla"], ["/var/task/lambda_function.py", 7, "blabla]]}',
807+
Payload: toByteArray('{"errorMessage": "Exception raised during execution.", ' +
808+
'"errorType": "Exception", "requestId": "c9e545c9-373c-402b-827f-e1c19af39e99", ' +
809+
'"stackTrace": ["File \\"/var/task/lambda_function.py\\", line 9, in lambda_handler, raise Exception(\\"Exception raised during execution.\\")"]}'),
800810
};
801811
});
802812
const error = await invokeForFailure(handler, {
@@ -837,7 +847,9 @@ describe('Lambda Functions', async() => {
837847
.callsFake(async(_arn, _alias, payload) => {
838848
return {
839849
FunctionError: 'Unhandled',
840-
Payload: '{"errorType": "MemoryError", "stackTrace": [["/var/task/lambda_function.py", 11, "lambda_handler", "blabla"], ["/var/task/lambda_function.py", 7, "blabla]]}',
850+
Payload: toByteArray('{"errorMessage": "Exception raised during execution.", ' +
851+
'"errorType": "Exception", "requestId": "c9e545c9-373c-402b-827f-e1c19af39e99", ' +
852+
'"stackTrace": ["File \\"/var/task/lambda_function.py\\", line 9, in lambda_handler, raise Exception(\\"Exception raised during execution.\\")"]}'),
841853
};
842854
});
843855
const error = await invokeForFailure(handler, {
@@ -879,7 +891,9 @@ describe('Lambda Functions', async() => {
879891
.callsFake(async(_arn, _alias, payload) => {
880892
return {
881893
FunctionError: 'Unhandled',
882-
Payload: '{"errorType": "MemoryError", "stackTrace": [["/var/task/lambda_function.py", 11, "lambda_handler", "blabla"], ["/var/task/lambda_function.py", 7, "blabla]]}',
894+
Payload: toByteArray('{"errorMessage": "Exception raised during execution.", ' +
895+
'"errorType": "Exception", "requestId": "c9e545c9-373c-402b-827f-e1c19af39e99", ' +
896+
'"stackTrace": ["File \\"/var/task/lambda_function.py\\", line 9, in lambda_handler, raise Exception(\\"Exception raised during execution.\\")"]}'),
883897
};
884898
});
885899
const error = await invokeForFailure(handler, {
@@ -907,7 +921,9 @@ describe('Lambda Functions', async() => {
907921
.callsFake(async(_arn, _alias, payload) => {
908922
return {
909923
FunctionError: 'Unhandled',
910-
Payload: '{"errorType": "MemoryError", "stackTrace": [["/var/task/lambda_function.py", 11, "lambda_handler", "blabla"], ["/var/task/lambda_function.py", 7, "blabla]]}',
924+
Payload: toByteArray('{"errorMessage": "Exception raised during execution.", ' +
925+
'"errorType": "Exception", "requestId": "c9e545c9-373c-402b-827f-e1c19af39e99", ' +
926+
'"stackTrace": ["File \\"/var/task/lambda_function.py\\", line 9, in lambda_handler, raise Exception(\\"Exception raised during execution.\\")"]}'),
911927
};
912928
});
913929
const error = await invokeForFailure(handler, {
@@ -936,7 +952,9 @@ describe('Lambda Functions', async() => {
936952
.callsFake(async(_arn, _alias, payload) => {
937953
return {
938954
FunctionError: 'Unhandled',
939-
Payload: '{"errorType": "MemoryError", "stackTrace": [["/var/task/lambda_function.py", 11, "lambda_handler", "blabla"], ["/var/task/lambda_function.py", 7, "blabla]]}',
955+
Payload: toByteArray('{"errorMessage": "Exception raised during execution.", ' +
956+
'"errorType": "Exception", "requestId": "c9e545c9-373c-402b-827f-e1c19af39e99", ' +
957+
'"stackTrace": ["File \\"/var/task/lambda_function.py\\", line 9, in lambda_handler, raise Exception(\\"Exception raised during execution.\\")"]}'),
940958
};
941959
});
942960
await invokeForFailure(handler, {
@@ -1130,7 +1148,9 @@ describe('Lambda Functions', async() => {
11301148
.callsFake(async(_arn, _alias, payload) => {
11311149
return {
11321150
FunctionError: 'Unhandled',
1133-
Payload: '{"errorType": "MemoryError", "stackTrace": [["/var/task/lambda_function.py", 11, "lambda_handler", "blabla"], ["/var/task/lambda_function.py", 7, "blabla]]}',
1151+
Payload: toByteArray('{"errorMessage": "Exception raised during execution.", ' +
1152+
'"errorType": "Exception", "requestId": "c9e545c9-373c-402b-827f-e1c19af39e99", ' +
1153+
'"stackTrace": ["File \\"/var/task/lambda_function.py\\", line 9, in lambda_handler, raise Exception(\\"Exception raised during execution.\\")"]}'),
11341154
};
11351155
});
11361156

@@ -1166,7 +1186,9 @@ describe('Lambda Functions', async() => {
11661186
.callsFake(async(_arn, _alias, payload) => {
11671187
return {
11681188
FunctionError: 'Unhandled',
1169-
Payload: '{"errorType": "MemoryError", "stackTrace": [["/var/task/lambda_function.py", 11, "lambda_handler", "blabla"], ["/var/task/lambda_function.py", 7, "blabla]]}',
1189+
Payload: toByteArray('{"errorMessage": "Exception raised during execution.", ' +
1190+
'"errorType": "Exception", "requestId": "c9e545c9-373c-402b-827f-e1c19af39e99", ' +
1191+
'"stackTrace": ["File \\"/var/task/lambda_function.py\\", line 9, in lambda_handler, raise Exception(\\"Exception raised during execution.\\")"]}'),
11701192
};
11711193
});
11721194

test/unit/test-utils.js

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@ s3Mock.on(GetObjectCommand).resolves({
4848
}
4949
});
5050

51+
// utility to create a UInt8Array from a string
52+
const toByteArray = (inputString) => {
53+
const textEncoder = new TextEncoder();
54+
return textEncoder.encode(inputString);
55+
};
56+
5157

5258
describe('Lambda Utils', () => {
5359

@@ -531,16 +537,18 @@ describe('Lambda Utils', () => {
531537
.callsFake(async () => {
532538
invokeLambdaCounter++;
533539
return {
534-
Payload: '{"KO": "KO"}',
540+
Payload: toByteArray('{"errorMessage": "Exception raised during execution.", ' +
541+
'"errorType": "Exception", "requestId": "c9e545c9-373c-402b-827f-e1c19af39e99", ' +
542+
'"stackTrace": ["File \\"/var/task/lambda_function.py\\", line 9, in lambda_handler, raise Exception(\\"Exception raised during execution.\\")"]}'),
535543
FunctionError: 'Unhandled',
536544
};
537545
});
538546
try {
539547
const data = await utils.invokeLambdaProcessor('arnOK', payload, 'Pre', disablePayloadLogs);
540548
expect(data).to.be(null);
541549
} catch (ex) {
542-
expect(ex.message).to.contain('failed with error');
543-
expect(ex.message.includes('and payload')).to.be(isPayloadInErrorMessage);
550+
expect(ex.message).to.contain('failed');
551+
expect(ex.message.includes('with payload')).to.be(isPayloadInErrorMessage);
544552
}
545553

546554
expect(invokeLambdaCounter).to.be(1);
@@ -569,6 +577,48 @@ describe('Lambda Utils', () => {
569577
return true;
570578
};
571579

580+
describe('handleLambdaInvocationError', () => {
581+
582+
const invokeLambdaForInvocationErrorAndAssertOnErrorMessage = async({disablePayloadLogs, isPayloadInErrorMessage}) => {
583+
const errorMessage = 'Encountered invocation error';
584+
const originalErrorMessage = 'Exception raised during execution.';
585+
const originalErrorType = 'Exception';
586+
const originalStackTrace = '["File \\"/var/task/lambda_function.py\\", line 9, in lambda_handler, raise Exception(\\"Exception raised during execution.\\")"]';
587+
const invocationResults = {
588+
Payload: toByteArray(`{"errorMessage": "${originalErrorMessage}", ` +
589+
`"errorType": "${originalErrorType}", "requestId": "c9e545c9-373c-402b-827f-e1c19af39e99", ` +
590+
`"stackTrace": ${originalStackTrace}}`),
591+
FunctionError: 'Unhandled',
592+
};
593+
const actualPayload = 'TEST_PAYLOAD';
594+
595+
try {
596+
utils.handleLambdaInvocationError(errorMessage, invocationResults, actualPayload, disablePayloadLogs);
597+
} catch (error) {
598+
expect(error.message).to.contain(errorMessage);
599+
expect(error.message).to.contain(originalErrorMessage);
600+
expect(error.message).to.contain(originalErrorType);
601+
expect(error.message).to.contain(originalStackTrace);
602+
expect(error.message.includes(actualPayload)).to.be(isPayloadInErrorMessage);
603+
}
604+
};
605+
606+
it('should NOT contain not payload in error message if display payload logging is disabled', async() => invokeLambdaForInvocationErrorAndAssertOnErrorMessage({
607+
disablePayloadLogs: true,
608+
isPayloadInErrorMessage: false,
609+
}));
610+
611+
it('should contain payload in error message if display payload logging is NOT disabled', async() => invokeLambdaForInvocationErrorAndAssertOnErrorMessage({
612+
disablePayloadLogs: false,
613+
isPayloadInErrorMessage: true,
614+
}));
615+
616+
it('should contain payload in error message if disablePayloadLogs is undefined', async() => invokeLambdaForInvocationErrorAndAssertOnErrorMessage({
617+
disablePayloadLogs: undefined,
618+
isPayloadInErrorMessage: true,
619+
}));
620+
});
621+
572622
describe('convertPayload', () => {
573623

574624
it('should JSON-encode strings, if not JSON strings already', async () => {

0 commit comments

Comments
 (0)