Raportowanie metryk CloudWatch bez użycia AWS SDK

16 stycznia 2020 · 2 minuty czytania

Zdarza się, że pisząc funkcję Lambda, potrzebujemy wysłać niestandardową metrykę do CloudWatch Metrics. Może to być metryka techniczna (np. długość żądania do zewnętrznej usługi) lub metryka odnośnie do procesu biznesowego (np. liczba logowań użytkowników). Oczywistym sposobem zrobienia tego jest wykorzystanie metody putMetricData z AWS SDK:

const AWS = require('aws-sdk')
const cloudwatch = new AWS.CloudWatch()

exports.handler = async () => {
  // business logic

  const metric = {
    Namespace: 'Service1',
    MetricData: [
      {
        MetricName: 'loginAttempts',
        Dimensions: [
          {
            Name: 'tenant',
            Value: 'client1'
          }
        ],
        Unit: 'Count',
        Value: 1
      }
    ]
  }

  await cloudwatch.putMetricData(metric).promise()

  // more business logic
}

To rozwiązanie wystarczy, jeżeli sporadycznie wysyłamy takie metryki. Problem zaczyna się, jeżeli tych metryk chcemy wysyłać dużo, z różnych miejsc w kodzie funkcji. Wywołanie metody putMetricData tak samo, jak wywołanie każdej innej metody z AWS SDK, wydłuża czas działania funkcji, a tym samym zwiększając jej koszty. Poza tym w jednym wywołaniu możemy wysłać tylko 40 KB danych. Pod koniec 2019 roku AWS umożliwiło raportowanie metryk bez potrzeby używanie AWS SDK.

Embedded Metric Format

Można to zrobić logując dane do stdout w odpowiednim formacie. Ten format to Embedded Metric Format i jest to JSON o odpowiedniej strukturze. Na przykład:

{
    "_aws": {
        "Timestamp": 1579211886742,
        "CloudWatchMetrics": [
            {
                "Dimensions": [
                    [
                        "tenant"
                    ]
                ],
                "Metrics": [
                    {
                        "Name": "loginAttempts",
                        "Unit": "Count"
                    }
                ],
                "Namespace": "Service1"
            }
        ]
    },
    "loginAttempts": 1,
    "tenant": "client1"
}

Aby ułatwić tworzenie takiego obiektu AWS udostępnił biblioteki dla Node.js i Pythona. Powyższy przykład wykorzystujący AWS SDK można teraz zapisać w następujący sposób:

const { createMetricsLogger, Unit } = require("aws-embedded-metrics")

exports.handler = async (event, context) => {
  // business logic

  const metrics = createMetricsLogger()
  metrics.setNamespace('Service1')
  metrics.putMetric('loginAttempts', 1, Unit.Count)
  metrics.setDimensions({ tenant: 'client1' })
  await metrics.flush()

  // more business logic
}

“Pod spodem” nie ma żadnych wywołań sieciowych do AWS, więc czas wywołania funkcji się nie zwiększa. Dane są logowane do stdout a resztę zajmuje się CloudWatch, przetwarzając je i publikując jako metryki. Dodatkowo nasza funkcja nie potrzebuje uprawnienia cloudwatch:PutMetricData.

Biblioteka aws-embedded-metrics udostępnia również wrapper dla funkcji Lambda, który eliminuje potrzebę ręcznego wywoływania metody flush(). Dzięki temu raportowanie metryk może być rozsiane po kodzie a wysłanie ich do stdout odbędzie się tylko raz na końcu wywołania funkcji.

const { metricScope } = require('aws-embedded-metrics')

exports.handler = metricScope(metrics => async () => {
  // business logic

  metrics.setNamespace('Service2')
  metrics.putMetric('loginAttempts', 1, Unit.Count)
  metrics.setDimensions({ tenant: 'client2' })
  metrics.setProperty('RequestId', context.awsRequestId)

  // more business logic
})

Dodatkowo, korzystając z metody setProperty możemy dodać opcjonalne parametry, które później możemy wyszukać w CloudWatch Logs Insights.

Podsumowowując, dzięki Embedded Metric Format możemy w optymalny sposób wysłać dużo niestandardowych metryk do usługi CloudWatch, nie wydłużając przy tym działania funkcji.