AWS X Ray node js trails not showing - node.js

I am using Lambda (Node 8.10) and working with AWS X Ray. I am calling an external ip address using promise.
When I call, other traces are shown but cannot get custom segment.
I am not using any frameworks just a pure node js.
const AWSXRay = require('aws-xray-sdk-core');
AWSXRay.enableManualMode();
AWSXRay.captureHTTPsGlobal(require('https'));
const https = AWSXRay.captureHTTPs(require('https'));
exports.handler = async (event, context, callback) => {
// other code
const response = await doSomething(event);
return callback(error, response);
};
async doSomething(event) {
return new Promise((resolve, reject) => {
const segment = new AWSXRay.Segment('custom_segment_here');
AWSXRay.captureAsyncFunc('send', (subsegment) => {
const options = {
hostname: host,
port: 443,
path: '/',
method: 'GET',
XRaySegment: subsegment,
};
const req = https.request(options, (res) => {
code = res.statusCode;
resolve(code);
});
req.on('error', (error) => {
subsegment.addError(error);
reject(error);
});
subsegment.close();
req.end();
}, segment);
}
}

In Lambda scenario, Lambda is responsible for creating Segments and AWS X-Ray SDKs only create Subsegments and then emits them. Based on your code snippet, you created a segment (const segment = new AWSXRay.Segment('custom_segment_here');) inside a lambda function which couldn't be emitted so that you cannot see it in our console. Hope my answer is clear. :)

Related

Wrong aws request signature caused by opentelemetry https plugin

When using the #opentelemetry/plugin-https and the aws-sdk together in a NodeJS application, the opentelemetry plugin adds the traceparent header to each AWS request. This works fine if there is no need for retries in the aws-sdk. When the aws-sdk retries a request the following errors can occur:
InvalidSignatureException: The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.
SignatureDoesNotMatch: The request signature we calculated does not match the signature you provided. Check your key and signing method.
The first AWS request contains the following headers:
traceparent: '00-32c9b7adee1da37fad593ee38e9e479b-875169606368a166-01'
Authorization: 'AWS4-HMAC-SHA256 Credential=<credential>, SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-amz-security-token;x-amz-target, Signature=<signature>'
Note that the SignedHeaders doesn't include traceparent.
The retried request contains the following headers:
traceparent: '00-c573e391a455a207469ffa4fb75b3cab-6f20c315628cfcc0-01'
Authorization: AWS4-HMAC-SHA256 Credential=<credential>, SignedHeaders=host;traceparent;x-amz-content-sha256;x-amz-date;x-amz-security-token;x-amz-target, Signature=<signature>
Note that the SignedHeaders does include traceparent.
Before the retry request is sent, the #opentelemetry/plugin-https sets new traceparent header and this makes the signature of the AWS request invalid.
Here is a code which reproduces the issue (you may need to run the script a few times before hitting the rate limit which causes the retries):
const opentelemetry = require("#opentelemetry/api");
const { NodeTracerProvider } = require("#opentelemetry/node");
const { SimpleSpanProcessor } = require("#opentelemetry/tracing");
const { JaegerExporter } = require("#opentelemetry/exporter-jaeger");
const provider = new NodeTracerProvider({
plugins: {
https: {
enabled: true,
path: "#opentelemetry/plugin-https"
}
}
});
const exporter = new JaegerExporter({ serviceName: "test" });
provider.addSpanProcessor(new SimpleSpanProcessor(exporter));
provider.register();
const AWS = require("aws-sdk");
const main = async () => {
const cwl = new AWS.CloudWatchLogs({ region: "us-east-1" });
const promises = new Array(100).fill(true).map(() => new Promise((resolve, reject) => {
cwl.describeLogGroups(function (err, data) {
if (err) {
console.log(err.name);
console.log("Got error:", err.message);
console.log("ERROR Request Authorization:");
console.log(this.request.httpRequest.headers.Authorization);
console.log("ERROR Request traceparent:");
console.log(this.request.httpRequest.headers.traceparent);
console.log("Retry count:", this.retryCount);
reject(err);
return;
}
resolve(data);
});
}));
const result = await Promise.all(promises);
console.log(result.length);
};
main().catch(console.error);
Possible solutions:
Ignore all calls to aws in the #opentelemetry/plugin-https.
Ignoring the calls to aws will lead to loosing all spans for aws requests.
Add the traceparent header to the unsignableHeaders in the aws-sdk - AWS.Signers.V4.prototype.unsignableHeaders.push("traceparent");
Changing the prototype seems like a hack and also doesn't handle the case if another node module uses different version of the aws-sdk.
Is there another solution which could allow me to keep the spans for aws requests and guarantees that the signature of all aws requests will be correct?
Update (16.12.2020):
The issue seems to be fixed in the aws sdk v3
The following code throws the correct error (ThrottlingException):
const opentelemetry = require("#opentelemetry/api");
const { NodeTracerProvider } = require("#opentelemetry/node");
const { SimpleSpanProcessor } = require("#opentelemetry/tracing");
const { JaegerExporter } = require("#opentelemetry/exporter-jaeger");
const { CloudWatchLogs } = require("#aws-sdk/client-cloudwatch-logs");
const provider = new NodeTracerProvider({
plugins: {
https: {
enabled: true,
path: "#opentelemetry/plugin-https"
}
}
});
const exporter = new JaegerExporter({ serviceName: "test" });
provider.addSpanProcessor(new SimpleSpanProcessor(exporter));
provider.register();
const main = async () => {
const cwl = new CloudWatchLogs({ region: "us-east-1" });
const promises = new Array(100).fill(true).map(() => new Promise((resolve, reject) => {
cwl.describeLogGroups({ limit: 50 })
.then(resolve)
.catch((err) => {
console.log(err.name);
console.log("Got error:", err.message);
reject(err);
});
}));
const result = await Promise.all(promises);
console.log(result.length);
};
main().catch(console.error);

Node function in AWS throws "missing ) after argument list" error

I have created a Lambda function (Node.js 12.x) in AWS so SNS messages are pushed to Slack from our ETL tool Matillion.
console.log('Loading function');
const https = require('https');
const url = require('url');
const slack_url = 'https://hooks.slack.com/services/T1MJBHQ95/B01DQ60NUR2/....';
const slack_req_opts = url.parse(slack_url);
slack_req_opts.method = 'POST';
slack_req_opts.headers = { 'Content-Type': 'application/json' };
exports.handler = function (event, context) {
(event.Records || []).forEach(function (rec) {
if (rec.Sns) {
var req = https.request(slack_req_opts, function (res) {
if (res.statusCode === 200) {
context.succeed('sns posted to slack');
}
else {
context.fail('status code: ' + res.statusCode);
}
});
req.on('error', function (e) {
console.log('problem with request: ' + e.message);
context.fail(e.message);
});
req.write(JSON.stringify({ text: `${rec.Sns.Message}` }));
req.end();
}
});
};
The function will fail with a missing ) after argument list syntax error. I run it thru a linter in Sublime and it throws an error on require and exports being undefined.
My research shows several challenges:
I may need a file called eslint.rc but I am unclear why I need to put
in.
The use of "require" and "exports" appears deprecated.
Can someone please give me pointers how what to focus on to resolve this? Thank you.
You made a syntax mistake in your code.
req.write(JSON.stringify({ text: `${rec.Sns.Message}` }));
req.end();
Need to add ) before ; in your req.write() statement.
Just use Axios, it will make your life easier:
console.log('Loading function');
const axios = require('axios');
const url = require('url');
const slack_url = 'https://hooks.slack.com/services/T1MJBHQ95/B01DQ60NUR2/....';
exports.handler = async (event, context) {
(event.Records || []).forEach(function(rec) {
if (rec.Sns) {
axios.post(slack_url, {
text: rec.Sns.Message
}, {
'Content-Type': 'application/json'
}).done(r => {
context.succeed('sns posted to slack');
}).catch(e => {
context.fail('status code: ' + e.statusCode);
});
});
Notice that in your code, after 1 sns event he end the invocation, not running all, to run all of them you need to wait for all requests to be done (can use Promise.all for example)
Also, be aware that sns can send directly to HTTPS without any code
https://aws.amazon.com/sns/features/
By the way, you can use an external monitoring tool that will show you exactly the outgoing request + all payload as Lumigo (As a disclaimer I work for a company named lumigo)
I fixed my issue:
I added the following statement at the top of my file /* eslint-env node, common */
I re-deployed my function.
I believe the redeployment of the function did the trick.

getting Task timed out after 59.05 seconds aws lambda nodejs

I am trying to write a lambda function in node 8.10 supported by aws lambda
this is with reference to my previous question
I am trying node async/await , same code work when I run it in my local(and is not even taking a second to get the response back) but when I am trying it in lambda getting time out error, here the code control is getting inside promise and printing url urltohit with the correct url but after it is getting stuck, have tried different thing like increasing the time in lambda config and even tried node request instead of http but still getting the same error.
'use strict';
var http = require('http');
var request = require('request');
exports.handler = async (event) => {
const response = await getRequest(urltohit);
console.log(response);
}
const getRequest = async (url) => {
console.log("Test log");
return new Promise((resolve, reject) => {
console.log(`url ${url}`);
http.get(url, function(res) {
console.log("Got response: " + res.statusCode);
resolve(res.statusCode);
}).on('error', function(e) {
console.log("Got error: " + e.message);
reject(e.message);
});
});
}
You need to call callback after function execution.
exports.handler = async (event, context, callback) => {
const response = await getRequest(urltohit);
console.log(response);
callback(null);
}
It's lambda behaviour to keep function running until callback called.
More could be found in official documentation

How do I call a third party Rest API from Firebase function for Actions on Google

I am trying to call a rest API from Firebase function which servers as a fulfillment for Actions on Google.
I tried the following approach:
const { dialogflow } = require('actions-on-google');
const functions = require('firebase-functions');
const http = require('https');
const host = 'wwws.example.com';
const app = dialogflow({debug: true});
app.intent('my_intent_1', (conv, {param1}) => {
// Call the rate API
callApi(param1).then((output) => {
console.log(output);
conv.close(`I found ${output.length} items!`);
}).catch(() => {
conv.close('Error occurred while trying to get vehicles. Please try again later.');
});
});
function callApi (param1) {
return new Promise((resolve, reject) => {
// Create the path for the HTTP request to get the vehicle
let path = '/api/' + encodeURIComponent(param1);
console.log('API Request: ' + host + path);
// Make the HTTP request to get the vehicle
http.get({host: host, path: path}, (res) => {
let body = ''; // var to store the response chunks
res.on('data', (d) => { body += d; }); // store each response chunk
res.on('end', () => {
// After all the data has been received parse the JSON for desired data
let response = JSON.parse(body);
let output = {};
//copy required response attributes to output here
console.log(response.length.toString());
resolve(output);
});
res.on('error', (error) => {
console.log(`Error calling the API: ${error}`)
reject();
});
}); //http.get
}); //promise
}
exports.myFunction = functions.https.onRequest(app);
This is almost working. API is called and I get the data back. The problem is that without async/await, the function does not wait for the "callApi" to complete, and I get an error from Actions on Google that there was no response. After the error, I can see the console.log outputs in the Firebase log, so everything is working, it is just out of sync.
I tried using async/await but got an error which I think is because Firebase uses old version of node.js which does not support async.
How can I get around this?
Your function callApi returns a promise, but you don't return a promise in your intent handler. You should make sure you add the return so that the handler knows to wait for the response.
app.intent('my_intent_1', (conv, {param1}) => {
// Call the rate API
return callApi(param1).then((output) => {
console.log(output);
conv.close(`I found ${output.length} items!`);
}).catch(() => {
conv.close('Error occurred while trying to get vehicles. Please try again later.');
});
});

Sinon NodeJS stub only for module under test

I have a module under test which uses https to PUT data to a response URL. Before doing so, it makes calls to the AWS SDK. I do not want to stub the calls that AWS SDK makes using https, but I do want to stub the call to https.post that my module under test uses (it's an AWS Lambda unit test if that matters).
Consider the following test code
describe('app', function () {
beforeEach(function () {
this.handler = require('../app').handler;
this.request = sinon.stub(https, 'request');
});
afterEach(function () {
https.request.restore();
});
describe('#handler()', function () {
it('should do something', function (done) {
var request = new PassThrough();
var write = sinon.spy(request, 'write');
this.request.returns(request);
var event = {...};
var context = {
done: function () {
assert(write.withArgs({...}).calledOnce);
done();
}
}
this.handler(event, context);
});
});
});
And my module under test (app.js)
var aws = require("aws-sdk");
var promise = require("promise");
exports.handler = function (event, context) {
var iam = new aws.IAM();
promise.denodeify(iam.getUser.bind(iam))().then(function (result) {
....
sendResponse(...);
}, function (err) {
...
});
};
// I only want to stub the use of https in THIS function, not the use of https by the AWS SDK itself
function sendResponse(event, context, responseStatus, responseData) {
var https = require("https");
var url = require("url");
var parsedUrl = url.parse(event.ResponseURL);
var options = {
...
};
var request = https.request(options, function (response) {
...
context.done();
});
request.on("error", function (error) {
...
context.done();
});
// write data to request body
request.write(...);
request.end();
}
How can I accomplish this?
You could use nock to mock specific HTTP/S requests, rather than function calls.
With nock, you can setup URL and request matchers that will allow requests through that don't match what you've defined.
Eg:
nock('https://www.something.com')
.post('/the-post-path-to-mock')
.reply(200, 'Mocked response!');
This would only intercept POST calls to https://www.something.com/the-post-path-to-mock, responding with a 200, and ignore other requests.
Nock also provides many options for mocking responses or accessing the original request data.

Resources