These days, the serverless approach to building applications is only getting more and more popular. It looks like the large-scale adoption of serverless is expected to take place this year, with the FaaS market possibly growing to $7.72 billion. 

Considering the above, it’s critical to keep up with the times and learn how to build production-ready serverless apps, meaning apps with a high level of reliability and continuous accessibility. One of the keys to achieving this is having a proper monitoring setup, allowing developers to predict and react to various scenarios in time.

In this blog, we’ll build a simple serverless app on AWS and cover everything you need to know about monitoring Lambda, probably the most popular and common serverless function in the market, using Epsagon. But first, let’s go over the basics.

AWS Lambda Monitoring and Observability

AWS Lambda is a well-known FaaS (Function as a Service) that allows you to execute functions in response to particular events occurring in your application. The service is very cost-effective and bills only for the total number of function invocations and their total execution time—meaning you’ll never pay for when your serverless app is idle. 

Since AWS Lambda is fully managed by AWS, a lot of engineers really love to develop applications using it because they can then spend more time writing the code and thinking about the logic instead of solving infrastructure tasks. However, there is one more thing that developers have to think about: observability and monitoring, which is an essential part of production-level systems.

Observability means understanding what’s happening inside your application—over time and in various scenarios. Good observability tooling should include log collecting and searches, trace-based metrics, tracking of events and payloads, and different configurable alerts; it should also have all of these preferably in one place. 

Despite AWS being a great computing provider, it doesn’t have an out-of-the-box solution to handle proper observability that’s also easy to set up and use. Epsagon is a complete monitoring and observability tool that fully covers the above needs and more.

Monitoring Lambda with Epsagon

Epsagon provides you with full visibility into how your serverless application is performing. It is tightly integrated with AWS, is very easy to implement for any existing or new project, and doesn’t require any complicated configurations or maintenance.

Using various visualizations on the Epsagon dashboard, you’ll be able to evaluate overall system health, detect bottlenecks, predict costs, and, of course, potentially find many useful insights based on the collected data and metrics. 

Correlated Logs and Traces

It’s definitely worth noting that all the data aggregated by Epsagon are correlated, which is extremely important and useful in distributed architectures using AWS Lambda and other serverless services. Distributed means that each logical step of the app is separated from the rest and is usually stateless, meaning that it doesn’t care what happened before this step and what’s going to happen next; it simply processes its current input and returns the result. Such an approach works great, is very scalable, and is cost-effective; however, it’s extremely tricky to monitor and debug.

An Example

Imagine an application that performs some “important” calculations in response to each user’s numeric input. For performance, scalability, and some other reasons, engineers decide to divide the whole process into multiple steps:

  1. Initial numeric input comes from Gateway into Lambda, which then publishes a message into an SNS topic.
  2. SNS triggers another Lambda which decides what calculations to perform and sends this information to the SQS queue.
  3. The final lambda takes messages from a queue, performs calculations, and saves the results into DynamoDB. 

Figure 1: Sample project

Even with such a simple scenario, it’s almost impossible to effectively monitor how the system is coping with the whole process because each step is stateless and its logs are not linked to any information about initial requests and other steps. Previously, developers used to change SNS/SQS messages and perform other tricks in order to manually link steps to each other and then be able to find the corresponding logs in CloudWatch, which, of course, they did manually as well.

Fortunately, such tricks are not required anymore, because Epsagon does all the work for you and efficiently displays all the information, saving developers time.

Trying It Out

Let’s build a simple serverless application, then connect it to Epsagon and see how powerful it is. To continue with this section, you’ll need to:

You’ll also have to register for an Epsagon account and follow all the steps in the onboarding wizard to integrate it into your AWS account; finally, you’ll have to install a special AWS CloudFormation stack that will bring the magic into your AWS environment.

In this example project, you’ll be implementing the scenario described in the previous section and will then be able to try out Epsagon yourself.

Go ahead and open a terminal, navigate to a directory on your machine where you’d like to start the development, and type:

$ serverless create --template aws-nodejs --path tracing-example

This command will create a “tracing-example” directory with all the essential files inside.

Coding the Service

The previous command generated serverless.yml and handler.js files. The first one is where you can list all of the resources and functions needed by your application, all of which will be created when your app is deployed. 

Open the serverless.yml file, and replace its contents with the following:

service: supertracing
frameworkVersion: '2'
provider:
  name: aws
  runtime: nodejs12.x
  stage: dev
  region: eu-west-1
  iamRoleStatements:
    - Effect: Allow
      Action:
        - sqs:SendMessage
        - sqs:ReceiveMessage
      Resource: "*"
    - Effect: Allow
      Action:
        - sns:Publish
        - sns:Subscribe
      Resource: "*"
    - Effect: Allow
      Action:
        - dynamodb:PutItem
      Resource: "*"


resources:
  Resources:
    snsTopic:
      Type: AWS::SNS::Topic
      Properties:
        DisplayName: actionTopic
        TopicName: actionTopic
    sqsQueue:
      Type: AWS::SQS::Queue
      Properties:
        QueueName: sqsQueue
        VisibilityTimeout: 60
    dynamodbTable:
      Type: AWS::DynamoDB::Table
      DeletionPolicy: Retain
      Properties:
        AttributeDefinitions:
          - AttributeName: uniqId
            AttributeType: S
        KeySchema:
          - AttributeName: uniqId
            KeyType: HASH
        BillingMode: PAY_PER_REQUEST
        TableName: supertracing-data


functions:
  publisher:
    handler: publisher.handler
    events:
      - http:
          path: start
          method: post
    environment:
      SNS_ARN: !Ref snsTopic


  getAction:
    handler: getAction.handler
    events:
      - sns:
          topicName: actionTopic
          arn: !Ref snsTopic
    environment:
      SQS_URL: !Ref sqsQueue


  calculate:
    handler: calculate.handler
    events:
      - sqs:
          arn:
            Fn::GetAtt:
              - sqsQueue
              - Arn

Here, you can see that you’ve declared a service named “super-tracing” with permissions to use SQS, SNS, and DynamoDB. You’ve also described all the required resources like the DynamoDB table, SNS topic, and SQS queue, while below the resources section, you have definitions for all three Lambda functions. (Note: There are many more possible properties that allow you to tune your application with a serverless framework.)

Now, delete the handler.js file and create three new files, one for each of your functions: publisher.js, getAction.js, and calculate.js.

Now, open the publisher.js file, and paste the following code there:

const AWS = require("aws-sdk");
const SNS_ARN = process.env.SNS_ARN;
const handler = async (event) => {
  const { inputNumber } = JSON.parse(event.body);
  const sns = new AWS.SNS();
  const params = {
    Message: JSON.stringify({ inputNumber }),
    TopicArn: SNS_ARN
  }
  await sns.publish(params).promise();
  return {
    statusCode: 200
  };
};
module.exports.handler = handler;

The above function will be executed in response to an HTTP POST request from a user and then send an input number to the SNS topic.

The next function is for selecting a random action among “square,” “cube,” and “root” and forwarding all the information to SQS. Open the getAction.js file and put the following code there:

const AWS = require("aws-sdk");
const SQS_URL = process.env.SQS_URL;
const handler = async (event) => {
  const message = JSON.parse(event.Records[0].Sns.Message);
  const { inputNumber } = message;
  const sqs = new AWS.SQS();
  const random = Math.random();
  let action;
  if (random < 0.33) {
    action = "square";
  } else if (random >= 0.33 && random < 0.66) {
    action = "root";
  } else {
    action = "cube";
  }
  const params = {
    MessageBody: JSON.stringify({ inputNumber, action }),
    QueueUrl: SQS_URL
  };
  await sqs.sendMessage(params).promise();
};
module.exports.handler = handler;

And the last one you need is for receiving messages from SQS, performing calculations, and saving the final result into DynamoDB. Note that since DynamoDB requires some primary unique key to be used, you’ll be using the message identifier for that. Open the calculate.js file, and put the following code there:

const AWS = require("aws-sdk");
const handler = async (event) => {
  const record = event.Records[0];
  const message = JSON.parse(record.body);
  const { inputNumber, action } = message;
  let result;
  if (action === "square") {
    result = Number(inputNumber) * Number(inputNumber);
  } else if (action === "root") {
    result = Math.sqrt(Number(inputNumber));
  } else {
    result = Number(inputNumber) * Number(inputNumber) * Number(inputNumber);
  }
  const dbClient = new AWS.DynamoDB.DocumentClient();
  const params = {
    TableName: "supertracing-data",
    Item: {
      uniqId: record.messageId,
      result
    }
  }
  await dbClient.put(params).promise();
};
module.exports.handler = handler;

Now, deploy the service by running the command below:

$ serverless deploy

If everything is OK, you will see logs of the deployment process and the endpoint of your service in the end:

Figure 2: Successful deployment

Testing the Service

To test your service, you need to send POST requests to the endpoint that was created after deployment. You can do this via the Postman application or simply by using the command-line tool “curl”:

curl -X POST -d '{"inputNumber":13}' https://your-endpoint.amazonaws.com/dev/start

This will send number 13 to your service and have an empty response. Make a few requests with various numbers, then navigate to DynamoDB in the AWS console. If everything is fine, you should see some random numbers appearing in the items list.

Figure 3: Just-deployed service works perfectly

Congratulations, you have just deployed a serverless application.

Enabling Epsagon

Now, if you open the Epsagon application after testing your service and navigate to any of your function pages, you’ll see the list of invocations and some basic information:

Figure 4: Basic information about executions

This information can be useful, but to achieve a better experience and all the functionality Epsagon has to offer, you need to enable tracing. This can be done in multiple ways:

The first one is cool but not flexible enough because you won’t be able to add custom tags from the code. The second method is good for your purposes since you are using a serverless framework in the current example. However, the third is better because it is completely agnostic of what libraries or tools you use and will work for any Lambda function.

First, initialize the node package manager in the current directory using this command:

$ npm init

Simply confirm any prompts, and then run the following command to install the Epsagon module:

$ npm install epsagon

Now, you just need to initialize Epsagon in the code and wrap the handler in each of your functions. In the handler.js file, add the following code on the first line: 

const epsagon = require('epsagon');
epsagon.init({
  token: 'token-from-epsagon-settings-page',
  appName: 'app-supertracing',
  metadataOnly: false
});

Replace the last line in each of the three files with this:

module.exports.handler = epsagon.lambdaWrapper(handler);

This would be enough to make things work! Just a few lines of code and your serverless service is on a new level of observability! 

But let’s also add a few custom tags to the code to be able to see even more useful information on the dashboard later. For example, let’s track which action was chosen in the getAction function. Simply add one line after the “if/else” block in the code like this:

// rest of the code
epsagon.label("action", action);
// rest of the code

This will mark each execution’s trace with the custom tag “action,” indicating what math operation was randomly selected for the particular request. Now, deploy the application: 

$ serverless deploy

Send a dozen requests to your service to let Epsagon aggregate some data regarding Lambda executions. Also, try to send an empty “inputNumber” parameter or any invalid values in order to intentionally cause some errors.

Epsagon in Action

Now it’s time to get an idea of what Epsagon brings to the table.

Trace Search and Dashboards

Navigate to the Epsagon application and open the “Traces” section from the left-side menu. You will now see every single trace of your application, like incoming requests, SNS and SQS usage, database requests, etc:

Figure 5: List of traces

You can also click on any item in the list and see detailed information about that particular operation:

Figure 6: Detailed view of a trace

 

Look how easily you can see all the steps related to a particular request—their timing, inputs/outputs, logs, and custom labels (like your “action” one). Monitoring systems and getting insights with correlated information is a real pleasure for a developer. Note that this powerful functionality was achieved in just a few lines of code!

Since today we are talking about monitoring Lambda, let’s create a custom dashboard with some specific metrics for your service and also configure some alerts that will be sent via email.

Using the trace-search feature, select filters you’re interested in, check out the results charts and add them to a dashboard if you want. For example, the dashboard below consists of custom charts indicating the count of particular “actions,” the average duration for each of your functions, and the general error and request rate.

Figure 7: Custom dashboard

Issue Reports and Alerts

When your application experiences problems, Epsagon will automatically send you an email; however, you may also check out any problems in the Issues Manager under the “Issues” section. 

Figure 8: Issues Manager

Of course, you can click on any item in the list to be taken to the Trace Search page with the proper filter enabled automatically. Great! But what if you want to receive notifications when some specific scenario occurs in the application? Navigate to the “Alerts” section and press the “create new alert” button. Here, you can create a custom alert based on the trace metrics, including custom tags, or lambda metrics and events. For example, you may want to be notified if an execution takes suspiciously long, especially if it was not a Lambda cold start.

Figure 9: Creating a custom alert

One nice feature of Epsagon is that email is not the only communication channel available; you can also be alerted via systems like Slack, Teams, Telegram, and others or even create a webhook to your selected endpoint.

Service Map

Finally, you can always get a simple overview of your system by visiting the “Service Map” section. This lets you see the complete schema of resources and connections of your application. Thicker lines indicate a higher total count of that particular operation, while a red line indicates issues are happening in that given part.

Figure 10: Service map

Lambda Monitoring with Epsagon

You may consider a combination of multiple open-source monitoring tools or ones provided by AWS like CloudWatch, X-Ray, and others, but as we already mentioned, these are much trickier to set up, harder to understand, and at times not so sufficient to work with. Another benefit of using Epsagon is that you actually kill two birds with one stone because you receive not only the monitoring service but also a great tool for troubleshooting and debugging your application.

To learn more about monitoring AWS Lambda with Epsagon, check out our documentation here. To take the next step, start your Epsagon 14-day free trial.

Read More:

Monitoring Microservices-based Environments Using Epsagon

Monitoring Amazon ECS Clusters with Epsagon

How to Troubleshoot API Errors with Epsagon