Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(metrics): support to bring your own metrics provider #2194

Merged
merged 54 commits into from
Aug 1, 2023

Conversation

roger-zhangg
Copy link
Member

@roger-zhangg roger-zhangg commented May 2, 2023

Issue number: #2015

Summary

Changes

This PR refactors Metrics class to use Metrics Provider. Enabling customer to bring their own provider for outputting custom metrics format.The default CloudWatch EMF logic is refactored into a EMF provider.

User experience

  • For Customer using the default EMF metric format, this PR won't change any UX
  • For Customer willing to implement custom metrics format in Powertools Metrics
    • Before:
      • The Metrics class only supports EMF format.
    • After:
      • User can bring their own metrics provider to support custom metrics format

Metric provider UX

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.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(capture_cold_start_metric=True, raise_on_empty_metrics=False)
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 UX

If the customer would like to use another observability provider, or define their own metrics functions, the provider acts as an interface that the customer can implement and pass to the Metrics class using the 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(capture_cold_start_metric: bool = True, raise_on_empty_metrics: bool = False)
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']"
# }

Tasks

  • Create documentation
  • Check the change that can generate breaking change
  • Use the simplified version of typing from Python 3.10
  • Create an external provider like DataDog to validate our logic

Not covered in this PR (Added - 12/07/2023)

  • Method to create standard operational metrics

Checklist

If your change doesn't seem to apply, please leave them unchecked.

Is this a breaking change?

RFC issue number:

Checklist:

  • Migration process documented
  • Implement warnings (if it can live side by side)

Acknowledgment

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

Disclaimer: We value your time and bandwidth. As such, any pull requests created on non-triaged issues might not be successful.

@roger-zhangg roger-zhangg requested a review from a team as a code owner May 2, 2023 21:38
@roger-zhangg roger-zhangg requested review from leandrodamascena and removed request for a team May 2, 2023 21:38
@boring-cyborg
Copy link

boring-cyborg bot commented May 2, 2023

Thanks a lot for your first contribution! Please check out our contributing guidelines and don't hesitate to ask whatever you need.
In the meantime, check out the #python channel on our AWS Lambda Powertools Discord: Invite link

@pull-request-size pull-request-size bot added the size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. label May 2, 2023
@github-actions github-actions bot added the feature New feature or functionality label May 2, 2023
@codecov-commenter
Copy link

codecov-commenter commented May 2, 2023

Codecov Report

Patch coverage: 93.96% and project coverage change: -1.14% ⚠️

Comparison is base (0916bc5) 97.57% compared to head (e8cfc81) 96.43%.

❗ Your organization is not using the GitHub App Integration. As a result you may experience degraded service beginning May 15th. Please install the Github App Integration for your organization. Read more.

Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #2194      +/-   ##
===========================================
- Coverage    97.57%   96.43%   -1.14%     
===========================================
  Files          162      169       +7     
  Lines         7475     7666     +191     
  Branches      1416     1447      +31     
===========================================
+ Hits          7294     7393      +99     
- Misses         133      217      +84     
- Partials        48       56       +8     
Files Changed Coverage Δ
aws_lambda_powertools/metrics/metric.py 0.00% <0.00%> (-100.00%) ⬇️
aws_lambda_powertools/metrics/metrics.py 93.75% <90.32%> (-3.75%) ⬇️
...ools/metrics/provider/cloudwatch_emf/cloudwatch.py 91.85% <91.85%> (ø)
aws_lambda_powertools/metrics/provider/base.py 97.29% <97.29%> (ø)
aws_lambda_powertools/metrics/__init__.py 100.00% <100.00%> (ø)
aws_lambda_powertools/metrics/base.py 71.23% <100.00%> (-28.77%) ⬇️
aws_lambda_powertools/metrics/exceptions.py 100.00% <100.00%> (ø)
aws_lambda_powertools/metrics/provider/__init__.py 100.00% <100.00%> (ø)
...ools/metrics/provider/cloudwatch_emf/cold_start.py 100.00% <100.00%> (ø)
...tools/metrics/provider/cloudwatch_emf/constants.py 100.00% <100.00%> (ø)
... and 2 more

... and 1 file with indirect coverage changes

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Contributor

@leandrodamascena leandrodamascena left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @roger-zhangg, excellent work this PR! This is a feature that we really want to have because it is a need for some customers. I made some initial considerations that can help us to make the code simpler and optimize some things.
I still have more to suggest, but I need to look at the code in more detail.

I'll leave some notes to remember what we still have to do to get this ready:

  • Create documentation
  • Check the change that can generate breaking change
  • Use the simplified version of typing from Python 3.10

Thank you very much for this PR, let's work together to merge as soon as possible.

aws_lambda_powertools/metrics/__init__.py Outdated Show resolved Hide resolved
aws_lambda_powertools/metrics/base.py Outdated Show resolved Hide resolved
aws_lambda_powertools/metrics/base.py Outdated Show resolved Hide resolved
aws_lambda_powertools/metrics/provider.py Outdated Show resolved Hide resolved
aws_lambda_powertools/metrics/types.py Outdated Show resolved Hide resolved
tests/functional/test_metrics.py Outdated Show resolved Hide resolved
@boring-cyborg boring-cyborg bot added dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation github-actions Pull requests that update Github_actions code logger labels Jun 5, 2023
@pull-request-size pull-request-size bot added size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. and removed size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. labels Jun 5, 2023
@github-actions
Copy link
Contributor

github-actions bot commented Jun 5, 2023

⚠️Large PR detected⚠️

Please consider breaking into smaller PRs to avoid significant review delays. Ignore if this PR has naturally grown to this size after reviews.

4 similar comments
@github-actions
Copy link
Contributor

github-actions bot commented Jun 5, 2023

⚠️Large PR detected⚠️

Please consider breaking into smaller PRs to avoid significant review delays. Ignore if this PR has naturally grown to this size after reviews.

@github-actions
Copy link
Contributor

github-actions bot commented Jun 5, 2023

⚠️Large PR detected⚠️

Please consider breaking into smaller PRs to avoid significant review delays. Ignore if this PR has naturally grown to this size after reviews.

@github-actions
Copy link
Contributor

github-actions bot commented Jun 5, 2023

⚠️Large PR detected⚠️

Please consider breaking into smaller PRs to avoid significant review delays. Ignore if this PR has naturally grown to this size after reviews.

@github-actions
Copy link
Contributor

github-actions bot commented Jun 5, 2023

⚠️Large PR detected⚠️

Please consider breaking into smaller PRs to avoid significant review delays. Ignore if this PR has naturally grown to this size after reviews.

@pull-request-size pull-request-size bot added size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. and removed size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. labels Jul 27, 2023
@leandrodamascena
Copy link
Contributor

leandrodamascena commented Jul 28, 2023

Hello @heitorlessa! About the documentation, do you think it makes sense to add it now OR when we add the Datadog provider? In my opinion, we should add Datadog and create the documentation.

Can you make a final review, please?

@leandrodamascena
Copy link
Contributor

@roger-zhangg I would like your review here also.

Copy link
Member Author

@roger-zhangg roger-zhangg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Contributor

@heitorlessa heitorlessa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metrics Provider PR notes

  • Remove docs until we cleanup implementations and have our first provider (Datadog)
  • Check whether we have a gap in tests for default dimensions not being updated locally but within a provider

Separate PR for cleanup

  • Export CloudWatch EMF stuff within cloudwatch_emf
  • MetricsBase and MetricsProviderBase confusion
  • Refactor _extract_* into standalone functions to reduce call graph
  • Split up functional and unit tests with new providers

aws_lambda_powertools/metrics/metrics.py Show resolved Hide resolved
aws_lambda_powertools/metrics/provider/base.py Outdated Show resolved Hide resolved
aws_lambda_powertools/metrics/provider/base.py Outdated Show resolved Hide resolved
@heitorlessa
Copy link
Contributor

peer review last comments so we can merge this today. We can cleanup the slight confusion in OOO / Protocol, docs, etc. in a separate PR (easier to review later).

CAN'T WAIT FOR THIS!

@leandrodamascena
Copy link
Contributor

peer review last comments so we can merge this today. We can cleanup the slight confusion in OOO / Protocol, docs, etc. in a separate PR (easier to review later).

CAN'T WAIT FOR THIS!

I removed the docs for now and I'll create an additional issue to pay this tech debt.

ALMOST THERE! @roger-zhangg!

@sonarqubecloud
Copy link

sonarqubecloud bot commented Aug 1, 2023

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 0 Code Smells

No Coverage information No Coverage information
No Duplication information No Duplication information

@heitorlessa
Copy link
Contributor

pending CI checks, feel free to merge ;) Gotta go cook dinner. Thank you both @roger-zhangg and @leandrodamascena - this marks the initial steps of supporting any observability provider 🍺 🎉

@heitorlessa heitorlessa changed the title feat(metrics): support metrics provider feat(metrics): support to bring your own metrics provider Aug 1, 2023
@leandrodamascena leandrodamascena merged commit cdf9084 into aws-powertools:develop Aug 1, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
commons dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation feature New feature or functionality github-actions Pull requests that update Github_actions code github-templates internal Maintenance changes logger metrics size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. tests
Projects
None yet
4 participants