I am trying to trace a serverless express Lambda function using AWSXRay.
I have tried several different approaches to this and nothing seems to work.
If I leave out the aws-xray-sdk-express middleware, I will see all my segments in the Timeline, and see my Lambda function appearing twice in the Trace Map. If I include the express middleware, I will see the middleware segment ('Super Dooper Trace Function') in the Trace Map and just the first subsegment ('MyFirstTrace') in the Timeline (not in the Trace Map).
I am trying to get all Subsegments to appear in both the Timelines and Node Graphs
const AWSXray = require('aws-xray-sdk');
const xrayExpress = require('aws-xray-sdk-express');
const express = require('express');
const awsServerlessExpress = require('aws-serverless-express');
module.exports.handler = async (event, context) => {
const app = express();
app.use(xrayExpress.openSegment('Super Dooper Trace Function'));
app.get('/test', async (req, res) => {
const result = await doWork();
res.send(result);
});
app.use(xrayExpress.closeSegment());
const server = awsServerlessExpress.createServer(app);
return awsServerlessExpress.proxy(server, event, context, 'PROMISE').promise;
}
const doWork = async () => {
const res1 = await traceMyFunction('MyFirstTrace', 3000)
const res2 = await traceMyFunction('MySecondTrace', 3000);
const res3 = await traceMyFunction('MyThirdTrace', 2000);
return [res1, res2, res3];
}
const traceMyFunction = (name, delayMs) => {
return trace(name, async () => {
return delay(delayMs)
});
}
function trace(name, promiseFunction) {
return new Promise((resolve, reject) => {
AWSXray.captureAsyncFunc(name, (subSegment) => {
promiseFunction(subSegment)
.then((result) => {
resolve(result);
subSegment.close();
}).catch((e) => {
subSegment.close();
reject(e)
});
});
});
}
const delay = (ms) => {
return new Promise((res, rej) => {
setTimeout(() => res({ time: ms }), ms)
});
};
This is the resultant XRay Trace
And this is the Trace Raw Data
{
"Duration": 8.278,
"Id": "1-5e2f6cc3-a99e6c08f02ce0aec7ab7121",
"Segments": [
{
"Document": {
"id": "87ca46b60ded68e1",
"name": "Super Dooper Trace Function",
"start_time": 1580166338.92,
"end_time": 1580166347.198,
"http": {
"request": {
"url": "http://localhost/test",
"method": "GET",
"user_agent": "",
"client_ip": ""
},
"response": {
"status": 200
}
},
"aws": {
"xray": {
"sdk": "X-Ray for Node.js",
"sdk_version": "2.5.0",
"package": "aws-xray-sdk"
}
},
"service": {
"version": "unknown",
"runtime": "node",
"runtime_version": "v12.13.0",
"name": "unknown"
},
"trace_id": "1-5e2f6cc3-a99e6c08f02ce0aec7ab7121",
"subsegments": [
{
"id": "98e9f32273700e6e",
"name": "MyFirstTrace",
"start_time": 1580166339.078,
"end_time": 1580166342.082
}
]
},
"Id": "87ca46b60ded68e1"
}
]
}
Serverless Express has known incompatabilities with the current X-Ray SDK - due to the fact Lambda generates its own Segment, then the Express middleware tries to create one as well. We're planning to address this in the near future.
https://github.com/aws/aws-xray-sdk-node/issues/45
For the full explanation, see this thread: https://github.com/aws/aws-xray-sdk-node/issues/30
Related
I use a Dialogflow API as NLP and the interface that we use is Whatsapp API.
my problem is, when I want to bypass Text and Whatsapp client number to Dialogflow (my reference), I didn't found document to explain that. for comparison, the Telegram official integration dialogflow, from the body request we can extract that data like name and Telegram user ID.
const sessionId = phone_number_id; //session ID get from phone number
const sessionPath = sessionClient.projectAgentSessionPath(projectId, sessionId);
const request = {
session: sessionPath,
queryInput: {
text: {
text: msg_body,
languageCode: "id-ID"
},
},
payload: {
data: "testing",
phoneNumber : phone_number_id
}
};
console.log("request", request);
await sessionClient.detectIntent(request).then(responses => {
console.log("DetectIntent", JSON.stringify(responses));
}).catch(err => {
console.error("ERROR:", err);
})
I tried it with request variable like that but in request body in dialogflow fulfillment, it never showed up
{
"responseId": "censored",
"queryResult": {
"queryText": "halo",
"action": "input.welcome",
"parameters": {},
"allRequiredParamsPresent": true,
"fulfillmentText": "error",
"fulfillmentMessages": [
{
"text": {
"text": [
"error"
]
}
}
],
"outputContexts": [
{
"name": "censored",
"parameters": {
"no-input": 0,
"no-match": 0
}
}
],
"intent": {
"name": "censored",
"displayName": "Default Welcome Intent"
},
"intentDetectionConfidence": 1,
"languageCode": "id"
},
"originalDetectIntentRequest": {
"payload": {}
},
"session": "censored"
}
#Maulana ahmad, As you have mentioned in the comment below example code can be referred to extract data from the body request.
const dialogflow = require('dialogflow');
// Import the JSON to gRPC struct converter
const structjson = require('./structjson.js');
// Instantiates a sessison client
const sessionClient = new dialogflow.SessionsClient();
// The path to identify the agent that owns the created intent.
const sessionPath = sessionClient.sessionPath(projectId, sessionId);
// The text query request.
const request = {
session: sessionPath,
queryInput: {
event: {
name: eventName,
parameters: structjson.jsonToStructProto({foo: 'bar'}),
languageCode: languageCode,
},
},
};
sessionClient
.detectIntent(request)
.then(responses => {
console.log('Detected intent');
logQueryResult(sessionClient, responses[0].queryResult);
})
.catch(err => {
console.error('ERROR:', err);
});
This Stack Overflow link can be referred for more information.
Posting the answer as community wiki for the benefit of the community that might encounter this use case in the future.
Feel free to edit this answer for additional information.
So I'm having some issues trying to use sequelize with AWS Lambda, when querying a table where id = 1 it sometimes returns me data and sometimes it doesn't. I read that Sequelize connections and AWS Lambda service don't get well each other because of the how Lambda executes a function.
My question is, is it not enough to open a connection at the top of a function and then close it at the bottom (before returning something)? If not, what else can I do?
Update:
const findCityByPk = async (pathParameters) => {
const { Postgresql: ps } = require('../libs/utils/potsgresql');
console.log(ps.connection.connectionManager);
const { id } = pathParameters;
try {
const city = await City.findByPk(id);
if (city) {
ps.connection.close();
return {
statusCode: 200,
body: JSON.stringify(city)
};
}
ps.connection.close();
return {
statusCode: 500,
body: JSON.stringify({ message: 'City not found`' })
};
} catch (err) {
console.log(err);
await ps.connection.close();
return {
statusCode: 500,
body: JSON.stringify(err)
};
}
}
This is the code I'm testing it sometimes returns me the correct object from my table
{"id":"1","city_name":"Lima2","zip_code":"12312","time_zone_utc":-5} -> this is what is supposed to be returning, and instead I'm getting this object
{
"requestTime": "06/Dec/2021:18:07:24 +0000",
"requestId": "8b5bf017-c180-41cc-9de6-b07599f0e9b8",
"apiId": "xx",
"resourceId": "xx",
"resourcePath": "/city/{id}",
"path": "/dev/city/1",
"httpMethod": "GET",
"status": "500",
"authLatency": "-",
"integrationLatency": "48",
"integrationStatus": "200",
"responseLatency": "50",
"responseLength": "2",
"errorMessage": "-",
"format": "SLS_ACCESS_LOG",
"version": "1.0.0"
}
And also, this is how it's being made the connection
const createConnection = () => {
console.info("[Postgresql] createConnection: start")
console.info("[Postgresql] createConnection: creating conection start")
let conn;
let string_connection;
try {
string_connection = `postgres://${config.DB_USER}:${config.DB_PASSWORD}#${config.DB_HOST}:5432/${config.DB_NAME}`
//console.debug(`[Postgresql] string_connection: ${string_connection}`)
conn = new Sequelize(string_connection, { logging: false, pool: { max: 1, min: 0, idle: 1000 } });
} catch (e) {
console.debug(`[Postgresql] createConnection: creating conection error ${string_connection}`)
throw e;
}
console.info("[Postgresql] createConnection:creating conection end")
return conn;
}
I am trying to integrate dialogflow with firestore but I can't get the required output. Diagnostic Info provides the below error. I need to get the response from the firestore according to the given input from the agent that's the basic requirement but I get this error while integrating.
{
"responseId": "a6a119a8-c406-4af2-aab0-b6863fb091a4-59c3eb0f",
"queryResult": {
"queryText": "15APC2375",
"parameters": {
"regno": "15APC2375"
},
"allRequiredParamsPresent": true,
"intent": {
"name": "projects/cis-bot-yhrvph/agent/intents/e3b1292b-89b1-48b7-b4b4-805a63d08168",
"displayName": "request-results"
},
"intentDetectionConfidence": 0.3,
"diagnosticInfo": {
"webhook_latency_ms": 4892
},
"languageCode": "en"
},
"webhookStatus": {
"code": 4,
"message": "Webhook call failed. Error: DEADLINE_EXCEEDED."
}
}
My Package.json includes the below code.
{
"name": "dialogflowFirebaseFulfillment",
"description": "This is the default fulfillment for a Dialogflow agents using Cloud Functions for Firebase",
"version": "0.0.1",
"private": true,
"license": "Apache Version 2.0",
"author": "Google Inc.",
"engines": {
"node": "10"
},
"scripts": {
"start": "firebase serve --only functions:dialogflowFirebaseFulfillment",
"deploy": "firebase deploy --only functions:dialogflowFirebaseFulfillment"
},
"dependencies": {
"firebase": "^7.13.2",
"actions-on-google": "^2.2.0",
"firebase-admin": "^5.13.1",
"firebase-functions": "^2.0.2",
"dialogflow": "^0.6.0",
"dialogflow-fulfillment": "^0.5.0"
}
}
As well as index.js includes the below code.
'use strict';
const firebase = require('firebase');
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const {WebhookClient} = require('dialogflow-fulfillment');
const {Card, Suggestion} = require('dialogflow-fulfillment');
admin.initializeApp();
const db = admin.firestore();
process.env.DEBUG = 'dialogflow:debug'; // enables lib debugging statements
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
const agent = new WebhookClient({ request, response });
console.log('Dialogflow Request headers: ' + JSON.stringify(request.headers));
console.log('Dialogflow Request body: ' + JSON.stringify(request.body));
function welcome(agent) {
agent.add(`Welcome to CIS BOT!`);
}
function fallback(agent) {
agent.add(`Please Contact the Front Desk for more information`);
}
function getResults(agent){
const regno=agent.parameters.regno;
const dialogflowAgentDoc = db.collection('results').where("reg_id","==",'15APC2375')
.get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
// doc.data() is never undefined for query doc snapshots
agent.add(doc.data().grade);
});
}).catch(() => {
agent.add('Error reading entry from the Firestore database.');
agent.add('Please add a entry to the database first by saying, "Write <your phrase> to the database"');
});
}
let intentMap = new Map();
intentMap.set('Default Welcome Intent', welcome);
intentMap.set('Default Fallback Intent', fallback);
intentMap.set('request-results', getResults);
// intentMap.set('your intent name here', googleAssistantHandler);
agent.handleRequest(intentMap);
});
I want to return connectionId to a client after the client connect to aws websocket.
I'm using apigwManagementApi.postToConnection to send a response to a client, but I always get an absurd error message.
I already try to debug & search in google, but I can't find a solution for this.
patch.js
require('aws-sdk/lib/node_loader');
var AWS = require('aws-sdk/lib/core');
var Service = AWS.Service;
var apiLoader = AWS.apiLoader;
apiLoader.services['apigatewaymanagementapi'] = {};
AWS.ApiGatewayManagementApi = Service.defineService('apigatewaymanagementapi', ['2018-11-29']);
Object.defineProperty(apiLoader.services['apigatewaymanagementapi'], '2018-11-29', {
get: function get() {
var model = {
"metadata": {
"apiVersion": "2018-11-29",
"endpointPrefix": "execute-api",
"signingName": "execute-api",
"serviceFullName": "AmazonApiGatewayManagementApi",
"serviceId": "ApiGatewayManagementApi",
"protocol": "rest-json",
"jsonVersion": "1.1",
"uid": "apigatewaymanagementapi-2018-11-29",
"signatureVersion": "v4"
},
"operations": {
"PostToConnection": {
"http": {
"requestUri": "/#connections/{connectionId}",
"responseCode": 200
},
"input": {
"type": "structure",
"members": {
"Data": {
"type": "blob"
},
"ConnectionId": {
"location": "uri",
"locationName": "connectionId"
}
},
"required": [
"ConnectionId",
"Data"
],
"payload": "Data"
}
}
},
"shapes": {}
}
model.paginators = {
"pagination": {}
}
return model;
},
enumerable: true,
configurable: true
});
module.exports = AWS.ApiGatewayManagementApi;
index.js
const AWS = require('aws-sdk');
require('./patch.js');
exports.handler = async(event) => {
const connectionId = event.requestContext.connectionId;
const apigwManagementApi = new AWS.ApiGatewayManagementApi({
apiVersion: '2018-11-29',
endpoint: event.requestContext.domainName + '/' + event.requestContext.stage
});
await apigwManagementApi.postToConnection({ ConnectionId: connectionId, Data: connectionId }).promise();
return {};
};
client.js
const WebSocket = require('ws');
const ws = new WebSocket('wss://****');
ws.on('open', () => {
console.log('connected ===================>')
ws.on('message', data => console.warn(`From server: ${data}`));
});
Error in cloudwatch
{
"errorMessage": "410",
"errorType": "UnknownError",
"stackTrace": [
"Object.extractError (/var/runtime/node_modules/aws-sdk/lib/protocol/json.js:48:27)",
"Request.extractError (/var/runtime/node_modules/aws-sdk/lib/protocol/rest_json.js:52:8)",
"Request.callListeners (/var/runtime/node_modules/aws-sdk/lib/sequential_executor.js:105:20)",
"Request.emit (/var/runtime/node_modules/aws-sdk/lib/sequential_executor.js:77:10)",
"Request.emit (/var/runtime/node_modules/aws-sdk/lib/request.js:683:14)",
"Request.transition (/var/runtime/node_modules/aws-sdk/lib/request.js:22:10)",
"AcceptorStateMachine.runTo (/var/runtime/node_modules/aws-sdk/lib/state_machine.js:14:12)",
"/var/runtime/node_modules/aws-sdk/lib/state_machine.js:26:10",
"Request.<anonymous> (/var/runtime/node_modules/aws-sdk/lib/request.js:38:9)",
"Request.<anonymous> (/var/runtime/node_modules/aws-sdk/lib/request.js:685:12)"
]
}
I don't know why, but if I'm trying in a custom route, this code can work.
Does anyone know how to solve this?
I'd suggest to look into this example from AWS, there is on connect response for subprotocol confirmation, but I think any payload can be provided.
The most important bit is the route integration settings in the template, basically, the following two lines in the route integration properties:
IntegrationMethod: POST
ConnectionType: INTERNET
then response will be sent to the connected client.
The only way I've found to make this work is to use a DynamoDB table to store connections, then set up a trigger from the table back to a Lambda function.
There are a few catches though. This Lambda function wont work like your index.js file above. You'll have to use NPM install --save aws-sdk on a folder with your index.js file, zip it and upload it to the lambda function, so that the SDK is localized.
You will also need to set up a user with proper access and put the credentials into a your Lambda function.
Note, if you see a 410 error, that means the connection is no longer there, so you're going in the right direction at that point.
const AWS = require('aws-sdk');
require('./patch.js');
var log = console.log;
AWS.config.update({
accessKeyId: "YOURDATAHERE",
secretAccessKey: "YOURDATAHERE"
});
let send = undefined;
function init() {
const apigwManagementApi = new AWS.ApiGatewayManagementApi({
apiVersion: '2018-11-29',
endpoint: "HARDCODEYOURENDPOINTHERE"
});
send = async (connectionId, data) => {
await apigwManagementApi.postToConnection({ ConnectionId: connectionId, Data: `${data}` }).promise();
}
}
exports.handler = async (event, context) => {
init();
console.log('Received event:', JSON.stringify(event, null, 2));
for (const record of event.Records) {
//console.log(record.eventID);
console.log(record.eventName);
console.log('DynamoDB Record: %j', record.dynamodb);
if(record.eventName == "INSERT"){
var connectionId = record.dynamodb.NewImage.connectionId.S;
try{
await send(connectionId, connectionId);
}catch(err){
log("Error", err);
}
log("sent");
}
}
return `Successfully processed ${event.Records.length} records.`;
};
I am trying to search a non primary key using AWS Lambda and integrating it into the Alexa Skills Kit. I am very new to using DynamoDB and Alexa Skills Kit and I'm struggling to find any solutions to this online. The basic premise for what I am trying to do is querying the table yesno with two columns, id and message. Only looking through the message column to find a match with the text i specify in params.
Here is the Lambda code I am working with:
const AWSregion = 'eu-west-1';
const Alexa = require('alexa-sdk');
const AWS = require('aws-sdk');
//params for searching table
const params = {
TableName: 'yesno',
Key:{ "message": 'Ben Davies' }
};
AWS.config.update({
region: AWSregion
});
exports.handler = function(event, context, callback) {
var alexa = Alexa.handler(event, context);
// alexa.appId = 'amzn1.echo-sdk-ams.app.1234';
// alexa.dynamoDBTableName = 'YourTableName'; // creates new table for session.attributes
alexa.registerHandlers(handlers);
alexa.execute();
};
const handlers = {
'LaunchRequest': function () {
this.response.speak('welcome to magic answers. ask me a yes or no question.').listen('try again');
this.emit(':responseReady');
},
'MyIntent': function () {
var MyQuestion = this.event.request.intent.slots.MyQuestion.value;
console.log('MyQuestion : ' + MyQuestion);
readDynamoItem(params, myResult=>{
var say = MyQuestion;
say = myResult;
say = 'you asked, ' + MyQuestion + '. I found a reckord for: ' + myResult;
this.response.speak(say).listen('try again');
this.emit(':responseReady');
});
},
'AMAZON.HelpIntent': function () {
this.response.speak('ask me a yes or no question.').listen('try again');
this.emit(':responseReady');
},
'AMAZON.CancelIntent': function () {
this.response.speak('Goodbye!');
this.emit(':responseReady');
},
'AMAZON.StopIntent': function () {
this.response.speak('Goodbye!');
this.emit(':responseReady');
}
};
// END of Intent Handlers {} ========================================================================================
// Helper Function =================================================================================================
function readDynamoItem(params, callback) {
var AWS = require('aws-sdk');
AWS.config.update({region: AWSregion});
var dynamodb = new AWS.DynamoDB();
console.log('reading item from DynamoDB table');
dynamodb.query(params, function (err, data) {
if (err) console.log(err, err.stack); // an error occurred
else{
console.log(data); // successful response
callback(data.Item.message);
}
});
}
I know I am probably doing this completely wrong but there isn't much online for integrating DynamoDB with an Alexa Skill and the only thing i was able to find was searching by ID. This doesn't work for what i want to do without pulling all the items from the table into a map or a list, and seeing as I want to create a big database it seems quite inefficient.
On the Alexa side of things I am receiving the following service request when testing the code:
{
"session": {
"new": true,
"sessionId": "SessionId.f9558462-6db8-4bf5-84aa-22ee0920ae95",
"application": {
"applicationId": "amzn1.ask.skill.9f280bf7-d506-4d58-95e8-b9e93a66a420"
},
"attributes": {},
"user": {
"userId": "amzn1.ask.account.AF5IJBMLKNE32GEFQ5VFGVK2P4YQOLVUSA5YPY7RNEMDPKSVCBRCPWC3OBHXEXAHROBTT7FGIYA7HJW2PMEGXWHF6SQHRX3VA372OHPZZJ33K7S4K7D6V3PXYB6I72YFIQBHMJ4QGJW3NS3E2ZFY5YFSBOEFW6V2E75YAZMRQCU7MNYPJUMJSUISSUA2WF2RA3CIIDCSEY35TWI"
}
},
"request": {
"type": "IntentRequest",
"requestId": "EdwRequestId.7310073b-981a-41f8-9fa5-03d1b28c5aba",
"intent": {
"name": "MyIntent",
"slots": {
"MyQuestion": {
"name": "MyQuestion",
"value": "erere"
}
}
},
"locale": "en-US",
"timestamp": "2018-01-25T14:18:40Z"
},
"context": {
"AudioPlayer": {
"playerActivity": "IDLE"
},
"System": {
"application": {
"applicationId": "amzn1.ask.skill.9f280bf7-d506-4d58-95e8-b9e93a66a420"
},
"user": {
"userId": "amzn1.ask.account.AF5IJBMLKNE32GEFQ5VFGVK2P4YQOLVUSA5YPY7RNEMDPKSVCBRCPWC3OBHXEXAHROBTT7FGIYA7HJW2PMEGXWHF6SQHRX3VA372OHPZZJ33K7S4K7D6V3PXYB6I72YFIQBHMJ4QGJW3NS3E2ZFY5YFSBOEFW6V2E75YAZMRQCU7MNYPJUMJSUISSUA2WF2RA3CIIDCSEY35TWI"
},
"device": {
"supportedInterfaces": {}
}
}
},
"version": "1.0"
}
And I am receiving a service response error simply saying 'The response is invalid'
Any help with this would be greatly appreciated
I would like to help you in dynamo db part.
In order to access non primary key columns in dynamodb you should perform scan operation.
For your table (yesno), id is a primary key and message is an additional column.
Snippet to access non primary key column [Message]
var dynamodb = new AWS.DynamoDB();
var params = {
TableName: 'yesno',
FilterExpression: 'message = :value',
ExpressionAttributeValues: {
':value': {"S": "Ben Davies"}
}
};
dynamodb.scan(params, function(err, data) {
if (err) // an error occurred
else console.log(data); // successful response
});
Snippet to access primary key column [Id]
var docClient = new AWS.DynamoDB.DocumentClient();
//Get item by key
var params = {
TableName: 'sis_org_template',
Key: { "id": "1"}
};
docClient.get(params, function(err, data) {
if (err) // an error occurred
else console.log(data); // successful response
});