3 ways to send custom metrics to AWS Cloudwatch

·

4 min read

Amazon CloudWatch is a monitoring and observability service. CloudWatch monitors your resources and the applications you run on AWS in real-time. You can use CloudWatch to collect and track metrics, which are variables you can measure for your resources and applications. CloudWatch collects monitoring and operational data in the form of logs, metrics, and events. In this post, I will show you 3 ways to send your own custom metrics to AWS Cloudwatch.

1. Using AWS SDK

Amazon has provided SDKs for many popular languages including C++, Go, Java, Javascript, .NET, Node.js, PHP, Python, and ruby. When it comes to sending metrics, the easiest and most intuitive way is to use the AWS SDK. Let’s say we have a JVM based application and want to send metrics.

To publish your own metric data, call the AmazonCloudWatchClient’s putMetricData method with a PutMetricDataRequest. The PutMetricDataRequest must include the custom namespace to use for the data, and information about the data point itself in a MetricDatum object.

Tip!
A number of AWS services publish their own metrics in namespaces beginning with “AWS/”. Don’t start your namespace’s name with AWS.

Here is the sample code:

import com.amazonaws.services.cloudwatch.AmazonCloudWatch;
import com.amazonaws.services.cloudwatch.AmazonCloudWatchClientBuilder;
import com.amazonaws.services.cloudwatch.model.Dimension;
import com.amazonaws.services.cloudwatch.model.MetricDatum;
import com.amazonaws.services.cloudwatch.model.PutMetricDataRequest;
import com.amazonaws.services.cloudwatch.model.PutMetricDataResult;
import com.amazonaws.services.cloudwatch.model.StandardUnit;

final AmazonCloudWatch cw =
    AmazonCloudWatchClientBuilder.defaultClient();

Dimension dimension = new Dimension()
    .withName("UNIQUE_PAGES")
    .withValue("URLS");

MetricDatum datum = new MetricDatum()
    .withMetricName("PAGES_VISITED")
    .withUnit(StandardUnit.None)
    .withValue(data_point)
    .withDimensions(dimension);

PutMetricDataRequest request = new PutMetricDataRequest()
    .withNamespace("SITE/TRAFFIC")
    .withMetricData(datum);

PutMetricDataResult response = cw.putMetricData(request);

2. Using metric filters

In this way, you send your metrics inside your log data. I know all of you are familiar with logs but to ensure that we all are on the same page, here are 2 sample logs:

127.0.0.1 - frank [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326
20/06/28 06:59:42 INFO SparkContext: Running Spark version 2.4.4

The idea is, put your metrics inside the log and ask Cloudwatch to extract them. For example, if you want to have a metric which counts errors, you can set “filter pattern” to “ERROR”. This will match log event messages that contain this term, such as the following:

  • [ERROR] A fatal exception has occurred

  • Exiting with ERRORCODE: -1

The filter pattern is not limited to just one word. You can provide more complex patterns and you can even set pattern against JSON. To create a metric filter using the AWS CLI, open command prompt and run the following command:

aws logs put-metric-filter \
  --log-group-name MyApp/message.log \
  --filter-name MyAppErrorCount \
  --filter-pattern 'Error' \
  --metric-transformations \
      metricName=ErrorCount,metricNamespace=MyNamespace,metricValue=1,defaultValue=0

Now lets send some logs to Cloudwatch:

aws logs put-log-events \
  --log-group-name MyApp/access.log
  --log-stream-name TestStream1 \
  --log-events \
    timestamp=1394793518000,message="This message contains an Error" \
    timestamp=1394793528000,message="This message also contains an Error"

You should see ErrorCount metric appears in namespace: MyNamespace.

3. Using Embedded Metric Format

Like the metric filter way, this way also relies on string logs. But unlike metric filter which is very limited to simple use cases, with Embedded Metric Format you have all the power of Cloudwatch SDK, but without the need to use SDK at all. There are some use cases where adding SDK is impossible or hard, but with Embedded Metric Format, you do not have to rely on complex architecture or multiple third-party tools to gain insights into these environments. All you need to do is construct a string log which adheres to the Embedded Log Format. The following is a valid example of the embedded metric format:

{
  "_aws": {
    "Timestamp": 1574109732004,
    "CloudWatchMetrics": [
      {
        "Namespace": "lambda-function-metrics",
        "Dimensions": [["functionVersion"]],
        "Metrics": [
          {
            "Name": "time",
            "Unit": "Milliseconds"
          }
        ]
      }
    ]
  },
  "functionVersion": "LATEST",
  "time": 100,
  "requestId": "989ffbf8-9ace-4817-a57c-e4dd734019ee"
}

Here we created a metric called time with value 100 ms in the dimension of functionVersion=LATEST.

Tip!
In order for Cloudwatch to distinguish the Embedded Format from other logs, and extract the metrics from the log, you should send a request with the following header:

x-amzn-logs-format: json/emf

Java example:

PutMetricDataRequest request = ...
request.putCustomRequestHeader("x-amzn-logs-format", "json/emf")

Tip!
On Lambda, you do not need to set this header yourself. Writing JSON to standard out in the embedded metric format is sufficient.

Conclusion

Until now, we have reviewed 3 ways to send custom metrics to AWS. Now it’s time to examine the pros and cons of each of them.

Using AWS SDKUsing filter patternUsing embedded format
prosEasy to useSDK is available in many popular languagesFlexibleSend metrics using logsNo need to add extra SDKFast to implementSend metrics using logsNo need to add extra SDKFlexible
conslimited to simple use cases and patternsNot reliable, patterns can be matched to unwanted logsYou should produce the format on your own or rely on few client libraries