Description
Is this related to an existing feature request or issue?
Which AWS Lambda Powertools utility does this relate to?
Metrics
Summary
Problem
Customers has been using Lambda Powertools alongside with third party observability provider for Logging, metrics and tracing. Focusing on metrics part (this RFC is a part of support for observability provider. For Logging please check out this RFC), while Powertools for AWS Lambda (Powertools) provided powerful and easy to use metrics class. However, Powertools only supports AWS CloudWatch Embedded Metric Format (EMF), which is unfriendly to third party observability provider, bringing hardship to our customers when trying to ingest this format into other observability solutions, such as DataDog.
Goal
Goal for this RFC is to enable third party observability provider like Datadog, NewRelic, Lumigo, etc. to create their own Powertools specific metric provider and offer to their own customers. e.g., DataDog metric format. And if customer eventually want to add additional value or add features themselves. They can easily do so by overriding a provider too.
Use case
Typical use case of this is utility would be for customers who use Lambda Powertools to collect metrics and want to have them ingested into a third party observability provider.
Proposal
Current metric experience
The current Powertools’ metrics utility creates custom metrics asynchronously by logging metrics to standard output following Amazon CloudWatch Embedded Metric Format (EMF), and these metrics can then be seen in the CloudWatch console. This utility can aggregate up to 100 metrics using a single CloudWatch EMF object (which is in JSON format).
from aws_lambda_powertools import Metrics
from aws_lambda_powertools.metrics import MetricUnit
from aws_lambda_powertools.utilities.typing import LambdaContext
# Current experience in using metrics should not change, i.e. these metrics would still output to CW
metrics = Metrics()
@metrics.log_metrics # ensures metrics are flushed upon request completion/failure
def lambda_handler(event: dict, context: LambdaContext):
metrics.add_metric(name="SuccessfulBooking", unit=MetricUnit.Count, value=1)
# JSON output
# "_aws": {
# "Timestamp": 1656685750622,
# "CloudWatchMetrics": [
# {
# "Namespace": "ServerlessAirline",
# "Dimensions": [
# [
# "environment",
# "service"
# ]
# ],
# "Metrics": [
# {
# "Name": "TurbineReads",
# "Unit": "Count"
# }
# ]
# }
# ]
# },
# "environment": "dev",
# "service": "booking",
# "TurbineReads": [
# 1.0,
# 8.0
# ]
# }
Metric provider proposal
For this new utility, we propose a new metrics class for observability providers. With an optional parameter provider
to allow developers to pass in observability provider pre-defined or user custom provider. And the output will be in observability provider friendly format. The below code snippet is a rudimentary look at how this utility can be used and how it will function.
The default use case for metrics before is metrics=Metrics()
After we have this provider feature, Customers can still use original CloudWatch Metrics by metrics=Metrics()
or metrics=CloudWatchEMF()
. They can also use provider for third party provider by e.g.:Metrics=DataDogMetrics()
from aws_lambda_powertools import Metrics
from aws_lambda_powertools.metrics import MetricUnit
from aws_lambda_powertools.utilities.typing import LambdaContext
from aws_lambda_powertools.metrics.provider import DatadogMetrics
from aws_lambda_powertools.metrics.provider import DatadogMetricsProvider
# Use datadog-defined metrics provider
provider = DatadogMetricsProvider()
metrics = DatadogMetrics(provider=provider)
@metrics.log_metrics
def lambda_handler(event: dict, context: LambdaContext):
metrics.add_metric(name="SuccessfulBooking", value=1, tags={"product":"ticket","ordier":"online"})
# JSON output to log
# {
# "m": "SuccessfulBooking",
# "v": 1,
# "e": 1678509106,
# "t": "['product:ticket', 'order:online']"
# }
self-defined metrics provider usage
If the customer would like to use another observability provider, or define their own metrics functions, we will define an interface that the customer can implement and pass in to the Metrics class provider parameter
from aws_lambda_powertools.metrics.provider import MetricsBase, MetricsProviderBase
class DataDogProvider(MetricsProviderBase):
def __init__(self, namespace):
def add_metric(self, name: str, value: float, timestamp: Optional[int] = None, tags: Optional[List] = None):
self.metrics.append({"m": name, "v": int(value), "e": timestamp, "t": tags})
def serialize(self) -> Dict:
# logic here is to add dimension and metadata to each metric's tag with "key:value" format
extra_tags: List = []
output_list: List = []
for single_metric in self.metrics:
output_list.append(
{
"m": f"{self.namespace}.{single_metric['m']}",
"v": single_metric["v"],
"e": single_metric["e"],
"t": single_metric["t"] + extra_tags,
}
)
return {"List": output_list}
def flush(self, metrics):
# flush
def clear(self):
class DataDogMetrics(MetricsBase):
"""Class for datadog metrics standalone class.
Example
-------
dd_provider = DataDogProvider(namespace="default")
metrics = DataDogMetrics(provider=dd_provider)
@metrics.log_metrics(capture_cold_start_metric: bool = True, raise_on_empty_metrics: bool = False)
def lambda_handler(event, context)
metrics.add_metric(name="item_sold",value=1,tags)
"""
# `log_metrics` and `_add_cold_start_metric` are directly inherited from `MetricsBase`
def __init__(self, provider):
self.provider = provider
super().__init__()
def add_metric(self, name: str, value: float, timestamp: Optional[int] = None, tags: Optional[List] = None):
self.provider.add_metric(name=name, value=value, timestamp=timestamp, tags=tags)
def flush_metrics(self, raise_on_empty_metrics: bool = False) -> None:
metrics = self.provider.serialize()
if not metrics and raise_on_empty_metrics:
warnings.warn(
"No application metrics to publish. The cold-start metric may be published if enabled. "
"If application metrics should never be empty, consider using 'raise_on_empty_metrics'",
stacklevel=2,
)
self.provider.flush(metrics)
self.provider.clear()
metrics = DataDogMetrics(provider=DataDogProvider())
@metrics.log_metrics
def lambda_handler(event: dict, context: LambdaContext):
metrics.add_metric(name="SuccessfulBooking", value=1, tags={"product":"ticket","ordier":"online"})
# call functions in provider
metrics.provider.add_tag({"test":True})
# JSON output to log
# {
# "m": "SuccessfulBooking",
# "v": 1,
# "e": 1678509106,
# "t": "['product:ticket', 'order:online']"
# }
Out of scope
Introduction of third party observability provider dependency to submit metrics to API is out of scope. The configuration included in this project should only support modifying the metrics outputted to log. But on their end, customer can still submit to API if they implement observability provider SDK in metrics' flush function
Potential challenges
How to provide more value to customer with current Metrics provider design
- Currently we just provided
log_metrics
decorator and_add_cold_start_metric
function in the base metrics provider function. - Other than these two are just basic framework of metrics class.
- What else should we include in the metrics class to provide more value to our customer?
Dependencies and Integrations
Dependencies
No additional dependencies required
Changes and Additions to Public Interfaces
- Modification in
Metrics
,MetricsManager
Class- Add an optional
provider
parameter in metrics class. - Refactor the Metrics Class to enable use of provider. But all currently exist function will have same output.
- Provide a AWS CloudWatch EMF Metrics provider as default metrics provider.
- Add an optional
Performance Impact
Little or no performance impact expected
Backwards Compatibility and Upgrade Path
Full backward compatibility, old metrics codes without provider
parameter will get exact same output as before.
Alternative solutions
Acknowledgment
- This feature request meets Lambda Powertools Tenets
- Should this be considered in other Lambda Powertools languages? i.e. Java, TypeScript
Metadata
Metadata
Assignees
Type
Projects
Status