Skip to content

Metrics

Do not use this library in production

AWS Lambda Powertools for TypeScript is currently released as a beta developer preview and is intended strictly for feedback purposes only.
This version is not stable, and significant breaking changes might incur as part of the upcoming production-ready release.

Do not use this library for production workloads.

Metrics creates custom metrics asynchronously by logging metrics to standard output following Amazon CloudWatch Embedded Metric Format (EMF).

These metrics can be visualized through Amazon CloudWatch Console.

Key features

  • Aggregate up to 100 metrics using a single CloudWatch EMF object (large JSON blob)
  • Validate against common metric definitions mistakes (metric unit, values, max dimensions, max metrics, etc)
  • Metrics are created asynchronously by CloudWatch service, no custom stacks needed
  • Context manager to create a one off metric with a different dimension

Terminologies

If you're new to Amazon CloudWatch, there are two terminologies you must be aware of before using this utility:

  • Namespace. It's the highest level container that will group multiple metrics from multiple services for a given application, for example ServerlessEcommerce.
  • Dimensions. Metrics metadata in key-value format. They help you slice and dice metrics visualization, for example ColdStart metric by Payment service.
Metric terminology, visually explained

Getting started

Installation

Install the library in your project:

1
npm install @aws-lambda-powertools/metrics

Utility settings

The library requires two settings. You can set them as environment variables, or pass them in the constructor.

These settings will be used across all metrics emitted:

Setting Description Environment variable Constructor parameter
Metric namespace Logical container where all metrics will be placed e.g. serverlessAirline POWERTOOLS_METRICS_NAMESPACE namespace
Service Optionally, sets service metric dimension across all metrics e.g. payment POWERTOOLS_SERVICE_NAME service

For a complete list of supported environment variables, refer to this section.

Use your application or main service as the metric namespace to easily group all metrics

Example using AWS Serverless Application Model (SAM)

1
2
3
4
5
6
7
import { Metrics } from '@aws-lambda-powertools/metrics';


// Sets metric namespace and service via env var
const metrics = new Metrics();
// OR Sets metric namespace, and service as a metrics parameters
const metrics = new Metrics({namespace:"serverlessAirline", service:"orders"});
1
2
3
4
5
6
7
8
9
Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      Runtime: nodejs14.x
      Environment:
      Variables:
        POWERTOOLS_SERVICE_NAME: payment
        POWERTOOLS_METRICS_NAMESPACE: serverlessAirline

You can initialize Metrics anywhere in your code - It'll keep track of your aggregate metrics in memory.

Creating metrics

You can create metrics using addMetric, and you can create dimensions for all your aggregate metrics using addDimension method.

1
2
3
4
5
6
7
8
9
import { Metrics, MetricUnits } from '@aws-lambda-powertools/metrics';
import { Context } from 'aws-lambda'; 


const metrics = new Metrics({namespace:"serverlessAirline", service:"orders"});

export const handler = async (event: any, context: Context) => {
    metrics.addMetric('successfulBooking', MetricUnits.Count, 1);
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import { Metrics, MetricUnits } from '@aws-lambda-powertools/metrics';
import { Context } from 'aws-lambda';


const metrics = new Metrics({namespace:"serverlessAirline", service:"orders"});

export const handler = async (event: any, context: Context) => {
    metrics.addDimension('environment', 'prod');
    metrics.addMetric('successfulBooking', MetricUnits.Count, 1);
}

Autocomplete Metric Units

MetricUnit enum facilitate finding a supported metric unit by CloudWatch. Alternatively, you can pass the value as a string if you already know them e.g. "Count".

Metrics overflow

CloudWatch EMF supports a max of 100 metrics per batch. Metrics utility will flush all metrics when adding the 100th metric. Subsequent metrics, e.g. 101th, will be aggregated into a new EMF object, for your convenience.

Do not create metrics or dimensions outside the handler

Metrics or dimensions added in the global scope will only be added during cold start. Disregard if that's the intended behaviour.

Adding multi-value metrics

You can call addMetric() with the same name multiple times. The values will be grouped together in an array.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import { Metrics, MetricUnits } from '@aws-lambda-powertools/metrics';
import { Context } from 'aws-lambda'; 


const metrics = new Metrics({namespace:"serverlessAirline", service:"orders"});

export const handler = async (event: any, context: Context) => {
    metrics.addMetric('performedActionA', MetricUnits.Count, 2);
    // do something else...
    metrics.addMetric('performedActionA', MetricUnits.Count, 1);
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
    "performedActionA": [
        2,
        1
    ],
    "_aws": {
        "Timestamp": 1592234975665,
        "CloudWatchMetrics": [
            {
            "Namespace": "serverlessAirline",
            "Dimensions": [
                [
                "service"
                ]
            ],
            "Metrics": [
                {
                "Name": "performedActionA",
                "Unit": "Count"
                }
            ]
            }
        ]
    },
    "service": "orders"
}

Adding default dimensions

You can use add default dimensions to your metrics by passing them as parameters in 4 ways:

  • in the constructor
  • in the Middy middleware
  • using the setDefaultDimensions method
  • in the decorator

If you'd like to remove them at some point, you can use clearDefaultDimensions method.
See examples below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import { Metrics, MetricUnits } from '@aws-lambda-powertools/metrics';
import { Context } from 'aws-lambda'; 

const metrics = new Metrics({
    namespace:"serverlessAirline", 
    service:"orders", 
    defaultDimensions: { 'environment': 'prod', 'anotherDimension': 'whatever' } 
});

export const handler = async (event: any, context: Context) => {
    metrics.addMetric('successfulBooking', MetricUnits.Count, 1);
}

Note

Middy comes bundled with Metrics, so you can just import it when using the middleware.

Using Middy for the first time?

Learn more about its usage and lifecycle in the official Middy documentation.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import { Metrics, MetricUnits, logMetrics } from '@aws-lambda-powertools/metrics';
import { Context } from 'aws-lambda';
import middy from '@middy/core';

const metrics = new Metrics({ namespace: 'serverlessAirline', service: 'orders' });

const lambdaHandler = async (event: any, context: Context) => {
    metrics.addMetric('successfulBooking', MetricUnits.Count, 1);
}

export const handler = middy(lambdaHandler)
    .use(logMetrics(metrics, { defaultDimensions:{ 'environment': 'prod', 'anotherDimension': 'whatever' }  }));
1
2
3
4
5
6
7
8
9
import { Metrics, MetricUnits } from '@aws-lambda-powertools/metrics';
import { Context } from 'aws-lambda'; 

const metrics = new Metrics({namespace:"serverlessAirline", service:"orders"});
metrics.setDefaultDimensions({ 'environment': 'prod', 'anotherDimension': 'whatever' });

export const handler = async (event: any, context: Context) => {
    metrics.addMetric('successfulBooking', MetricUnits.Count, 1);
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import { Metrics, MetricUnits } from '@aws-lambda-powertools/metrics';
import { Context, Callback } from 'aws-lambda';

const metrics = new Metrics({namespace:"serverlessAirline", service:"orders"});
const DEFAULT_DIMENSIONS = {"environment": "prod", "another": "one"};

export class MyFunction {

    @metrics.logMetrics({defaultDimensions: DEFAULT_DIMENSIONS})
    public handler<TEvent, TResult>(_event: TEvent, _context: Context, _callback: Callback<TResult>): void | Promise<TResult> {
        metrics.addMetric('successfulBooking', MetricUnits.Count, 1);
    }
}

Flushing metrics

As you finish adding all your metrics, you need to serialize and "flush them" (= print them to standard output).

You can flush metrics automatically using one of the following methods:

Using the Middy middleware or decorator will automatically validate, serialize, and flush all your metrics. During metrics validation, if no metrics are provided then a warning will be logged, but no exception will be raised. If you do not the middleware or decorator, you have to flush your metrics manually.

Metric validation

If metrics are provided, and any of the following criteria are not met, a RangeError exception will be raised:

Using Middy middleware

See below an example of how to automatically flush metrics with the Middy-compatible logMetrics middleware.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    import { Metrics, MetricUnits, logMetrics } from '@aws-lambda-powertools/metrics';
    import { Context } from 'aws-lambda';
    import middy from '@middy/core';

    const metrics = new Metrics({ namespace: 'exampleApplication' , service: 'exampleService' });

    const lambdaHandler = async (event: any, context: Context) => {
        metrics.addMetric('bookingConfirmation', MetricUnits.Count, 1);
    }

    export const handler = middy(lambdaHandler)
        .use(logMetrics(metrics));
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
    "bookingConfirmation": 1.0,
    "_aws": {
        "Timestamp": 1592234975665,
        "CloudWatchMetrics": [
            {
            "Namespace": "exampleApplication",
            "Dimensions": [
                [
                "service"
                ]
            ],
            "Metrics": [
                {
                "Name": "bookingConfirmation",
                "Unit": "Count"
                }
            ]
            }
        ]
    },
    "service": "exampleService"
}

Using the class decorator

Info

Decorators can only be attached to a class declaration, method, accessor, property, or parameter. Therefore, if you prefer to write your handler as a standard function rather than a Class method, check the middleware or manual method sections instead.
See the official TypeScript documentation for more details.

The logMetrics decorator of the metrics utility can be used when your Lambda handler function is implemented as method of a Class.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import { Metrics, MetricUnits } from '@aws-lambda-powertools/metrics';
import { Context, Callback } from 'aws-lambda'; 

const metrics = new Metrics({namespace:"exampleApplication", service:"exampleService"});

export class MyFunction {

    @metrics.logMetrics()
    public handler<TEvent, TResult>(_event: TEvent, _context: Context, _callback: Callback<TResult>): void | Promise<TResult> {
        metrics.addMetric('bookingConfirmation', MetricUnits.Count, 1);
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
    "bookingConfirmation": 1.0,
    "_aws": {
        "Timestamp": 1592234975665,
        "CloudWatchMetrics": [
            {
            "Namespace": "exampleApplication",
            "Dimensions": [
                [
                "service"
                ]
            ],
            "Metrics": [
                {
                "Name": "bookingConfirmation",
                "Unit": "Count"
                }
            ]
            }
        ]
    },
    "service": "exampleService"
}

Manually

You can manually flush the metrics with publishStoredMetrics as follows:

Warning

Metrics, dimensions and namespace validation still applies.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import { Metrics, MetricUnits } from '@aws-lambda-powertools/metrics';

const metrics = new Metrics();

const lambdaHandler: Handler = async () => {
    metrics.addMetric('test-metric', MetricUnits.Count, 10);
    const metricsObject = metrics.serializeMetrics();
    metrics.publishStoredMetrics();
    console.log(JSON.stringify(metricsObject));
};

Throwing a RangeError when no metrics are emitted

If you want to ensure that at least one metric is emitted before you flush them, you can use the raiseOnEmptyMetrics parameter and pass it to the middleware or decorator:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    import { Metrics, MetricUnits, logMetrics } from '@aws-lambda-powertools/metrics';
    import { Context } from 'aws-lambda';
    import middy from '@middy/core';

    const metrics = new Metrics({namespace:"exampleApplication", service:"exampleService"});

    const lambdaHandler = async (event: any, context: Context) => {
        metrics.addMetric('bookingConfirmation', MetricUnits.Count, 1);
    }

    export const handler = middy(lambdaHandler)
        .use(logMetrics(metrics, { raiseOnEmptyMetrics: true }));

Capturing a cold start invocation as metric

You can optionally capture cold start metrics with the logMetrics middleware or decorator via the captureColdStartMetric param.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import { Metrics, MetricUnits, logMetrics } from '@aws-lambda-powertools/metrics';
import { Context } from 'aws-lambda';
import middy from '@middy/core';

const metrics = new Metrics({namespace: 'serverlessAirline', service: 'orders' });

const lambdaHandler = async (event: any, context: Context) => {
    metrics.addMetric('successfulBooking', MetricUnits.Count, 1);
}

export const handler = middy(lambdaHandler)
    .use(logMetrics(metrics, { captureColdStartMetric: true } }));
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import { Metrics, MetricUnits } from '@aws-lambda-powertools/metrics';
import { Context, Callback } from 'aws-lambda'; 
import middy from '@middy/core';

const metrics = new Metrics({namespace: 'serverlessAirline', service: 'orders' });

export class MyFunction {

    @metrics.logMetrics({ captureColdStartMetric: true })
    public handler<TEvent, TResult>(_event: TEvent, _context: Context, _callback: Callback<TResult>): void | Promise<TResult> {
        metrics.addMetric('successfulBooking', MetricUnits.Count, 1);
    }
}

If it's a cold start invocation, this feature will:

  • Create a separate EMF blob solely containing a metric named ColdStart
  • Add function_name, service and default dimensions

This has the advantage of keeping cold start metric separate from your application metrics, where you might have unrelated dimensions.

We do not emit 0 as a value for the ColdStart metric for cost-efficiency reasons. Let us know if you'd prefer a flag to override it.

Advanced

Adding metadata

You can add high-cardinality data as part of your Metrics log with addMetadata method. This is useful when you want to search highly contextual information along with your metrics in your logs.

Warning

This will not be available during metrics visualization - Use dimensions for this purpose

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    import { Metrics, MetricUnits, logMetrics } from '@aws-lambda-powertools/metrics';
    import { Context } from 'aws-lambda';
    import middy from '@middy/core';

    const metrics = new Metrics({namespace:"serverlessAirline", service:"orders"});

    const lambdaHandler = async (event: any, context: Context) => {
        metrics.addMetadata('bookingId', '7051cd10-6283-11ec-90d6-0242ac120003');
    }

    export const handler = middy(lambdaHandler)
        .use(logMetrics(metrics));
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
    "successfulBooking": 1.0,
    "_aws": {
        "Timestamp": 1592234975665,
        "CloudWatchMetrics": [
            {
            "Namespace": "exampleApplication",
            "Dimensions": [
                [
                "service"
                ]
            ],
            "Metrics": [
                {
                "Name": "successfulBooking",
                "Unit": "Count"
                }
            ]
            }
        ]
    },
    "service": "booking",
    "bookingId": "7051cd10-6283-11ec-90d6-0242ac120003"
}

Single metric with different dimensions

CloudWatch EMF uses the same dimensions across all your metrics. Use singleMetric if you have a metric that should have different dimensions.

Info

For cost-efficiency, this feature would be used sparsely since you pay for unique metric. Keep the following formula in mind:

unique metric = (metric_name + dimension_name + dimension_value)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import { Metrics, MetricUnits, logMetrics } from '@aws-lambda-powertools/metrics';
import { Context } from 'aws-lambda';
import middy from '@middy/core';

const metrics = new Metrics({namespace:"serverlessAirline", service:"orders"});

const lambdaHandler = async (event: any, context: Context) => {
    metrics.addDimension('metricUnit', 'milliseconds');
    // This metric will have the "metricUnit" dimension, and no "metricType" dimension:
    metrics.addMetric('latency', MetricUnits.Milliseconds, 56);

    const singleMetric = metrics.singleMetric();
    // This metric will have the "metricType" dimension, and no "metricUnit" dimension:
    singleMetric.addDimension('metricType', 'business');
    singleMetric.addMetric('orderSubmitted', MetricUnits.Count, 1);
}

export const handler = middy(lambdaHandler)
    .use(logMetrics(metrics, { captureColdStartMetric: true } }));
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import { Metrics, MetricUnits } from '@aws-lambda-powertools/metrics';
import { Context, Callback } from 'aws-lambda';

const metrics = new Metrics({namespace:"serverlessAirline", service:"orders"});

export class MyFunction {

    @metrics.logMetrics()
    public handler<TEvent, TResult>(_event: TEvent, _context: Context, _callback: Callback<TResult>): void | Promise<TResult> {
        metrics.addDimension('metricUnit', 'milliseconds');
        // This metric will have the "metricUnit" dimension, and no "metricType" dimension:
        metrics.addMetric('latency', MetricUnits.Milliseconds, 56);

        const singleMetric = metrics.singleMetric();
        // This metric will have the "metricType" dimension, and no "metricUnit" dimension:
        singleMetric.addDimension('metricType', 'business');
        singleMetric.addMetric('orderSubmitted', MetricUnits.Count, 1);
    }
}

Last update: 2022-01-04
Back to top