I have created a Lambda function in AWS that export logs from Cloudfront to Elasticsearch.
From the AWS console, I still have a warining in front of Invocation error, though the metrics show there is none for more than 24hours.
A typical workflow of logs looks like
START RequestId: 302f0b95-7856-11e8-9486-55b3f10e7d4e Version: $LATEST
Request complete
END RequestId: 302f0b95-7856-11e8-9486-55b3f10e7d4e
REPORT RequestId: 302f0b95-7856-11e8-9486-55b3f10e7d4e Duration: 794.93 ms Billed Duration: 800 ms Memory Size: 128 MB Max Memory Used: 52 MB
There is no error in the logs, and the only thing I guess could trigger this invocation error is that sometimes two request starts at the same time
09:01:47
START RequestId: 63cd00e1-7856-11e8-8f96-1f900def8e65 Version: $LATEST
09:01:47
START RequestId: 63e1e7f3-7856-11e8-97e6-3792244f6ab0 Version: $LATEST
Except from this, I don't understand where this error comes from.
Do I miss something? Or do I have to wait more than 24hours before the satus change? May be there is a way to pinpoint the error with AWS console/API that I did not find about?
Would be happy to hear what you think about this.
Edit: In case you'd like to take a look at the code itself.
var aws = require('aws-sdk');
var zlib = require('zlib');
var async = require('async');
const CloudFrontParser = require('cloudfront-log-parser');
var elasticsearch = require('elasticsearch');
var s3 = new aws.S3();
var client = new elasticsearch.Client({
host: process.env.ES_HOST,
log: 'trace',
keepAlive: false
});
exports.handler = function(event, context, callback) {
var srcBucket = event.Records[0].s3.bucket.name;
var srcKey = event.Records[0].s3.object.key;
async.waterfall([
function fetchLogFromS3(next){
console.log('Fetching compressed log from S3...');
s3.getObject({
Bucket: srcBucket,
Key: srcKey
},
next);
},
function uncompressLog(response, next){
console.log("Uncompressing log...");
zlib.gunzip(response.Body, next);
},
function publishNotifications(jsonBuffer, next) {
console.log('Filtering log...');
var json = jsonBuffer.toString();
var records;
CloudFrontParser.parse(json, { format: 'web' }, function (err, accesses) {
if(err){
console.log(err);
} else {
records = accesses;
}
});
var bulk = [];
records.forEach(function(record) {
bulk.push({"index": {}})
bulk.push(record);
});
client.bulk({
index: process.env.ES_INDEXPREFIX,
type: 'log',
body: bulk
}, function(err, resp, status) {
if(err) {
console.log('Error: ', err);
}
console.log(resp);
next();
});
console.log('CloudFront parsed:', records);
}
], function (err) {
if (err) {
console.error('Failed to send data: ', err);
} else {
console.log('Successfully send data.');
}
callback(null, 'success');
});
};
You need to explicitly return information back to the caller.
Here's the related documentation:
The Node.js runtimes v6.10 and v8.10 support the optional callback
parameter. You can use it to explicitly return information back to the
caller. The general syntax is:
callback(Error error, Object result); Where:
error – is an optional parameter that you can use to provide results
of the failed Lambda function execution. When a Lambda function
succeeds, you can pass null as the first parameter.
result – is an optional parameter that you can use to provide the
result of a successful function execution. The result provided must be
JSON.stringify compatible. If an error is provided, this parameter is
ignored.
If you don't use callback in your code, AWS Lambda will call it
implicitly and the return value is null.
When the callback is called (explicitly or implicitly), AWS Lambda
continues the Lambda function invocation until the event loop is
empty.
The following are example callbacks:
callback(); // Indicates success but no information returned to the caller.
callback(null); // Indicates success but no informatio returned to the caller.
callback(null, "success"); // Indicates success with information returned to the caller.
callback(error); // Indicates error with error information returned to the caller
https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-handler.html
Related
I have this code in node.js (nodejs10.x) running in AWS Lambda.
module.exports.register = (event, context, callback) => {
// Body es un json por lo que hay que deserializarlo
let body = JSON.parse(event.body);
let rules = {
'name': 'required|min:3',
'family_name': 'required|min:3',
'email': 'required|email',
'curp': 'required|size:18',
'modules': 'required',
'password': 'required'
};
let validation = new validator(body, rules);
// If errors this validation exits using the callback
if(validation.fails()){
console.log(validation.errors.all())
const response = {
statusCode: 422,
body: JSON.stringify(validation.errors.all())
};
callback(null, response);
}
// just for testing
const isModulesValid = false;
if(!isModulesValid){
console.log('Modules validation failed. ')
const response = {
statusCode: 422,
body: JSON.stringify({'modules': 'Invalid modules string. '})
};
callback(null, response);
// However this is not working
}
// and even if there is an error this code is executed
console.log('XXXX');
I am testing it locally with a code like this.
// Callback
let callback = function(err, result) {
if (err)
console.log(err);
if (result)
console.log(result);
// Terminate execution once done
process.exit(0);
}
// lambda.generateToken(event, context, callback);
lambda.register(event, context, callback);
Locally if isModulesValid = false the code exits and the console.log('XXXX') is not executed. However when running it in AWS Lambda, even if the validation fails the code continues to run and the console.log() is executed.
I cannot figure out whats going on. Help please?
Locally you are using the callback which has process.exit(0); which is making the process finish and hence the next line is not executing. Callback doesn't mean that the code afterwards wouldn't be executed. the code flow continues after that as well. It all depends on what you have in your callback.
This piece of code solved the problem:
if(!isModulesValid){
console.log('Modules validation failed. ')
const response = {
statusCode: 422,
body: JSON.stringify({'modules': 'Invalid modules string. '})
};
// Return callback explcitly
return callback(null, response);
}
Apparently it has to do with how AWS Lambda handles the task queue. I found a good explanation here: https://blog.danillouz.dev/aws-lambda-and-the-nodejs-event-loop/
My AWS Lambda function sends an email through SNS, but the response is null in the Lambda console. I used a blueprint provided by AWS and put the code in a Lambda handler using Node.js 10.x.
https://github.com/awsdocs/aws-doc-sdk-examples/blob/master/javascript/example_code/sns/sns_publishtotopic.js
I am not that experienced in the use of promises in node.js and my research on Stack Overflow seems to indicate that could be the problem here.
'use strict';
var AWS = require('aws-sdk');
// Set region
AWS.config.update({region: 'us-west-2'});
exports.handler = (event, context, callback) => {
// Create publish parameters
var params = {
//Message: `The fluid level is low on your Knight system: ${event.my_volume} mL.`,
Message: `The fluid level is low on your Knight system.`,
Subject: `Low Fluid Level Alert - Knight`,
TopicArn: `arn:aws:sns:us-west-2:468820349153:MyLowVolumeTopic`,
};
// Create promise and SNS service object
var publishTextPromise = new AWS.SNS({apiVersion: '2010-03-31'}).publish(params).promise();
// Handle promise's fulfilled/rejected states
publishTextPromise.then(
function(data) {
console.log("Message: " + params.Message);
console.log("Sent to the topic: " + params.TopicArn);
console.log("MessageID is " + data.MessageId);
}).catch(
function(err) {
console.error(err, err.stack);
});
};
The result is that I receive the email message inconsistently and see a null response in the Lambda console. Here is a sample log result:
Response: null
Request ID: "9dcc8395-5e17-413a-afad-a86b9e04fb97"
Function Logs:
START RequestId: 9dcc8395-5e17-413a-afad-a86b9e04fb97 Version: $LATEST
2019-08-17T21:44:31.136Z 9dcc8395-5e17-413a-afad-a86b9e04fb97 Message: The fluid level is low on your Knight system.
2019-08-17T21:44:31.136Z 9dcc8395-5e17-413a-afad-a86b9e04fb97 Sent to the topic: arn:aws:sns:us-west-2:468820349153:MyLowVolumeTopic
2019-08-17T21:44:31.136Z 9dcc8395-5e17-413a-afad-a86b9e04fb97 MessageID is cb139fb6-5d37-574e-9134-ca642a49fde5
END RequestId: 9dcc8395-5e17-413a-afad-a86b9e04fb97
REPORT RequestId: 9dcc8395-5e17-413a-afad-a86b9e04fb97 Duration: 1367.98 ms Billed Duration: 1400 ms Memory Size: 128 MB Max Memory Used: 75 MB
Your lambda is exiting before completing the promise. To circumvent this you can use async-await:
exports.handler = async (event, context, callback) => {
const data = await publishTextPromise;
//maybe some console log here
return data;
}
OR, You can return the promise, return publishTextPromise.then(...).catch(...)
so on my first time learning AWS stuff (it is a beast), I'm trying to create e-mail templates, I have this lambda function:
// Load the AWS SDK for Node.js
var AWS = require('aws-sdk');
// Set the region
AWS.config.update({ region: "us-east-1" });
exports.handler = async (event, context, callback) => {
// Create createTemplate params
var params = {
Template: {
TemplateName: "notification" /* required */,
HtmlPart: "HTML_CONTENT",
SubjectPart: "SUBJECT_LINE",
TextPart: "sending emails with aws lambda"
}
};
// Create the promise and SES service object
const templatePromise = new AWS.SES({ apiVersion: "2010-12-01" })
.createTemplate(params)
.promise();
// Handle promise's fulfilled/rejected states
templatePromise
.then((data) => {
console.log(data);
callback(null, JSON.stringify(data) );
// also tried callback(null, data);
}, (err) => {
console.error(err, err.stack);
callback(JSON.stringify(err) );
});
as far as I am understanding, this function should return me a template? an object, anything? when I use the lambda test functionality I always got null in the request response
does anyone know what I am doing wrong here?
edit: and It is not creating the e-mail template, I check the SES Panel - email templates and it is empty
edit2: if I try to return a string eg: callback(null, "some success message"); it does return the string, so my guess is something wrong with the SES, but this function is exactly what we have in the AWS docs, so I assume it should just work..
Try not to resolve the Promise and change your code to just returning it as-is:
return await templatePromise;
which should present you some more detail of what is really going wrong in your code - it might be some hidden access issue - so you might need to adjust the role your lambda function is using. createTemplate on the other side should not return much in case of successful execution but just create the template.
Also try to follow the following try/catch pattern when using async (as described here in more detail: https://aws.amazon.com/de/blogs/compute/node-js-8-10-runtime-now-available-in-aws-lambda/)
exports.handler = async (event) => {
try {
data = await lambda.getAccountSettings().promise();
}
catch (err) {
console.log(err);
return err;
}
return data;
};
I know this is a failing in my knowledge but I can not work it out. I've spent the last couple of hours browsing and trying variations but I can not work out what is wrong.
I have written a success piece of node.js that when run from the command line returns a table, in JSON form, from a local AWS DynamoDB.
I am now trying to turn that into a lamda call. And there's where it goes wrong. I can not get the lamda to return anything and I don't know why. I'm pretty sure it's because I don't understand how the callback mechanism works but reading around that hasn't help and hasn't allowed me to stumble over the answer.
This is the code I've got:
var AWS = require("aws-sdk");
exports.handler = (event, context, callback) => {
let id = (event.pathParameters || {}).division || false;
switch(event.httpMethod){
case "GET":
if(id) {
//callback(null, {body: "Returning Divison " + id});
AWS.config.update({
region: "us-west-2",
endpoint: "http://localhost:8000"
});
var docClient = new AWS.DynamoDB.DocumentClient()
var table = 'League';
var params = {
TableName : table,
KeyConditionExpression: "#division = :division",
ExpressionAttributeNames: {
"#division": "division"
},
ExpressionAttributeValues: {
":division": id
}
};
docClient.query(params, function(err, data) {
if (err)
{
//console.error("Unable to read item. Error JSON:", JSON.stringify(err, null, 2));
callback(err, null);
}
else
{
//console.log("GetItem succeeded:", JSON.stringify(data, null, 2));
callback(null, JSON.stringify(data.Item));
}
})
break;
}
callback(null, {body: "Return all divisions"});
break;
default:
// Send HTTP 501: Not Implemented
console.log("Error: unsupported HTTP method (" + event.httpMethod + ")");
callback(null, { statusCode: 501 })
}
}
If I comment out the docClient.query call it works and returns stuff to the browser (just the one line messages though). When I include it fails and I get the following message from SAM:
Function 'TableGetTest' timed out after 3 seconds
Function returned an invalid response (must include one of: body, headers or statusCode in the response object). Response received: b''
Any help or pointers would be appreciated.
Turns out this was a connection error. It would appear that the instance of SAM-Local cannot connect to the local instance of dynamoDB.
I'm now looking at how I get SAM-Local access to the local machine (it can't seem to see outside of its container).
I am using aws-sdk for javascript.
The code below works fine when using in a stand-alone program
//program.js
const AWS = require('aws-sdk');
const firehose = new AWS.Firehose({
accessKeyId: "XXX",
secretAccessKey: "YY"
});
const params = {
DeliveryStreamName: 'demo1',
Record: {
Data: new Buffer("Hello World")
}
};
firehose.putRecord(params, function (err, data){
if (err) {
console.log(err);
return;
}
console.log(data); // successful response
});
Again, the above code works fine as a stand alone file. Data gets pushed into firehose and then further down to Redshift.
so if i execute
node program.js
I am able to see my data in Redshift. Yay!!
=============================
However, what i really want to achieve is to push data to firehose when a certain route gets hit in my express application. So I take the exact same code as above and stick it in my route
// router.js
const AWS = require('aws-sdk');
const firehose = new AWS.Firehose({
accessKeyId: "XXX",
secretAccessKey: "YY"
});
router
.get('/v1/locations/:id?', (req, res) => {
const params = {
DeliveryStreamName: 'demo1',
Record: {
Data: new Buffer("Hello World")
}
};
firehose.putRecord(params, function (err, data){
if (err) {
console.log(err);
return;
}
console.log(data);
});
// do the work that needs to be done for this route and send a response
res.send("some data");
});
The minute firehose.putRecord is executed .. it crashes my program with the following error:
```
TypeError: doneCallback.cal is not a function
at Request.callListeners (/api-project/node_modules/aws-sdk/lib/sequential_executor.js:115:18)
at callNextListener (/api-project/node_modules/aws-sdk/lib/sequential_executor.js:95:12)
at /api-project/node_modules/aws-sdk/lib/event_listeners.js:74:9
at finish (/api-project/node_modules/aws-sdk/lib/config.js:315:7)
at /api-project/node_modules/aws-sdk/lib/config.js:333:9
at Credentials.get (/api-project/node_modules/aws-sdk/lib/credentials.js:126:7)
at getAsyncCredentials (/api-project/node_modules/aws-sdk/lib/config.js:327:24)
at Config.getCredentials (/api-project/node_modules/aws-sdk/lib/config.js:347:9)
at Request.VALIDATE_CREDENTIALS (/api-project/node_modules/aws-sdk/lib/event_listeners.js:69:26)
at Request.callListeners (/api-project/node_modules/aws-sdk/lib/sequential_executor.js:101:18)
I can't understand why this code crashes my express program. is this is bug in the aws-sdk library or am i doing something wrong ?
You should be sending the express response inside your success callback.
firehose.putRecord(params, function (err, data) {
if (err) {
console.log(err);
return;
}
console.log(data);
res.send("some data");
}
);
FYI, your res.send(data) will effectively exit the program and send data. However, your putRecord callback is the time when your exit should occur. In node, things do not happen in a sequence from top to bottom of the code, but instead it executes in order of the callback events. so the execution flow for your code would be like this:
file executes
some operation is performed
callback for operation occurs
then if theres additional code outside of operate, it will continue, otherwise the code will exit in that callback. Hence, put the res.send in your putRecord callback.