Objective: show errors on AWS X-ray (all errors from lambda).
'use strict';
const AWSXRay = require('aws-xray-sdk-core'),
AWS = AWSXRay.captureAWS(require('aws-sdk')),
env = process.env;
AWS.config.update({
region: env.REGION
});
const dynamodbDocumentClient = new AWS.DynamoDB.DocumentClient();
exports.handler = async (event, context, callback) => {
let seg=AWSXRay.getSegment();
try {
const params = {
/*PARAMS HERE*/
};
let res = await dynamodbDocumentClient.scan(params).promise();
throw('ERROR FOR TESTING');
return res;
//callback(null,res);
}
catch(err) {
let subseg=seg.addNewSubsegment('error');
subseg.addMetadata("error", "error", "my_error");
subseg.addAnnotation('errr', 'this is a test');
subseg.addError(err);
subseg.addErrorFlag();
subseg.close();
console.log('==ERROR==',err);
return err;
}
};
When I use AWSXRay.captureAWS the subsegment 'error' doesnt show on X-ray. If I dont use captureAWS the error appear in X-ray correctly.
The issue is likely with how you're structuring your Lambda. First, you shouldn't be using callback in an async function handler, async function handlers should return native promises or errors per the docs.
If you want to use a callback to return the result of your function, which it seems like you do, the handler should be synchronous. That being said, you're throwing your error after the callback, which will not be reflected in X-Ray because the X-Ray segment for Lambda functions closes when the callback is called. If an error is thrown before the callback (e.g. during the DynamoDB call) it should be captured in your error subsegment.
EDIT
After the original post was edited, I was unable to reproduce the error with this code:
const AWSXRay = require('aws-xray-sdk');
const AWS = AWSXRay.captureAWS(require('aws-sdk'));
const ddb = new AWS.DynamoDB.DocumentClient();
exports.handler = async (event, context) => {
try {
const res = await ddb.scan({TableName: 'scorekeep-game'}).promise();
throw('Test Error');
return result;
} catch (e) {
console.log('caught!');
var sub2 = AWSXRay.getSegment().addNewSubsegment('err');
sub2.addError(e);
sub2.addErrorFlag();
sub2.close();
return e;
}
};
Related
I'm having an issue while trying to mock lambda.invoke which I'm calling from within another lambda function.
The function is wrapped (I can't use sinon after, it will tell me it's already wrapped).
The test keeps calling the lambda function on AWS instead of calling the mock.
It does the same if I use sinon instead of aws-sdk-mock.
test.js
const { handler1 } = require('../handler');
const sinon = require('sinon');
const AWSMock = require('aws-sdk-mock');
describe('invoke', () => {
beforeEach(() => {
invokeMock = jest.fn();
AWSMock.mock('Lambda', 'invoke', invokeMock);
// const mLambda = { invoke: sinon.stub().returnsThis(), promise: sinon.stub() };
// sinon.stub(AWS, 'Lambda').callsFake(() => mLambda);
});
afterEach(() => {
AWSMock.restore();
sinon.restore();
});
test('test1', async () => {
const event = { test: 'ok'};
const handler = await handler1(event);
expect(handler.statusCode).toBe(204);
});
});
and my lambda function is:
handler.js
const AWS = require('aws-sdk');
module.exports.handler1 = (event) => {
// The initialisation bellow has to be in the handler not outside.
const lambda = new AWS.Lambda({
region: 'us-west-2' //change to your region
});
let params = {
InvocationType: 'Event',
LogType: 'Tail',
FunctionName: 'handler2', // the lambda function we are going to invoke
Payload: JSON.stringify(event)
};
return new Promise((resolve, reject) => {
lambda.invoke(params, function(error, data) {
if(error) return reject(error);
const payload = JSON.parse(data.Payload);
if(!payload.success){
return resolve({ statusCode: 400});
}
return resolve({ statusCode: 204});
});
});
};
EDIT: So the issue I had was because I had my lambda initialisation (const lambda = new AWS.Lambda({})) outside the handler instead on inside. Thanks to stijndepestel's answer.
It is not entirely clear from the code you have shared, but presumably, you have a reference to lambda in your handler.js before you have wrapped the function in your test. Could you add the const lambda = new AWS.Lamda({}) line inside your handler function instead of outside of it?
I am using NodeJS env with serverless framework.
The service is an endpoint for a contact form submission. Code looks something like this.
I have two async calls, one is writing to dynamoDB and another is sending an Email via SES.
module.exports.blog = async (event, context, callback) => {
const data = JSON.parse(event.body);
const handler = 'AB';
const sesParams = getSesParams(handler, data);
if (typeof data.text !== 'string') {
callback(null, validationErrRes);
return;
}
try {
await logToDB(handler, data);
} catch (dbErr) {
console.error(dbErr);
callback(null, errRes(dbErr, 'Failed to log to DB'));
return;
}
try {
await SES.sendEmail(sesParams).promise();
} catch (emailErr) {
console.error(emailErr);
callback(null, errRes(emailErr, 'Failed to send mail'));
return;
}
callback(null, succsessResponse);
return;
};
The response takes exactly 6sec when the dbput and sendMail takes total of < 300ms.
PS: Running both async calls parallelly does not help much.
Try removing the callback in your function definition and the call to your callback function. Just return the successResponse. You are already an async function so do not need to use a callback. You can also just return error.
module.exports.blog = async (event, context) => {
and
return {
statusCode: 200
}
and
return validationErrRes
I've deployed a Lambda function, which should get list of items with a scan(params, cb) function. In console, I see something different, not the returned list, but something that lookgs like http request body or response?
Can you please explain how to get the list correctly and what do I get?
exports.handler = async (event, context, callback) => {
console.log('function started')
let params = {
TableName: "documents"
}
console.log('params get')
let respond = await db.scan(params, (err, data) => {
console.log('scan started')
if (err) console.log(err, err.stack);
else {
console.log('else started')
return data
}
})
console.log('Respons IS: ')
console.log(respond)
};
The response is a huge huge huge list of something:
You are mixing callbacks and async/await ES6 feature.
I advise you to only use the latter in this case.
Here is what it would look like :
const aws = require('aws-sdk');
const db = new aws.DynamoDB.DocumentClient();
exports.handler = async (event, context) => {
console.log('function started');
const params = {
TableName: "documents"
};
console.log('params get');
const respond = await db.scan(params).promise();
console.log('Respons IS: ');
console.log(respond);
return ...
};
Well, my lambda function work's well according to the log's, but it never get completed in the codepipeline stage, I have already set permission to role for allow notificate pipeline ("codepipeline:PutJobSuccessResult",
"codepipeline:PutJobFailureResult") and even set maximun time to 20sec but still not working (it actually ends at 800ms).
const axios = require('axios')
const AWS = require('aws-sdk');
const url = 'www.exampleurl.com'
exports.handler = async (event, context) => {
const codepipeline = new AWS.CodePipeline();
const jobId = event["CodePipeline.job"].id;
const stage = event["CodePipeline.job"].data.actionConfiguration.configuration.UserParameters;
const putJobSuccess = function(message) {
var params = {
jobId: jobId
};
codepipeline.putJobSuccessResult(params, function(err, data) {
if (err) {context.fail(err); }
else {context.succeed(message);}
});
};
const putJobFailure = function(message) {
var params = {
jobId: jobId,
failureDetails: {
message: JSON.stringify(message),
type: 'JobFailed',
externalExecutionId: context.invokeid
}
};
codepipeline.putJobFailureResult(params, function(err, data) {
if (err) console.log(err)
context.fail(message);
});
};
try {
await axios.post(url, { content: stage})
putJobSuccess('all fine')
} catch (e) {
putJobFailure(e)
}
};
The root issue
Because nodeJS runs everything async by default, codepipeline.putJobSuccessResult is being run async. The issue seems to be that the Lambda function is finishing it's execution before codepipeline.putJobSuccessResult has a chance to complete.
The solution
Run codepipeline.putJobSuccessResult synchronously so that it is forced to complete before the response is returned to Lambda for the lambdaHandler.
const putJobSuccess = function(id) {
//await sleep(60);
console.log("Telling Codepipeline test passed for job: " + id)
var params = {
jobId: id
};
return codepipeline.putJobSuccessResult(params, function(err, data) {
if(err) {
console.error(err)
} else {
console.log(data)
}
}).promise()
};
exports.lambdaHandler = async (event, context) => {
...
await putJobSuccess( jobId )
return response
};
Whenever I see this issue, most of the time it is due to 'PutJobSuccessResult' never being invoked. The best way to check this is to go to CloudTrail > 'Event History' and look for 'Event name' = 'PutJobSuccessResult' during the time range you expect the Lambda function calling this API. Probably you will not find the 'PutJobSuccessResult', then please have a look at the code again and the Lambda execution logs in CloudWatch.
I am new to async functions and promises. I have written a Lambda function which queries a DynamoDB table and returns the result. The code is executing inside the callback success block and I am able to see the response in the log from the console.log(res) line. However the Lambda response is always showing as null, i.e. the response object below is not returned at all. I was able to make this work using a Synchronous Lambda function using a callback to return the data. Can you please suggest what I may be doing incorrectly.
const doc = require('dynamodb-doc');
var dynamodbclient;
const tablename = process.env.TABLE_NAME;
exports.handler = async(event) => {
if (!dynamodbclient) {
dynamodbclient = new doc.DynamoDB();
}
let id = event.params.id;
let queryparams = {
TableName: 'table-name',
Key: { id: id }
};[![enter image description here][1]][1]
var getItemsCallback = (err, res) => {
console.log('inside');
if (err) {
}
else {
console.log('success');
console.log(res);
const response = {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
return response;
}
};
var item = await dynamodbclient.getItem(queryparams, getItemsCallback).promise();
};
Your callback is still executing after the promise resolves, so the lambda will terminate and your callback will not finish.
Try:
try {
const item = await dynamodbclient.getItem(queryparams).promise();
} catch (err) {}
console.log('success');
console.log(item);
const response = {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
return response;