Skip to content

Commit

Permalink
Merge pull request #247 from andrestoll/242-executor-error-message
Browse files Browse the repository at this point in the history
Show error message and type on function invocation errors
  • Loading branch information
alexcasalboni authored May 2, 2024
2 parents cf03cf2 + 28f75e2 commit 6e91082
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 26 deletions.
14 changes: 4 additions & 10 deletions lambda/executor.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,11 +140,8 @@ const runInParallel = async({num, lambdaARN, lambdaAlias, payloads, preARN, post
const {invocationResults, actualPayload} = await utils.invokeLambdaWithProcessors(lambdaARN, lambdaAlias, payloads[i], preARN, postARN, disablePayloadLogs);
// invocation errors return 200 and contain FunctionError and Payload
if (invocationResults.FunctionError) {
let errorMessage = `Invocation error (running in parallel): ${invocationResults.Payload}`;
if (!disablePayloadLogs) {
errorMessage += ` with payload ${JSON.stringify(actualPayload)}`;
}
throw new Error(errorMessage);
let errorMessage = 'Invocation error (running in parallel)';
utils.handleLambdaInvocationError(errorMessage, invocationResults, actualPayload, disablePayloadLogs);
}
results.push(invocationResults);
});
Expand All @@ -160,11 +157,8 @@ const runInSeries = async({num, lambdaARN, lambdaAlias, payloads, preARN, postAR
const {invocationResults, actualPayload} = await utils.invokeLambdaWithProcessors(lambdaARN, lambdaAlias, payloads[i], preARN, postARN, disablePayloadLogs);
// invocation errors return 200 and contain FunctionError and Payload
if (invocationResults.FunctionError) {
let errorMessage = `Invocation error (running in series): ${invocationResults.Payload}`;
if (!disablePayloadLogs) {
errorMessage += ` with payload ${JSON.stringify(actualPayload)}`;
}
throw new Error(errorMessage);
let errorMessage = 'Invocation error (running in series)';
utils.handleLambdaInvocationError(errorMessage, invocationResults, actualPayload, disablePayloadLogs);
}
if (sleepBetweenRunsMs > 0) {
await utils.sleep(sleepBetweenRunsMs);
Expand Down
21 changes: 16 additions & 5 deletions lambda/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -254,11 +254,8 @@ module.exports.deleteLambdaAlias = (lambdaARN, alias) => {
module.exports.invokeLambdaProcessor = async(processorARN, payload, preOrPost = 'Pre', disablePayloadLogs = false) => {
const processorData = await utils.invokeLambda(processorARN, null, payload, disablePayloadLogs);
if (processorData.FunctionError) {
let errorMessage = `${preOrPost}Processor ${processorARN} failed with error ${processorData.Payload}`;
if (!disablePayloadLogs) {
errorMessage += ` and payload ${JSON.stringify(payload)}`;
}
throw new Error(errorMessage);
let errorMessage = `${preOrPost}Processor ${processorARN} failed`;
utils.handleLambdaInvocationError(errorMessage, processorData, payload, disablePayloadLogs);
}
return processorData.Payload;
};
Expand Down Expand Up @@ -315,6 +312,20 @@ module.exports.invokeLambda = (lambdaARN, alias, payload, disablePayloadLogs) =>
return lambda.send(new InvokeCommand(params));
};

/**
* Handle a Lambda invocation error and generate an error message containing original error type, message and trace.
*/
module.exports.handleLambdaInvocationError = (errorMessageToDisplay, invocationResults, actualPayload, disablePayloadLogs) => {
const parsedResults = JSON.parse(Buffer.from(invocationResults.Payload));
if (!disablePayloadLogs) {
errorMessageToDisplay += ` with payload ${JSON.stringify(actualPayload)}`;
}
errorMessageToDisplay += ` - original error type: "${parsedResults.errorType}", ` +
`original error message: "${parsedResults.errorMessage}",` +
`trace: "${JSON.stringify(parsedResults.stackTrace)}"`;
throw new Error(errorMessageToDisplay);
};

/**
* Fetch the body of an S3 object, given an S3 path such as s3://BUCKET/KEY
*/
Expand Down
38 changes: 30 additions & 8 deletions test/unit/test-lambda.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ const invokeForFailure = async(handler, event) => {

};

// utility to create a UInt8Array from a string
const toByteArray = (inputString) => {
const textEncoder = new TextEncoder();
return textEncoder.encode(inputString);
};

// Stub stuff
const sandBox = sinon.createSandbox();
var getLambdaAliasStub,
Expand Down Expand Up @@ -775,7 +781,9 @@ describe('Lambda Functions', async() => {
.callsFake(async(_arn, _alias, payload) => {
return {
FunctionError: 'Unhandled',
Payload: '{"errorType": "MemoryError", "stackTrace": [["/var/task/lambda_function.py", 11, "lambda_handler", "blabla"], ["/var/task/lambda_function.py", 7, "blabla]]}',
Payload: toByteArray('{"errorMessage": "Exception raised during execution.", ' +
'"errorType": "Exception", "requestId": "c9e545c9-373c-402b-827f-e1c19af39e99", ' +
'"stackTrace": ["File \\"/var/task/lambda_function.py\\", line 9, in lambda_handler, raise Exception(\\"Exception raised during execution.\\")"]}'),
};
});
await invokeForFailure(handler, {
Expand All @@ -796,7 +804,9 @@ describe('Lambda Functions', async() => {
.callsFake(async(_arn, _alias, payload) => {
return {
FunctionError: 'Unhandled',
Payload: '{"errorType": "MemoryError", "stackTrace": [["/var/task/lambda_function.py", 11, "lambda_handler", "blabla"], ["/var/task/lambda_function.py", 7, "blabla]]}',
Payload: toByteArray('{"errorMessage": "Exception raised during execution.", ' +
'"errorType": "Exception", "requestId": "c9e545c9-373c-402b-827f-e1c19af39e99", ' +
'"stackTrace": ["File \\"/var/task/lambda_function.py\\", line 9, in lambda_handler, raise Exception(\\"Exception raised during execution.\\")"]}'),
};
});
const error = await invokeForFailure(handler, {
Expand Down Expand Up @@ -837,7 +847,9 @@ describe('Lambda Functions', async() => {
.callsFake(async(_arn, _alias, payload) => {
return {
FunctionError: 'Unhandled',
Payload: '{"errorType": "MemoryError", "stackTrace": [["/var/task/lambda_function.py", 11, "lambda_handler", "blabla"], ["/var/task/lambda_function.py", 7, "blabla]]}',
Payload: toByteArray('{"errorMessage": "Exception raised during execution.", ' +
'"errorType": "Exception", "requestId": "c9e545c9-373c-402b-827f-e1c19af39e99", ' +
'"stackTrace": ["File \\"/var/task/lambda_function.py\\", line 9, in lambda_handler, raise Exception(\\"Exception raised during execution.\\")"]}'),
};
});
const error = await invokeForFailure(handler, {
Expand Down Expand Up @@ -879,7 +891,9 @@ describe('Lambda Functions', async() => {
.callsFake(async(_arn, _alias, payload) => {
return {
FunctionError: 'Unhandled',
Payload: '{"errorType": "MemoryError", "stackTrace": [["/var/task/lambda_function.py", 11, "lambda_handler", "blabla"], ["/var/task/lambda_function.py", 7, "blabla]]}',
Payload: toByteArray('{"errorMessage": "Exception raised during execution.", ' +
'"errorType": "Exception", "requestId": "c9e545c9-373c-402b-827f-e1c19af39e99", ' +
'"stackTrace": ["File \\"/var/task/lambda_function.py\\", line 9, in lambda_handler, raise Exception(\\"Exception raised during execution.\\")"]}'),
};
});
const error = await invokeForFailure(handler, {
Expand Down Expand Up @@ -907,7 +921,9 @@ describe('Lambda Functions', async() => {
.callsFake(async(_arn, _alias, payload) => {
return {
FunctionError: 'Unhandled',
Payload: '{"errorType": "MemoryError", "stackTrace": [["/var/task/lambda_function.py", 11, "lambda_handler", "blabla"], ["/var/task/lambda_function.py", 7, "blabla]]}',
Payload: toByteArray('{"errorMessage": "Exception raised during execution.", ' +
'"errorType": "Exception", "requestId": "c9e545c9-373c-402b-827f-e1c19af39e99", ' +
'"stackTrace": ["File \\"/var/task/lambda_function.py\\", line 9, in lambda_handler, raise Exception(\\"Exception raised during execution.\\")"]}'),
};
});
const error = await invokeForFailure(handler, {
Expand Down Expand Up @@ -936,7 +952,9 @@ describe('Lambda Functions', async() => {
.callsFake(async(_arn, _alias, payload) => {
return {
FunctionError: 'Unhandled',
Payload: '{"errorType": "MemoryError", "stackTrace": [["/var/task/lambda_function.py", 11, "lambda_handler", "blabla"], ["/var/task/lambda_function.py", 7, "blabla]]}',
Payload: toByteArray('{"errorMessage": "Exception raised during execution.", ' +
'"errorType": "Exception", "requestId": "c9e545c9-373c-402b-827f-e1c19af39e99", ' +
'"stackTrace": ["File \\"/var/task/lambda_function.py\\", line 9, in lambda_handler, raise Exception(\\"Exception raised during execution.\\")"]}'),
};
});
await invokeForFailure(handler, {
Expand Down Expand Up @@ -1130,7 +1148,9 @@ describe('Lambda Functions', async() => {
.callsFake(async(_arn, _alias, payload) => {
return {
FunctionError: 'Unhandled',
Payload: '{"errorType": "MemoryError", "stackTrace": [["/var/task/lambda_function.py", 11, "lambda_handler", "blabla"], ["/var/task/lambda_function.py", 7, "blabla]]}',
Payload: toByteArray('{"errorMessage": "Exception raised during execution.", ' +
'"errorType": "Exception", "requestId": "c9e545c9-373c-402b-827f-e1c19af39e99", ' +
'"stackTrace": ["File \\"/var/task/lambda_function.py\\", line 9, in lambda_handler, raise Exception(\\"Exception raised during execution.\\")"]}'),
};
});

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

Expand Down
56 changes: 53 additions & 3 deletions test/unit/test-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ s3Mock.on(GetObjectCommand).resolves({
}
});

// utility to create a UInt8Array from a string
const toByteArray = (inputString) => {
const textEncoder = new TextEncoder();
return textEncoder.encode(inputString);
};


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

Expand Down Expand Up @@ -531,16 +537,18 @@ describe('Lambda Utils', () => {
.callsFake(async () => {
invokeLambdaCounter++;
return {
Payload: '{"KO": "KO"}',
Payload: toByteArray('{"errorMessage": "Exception raised during execution.", ' +
'"errorType": "Exception", "requestId": "c9e545c9-373c-402b-827f-e1c19af39e99", ' +
'"stackTrace": ["File \\"/var/task/lambda_function.py\\", line 9, in lambda_handler, raise Exception(\\"Exception raised during execution.\\")"]}'),
FunctionError: 'Unhandled',
};
});
try {
const data = await utils.invokeLambdaProcessor('arnOK', payload, 'Pre', disablePayloadLogs);
expect(data).to.be(null);
} catch (ex) {
expect(ex.message).to.contain('failed with error');
expect(ex.message.includes('and payload')).to.be(isPayloadInErrorMessage);
expect(ex.message).to.contain('failed');
expect(ex.message.includes('with payload')).to.be(isPayloadInErrorMessage);
}

expect(invokeLambdaCounter).to.be(1);
Expand Down Expand Up @@ -569,6 +577,48 @@ describe('Lambda Utils', () => {
return true;
};

describe('handleLambdaInvocationError', () => {

const invokeLambdaForInvocationErrorAndAssertOnErrorMessage = async({disablePayloadLogs, isPayloadInErrorMessage}) => {
const errorMessage = 'Encountered invocation error';
const originalErrorMessage = 'Exception raised during execution.';
const originalErrorType = 'Exception';
const originalStackTrace = '["File \\"/var/task/lambda_function.py\\", line 9, in lambda_handler, raise Exception(\\"Exception raised during execution.\\")"]';
const invocationResults = {
Payload: toByteArray(`{"errorMessage": "${originalErrorMessage}", ` +
`"errorType": "${originalErrorType}", "requestId": "c9e545c9-373c-402b-827f-e1c19af39e99", ` +
`"stackTrace": ${originalStackTrace}}`),
FunctionError: 'Unhandled',
};
const actualPayload = 'TEST_PAYLOAD';

try {
utils.handleLambdaInvocationError(errorMessage, invocationResults, actualPayload, disablePayloadLogs);
} catch (error) {
expect(error.message).to.contain(errorMessage);
expect(error.message).to.contain(originalErrorMessage);
expect(error.message).to.contain(originalErrorType);
expect(error.message).to.contain(originalStackTrace);
expect(error.message.includes(actualPayload)).to.be(isPayloadInErrorMessage);
}
};

it('should NOT contain not payload in error message if display payload logging is disabled', async() => invokeLambdaForInvocationErrorAndAssertOnErrorMessage({
disablePayloadLogs: true,
isPayloadInErrorMessage: false,
}));

it('should contain payload in error message if display payload logging is NOT disabled', async() => invokeLambdaForInvocationErrorAndAssertOnErrorMessage({
disablePayloadLogs: false,
isPayloadInErrorMessage: true,
}));

it('should contain payload in error message if disablePayloadLogs is undefined', async() => invokeLambdaForInvocationErrorAndAssertOnErrorMessage({
disablePayloadLogs: undefined,
isPayloadInErrorMessage: true,
}));
});

describe('convertPayload', () => {

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

0 comments on commit 6e91082

Please sign in to comment.