Skip to content

improv: Better namespace/dimension handling for Metrics #62

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

Merged
merged 9 commits into from
Jun 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions aws_lambda_powertools/metrics/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class MetricManager:

Environment variables
---------------------
POWERTOOLS_SERVICE_NAME : str
POWERTOOLS_METRICS_NAMESPACE : str
metric namespace to be set for all metrics

Raises
Expand All @@ -53,7 +53,7 @@ class MetricManager:
def __init__(self, metric_set: Dict[str, str] = None, dimension_set: Dict = None, namespace: str = None):
self.metric_set = metric_set if metric_set is not None else {}
self.dimension_set = dimension_set if dimension_set is not None else {}
self.namespace = namespace or os.getenv("POWERTOOLS_SERVICE_NAME")
self.namespace = namespace or os.getenv("POWERTOOLS_METRICS_NAMESPACE")
self._metric_units = [unit.value for unit in MetricUnit]
self._metric_unit_options = list(MetricUnit.__members__)

Expand Down
16 changes: 8 additions & 8 deletions aws_lambda_powertools/metrics/metric.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class SingleMetric(MetricManager):

Environment variables
---------------------
POWERTOOLS_SERVICE_NAME : str
POWERTOOLS_METRICS_NAMESPACE : str
metric namespace

Example
Expand All @@ -30,7 +30,7 @@ class SingleMetric(MetricManager):

from aws_lambda_powertools.metrics import SingleMetric, MetricUnit
import json
metric = Single_Metric(service="ServerlessAirline")
metric = Single_Metric(namespace="ServerlessAirline")

metric.add_metric(name="ColdStart", unit=MetricUnit.Count, value=1)
metric.add_dimension(name="function_version", value=47)
Expand Down Expand Up @@ -62,7 +62,7 @@ def add_metric(self, name: str, unit: MetricUnit, value: float):


@contextmanager
def single_metric(name: str, unit: MetricUnit, value: float, service: str = None):
def single_metric(name: str, unit: MetricUnit, value: float, namespace: str = None):
"""Context manager to simplify creation of a single metric

Example
Expand All @@ -71,12 +71,12 @@ def single_metric(name: str, unit: MetricUnit, value: float, service: str = None

from aws_lambda_powertools.metrics import single_metric, MetricUnit

with single_metric(name="ColdStart", unit=MetricUnit.Count, value=1, service="ServerlessAirline") as metric:
with single_metric(name="ColdStart", unit=MetricUnit.Count, value=1, namespace="ServerlessAirline") as metric:
metric.add_dimension(name="function_version", value=47)

**Same as above but set namespace using environment variable**

$ export POWERTOOLS_SERVICE_NAME="ServerlessAirline"
$ export POWERTOOLS_METRICS_NAMESPACE="ServerlessAirline"

from aws_lambda_powertools.metrics import single_metric, MetricUnit

Expand All @@ -91,8 +91,8 @@ def single_metric(name: str, unit: MetricUnit, value: float, service: str = None
`aws_lambda_powertools.helper.models.MetricUnit`
value : float
Metric value
service: str
Service name used as namespace
namespace: str
Namespace for metrics

Yields
-------
Expand All @@ -106,7 +106,7 @@ def single_metric(name: str, unit: MetricUnit, value: float, service: str = None
"""
metric_set = None
try:
metric: SingleMetric = SingleMetric(namespace=service)
metric: SingleMetric = SingleMetric(namespace=namespace)
metric.add_metric(name=name, unit=unit, value=value)
yield metric
logger.debug("Serializing single metric")
Expand Down
19 changes: 11 additions & 8 deletions aws_lambda_powertools/metrics/metrics.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import functools
import json
import logging
import os
from typing import Any, Callable

from aws_lambda_powertools.metrics.base import MetricManager
Expand Down Expand Up @@ -29,10 +30,9 @@ class Metrics(MetricManager):

from aws_lambda_powertools.metrics import Metrics

metrics = Metrics(service="ServerlessAirline")
metrics = Metrics(namespace="ServerlessAirline", service="payment")
metrics.add_metric(name="ColdStart", unit=MetricUnit.Count, value=1)
metrics.add_metric(name="BookingConfirmation", unit="Count", value=1)
metrics.add_dimension(name="service", value="booking")
metrics.add_dimension(name="function_version", value="$LATEST")
...

Expand All @@ -47,8 +47,10 @@ def do_something():

Environment variables
---------------------
POWERTOOLS_SERVICE_NAME : str
POWERTOOLS_METRICS_NAMESPACE : str
metric namespace
POWERTOOLS_SERVICE_NAME : str
service name used for default dimension

Parameters
----------
Expand All @@ -64,13 +66,14 @@ def do_something():
_metrics = {}
_dimensions = {}

def __init__(
self, service: str = None,
):
def __init__(self, service: str = None, namespace: str = None):
self.metric_set = self._metrics
self.dimension_set = self._dimensions
self.service = service
super().__init__(metric_set=self.metric_set, dimension_set=self.dimension_set, namespace=self.service)
self.service = service or os.environ.get("POWERTOOLS_SERVICE_NAME")
self.namespace = namespace
if self.service:
self.dimension_set["service"] = self.service
super().__init__(metric_set=self.metric_set, dimension_set=self.dimension_set, namespace=self.namespace)

def clear_metrics(self):
logger.debug("Clearing out existing metric set from memory")
Expand Down
37 changes: 19 additions & 18 deletions docs/content/core/metrics.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Metrics creates custom metrics asynchronously via logging metrics to standard ou

## Initialization

Set `POWERTOOLS_SERVICE_NAME` env var as a start - Here is an example using AWS Serverless Application Model (SAM)
Set `POWERTOOLS_SERVICE_NAME` and `POWERTOOLS_METRICS_NAMESPACE` env vars as a start - Here is an example using AWS Serverless Application Model (SAM)

```yaml:title=template.yaml
Resources:
Expand All @@ -27,37 +27,39 @@ Resources:
Runtime: python3.8
Environment:
Variables:
POWERTOOLS_SERVICE_NAME: ServerlessAirline # highlight-line
POWERTOOLS_SERVICE_NAME: payment # highlight-line
POWERTOOLS_METRICS_NAMESPACE: ServerlessAirline # highlight-line
```

We recommend you use your application or main service as a metric namespace.
You can explicitly set a namespace name via `service` param or via `POWERTOOLS_SERVICE_NAME` env var. This sets **namespace** key that will be used for all metrics.
You can explicitly set a namespace name via `namespace` param or via `POWERTOOLS_METRICS_NAMESPACE` env var. This sets **namespace** key that will be used for all metrics.
You can also pass a service name via `service` param or `POWERTOOLS_SERVICE_NAME` env var. This will create a dimension with the service name.

```python:title=app.py
from aws_lambda_powertools.metrics import Metrics, MetricUnit

# POWERTOOLS_SERVICE_NAME defined
# POWERTOOLS_METRICS_NAMESPACE and POWERTOOLS_SERVICE_NAME defined
metrics = Metrics() # highlight-line

# Explicit definition
Metrics(service="ServerlessAirline") # sets namespace to "ServerlessAirline"
Metrics(namespace="ServerlessAirline", service="orders") # creates a default dimension {"service": "orders"} under the namespace "ServerlessAirline"


```

You can initialize Metrics anywhere in your code as many time as you need - It'll keep track of your aggregate metrics in memory.
You can initialize Metrics anywhere in your code as many times as you need - It'll keep track of your aggregate metrics in memory.

## Creating metrics

You can create metrics using `add_metric`, and set dimensions for all your aggregate metrics using `add_dimension`.
You can create metrics using `add_metric`, and manually create dimensions for all your aggregate metrics using `add_dimension`.

```python:title=app.py
from aws_lambda_powertools.metrics import Metrics, MetricUnit

metrics = Metrics(service="ExampleService")
metrics = Metrics(namespace="ExampleApplication", service="booking")
# highlight-start
metrics.add_metric(name="ColdStart", unit=MetricUnit.Count, value=1)
metrics.add_dimension(name="service", value="booking")
metrics.add_dimension(name="environment", value="prod")
metrics.add_metric(name="SuccessfulBooking", unit=MetricUnit.Count, value=1)
# highlight-end
```

Expand All @@ -79,7 +81,7 @@ CloudWatch EMF uses the same dimensions across all your metrics. Use `single_met
```python:title=single_metric.py
from aws_lambda_powertools.metrics import MetricUnit, single_metric

with single_metric(name="ColdStart", unit=MetricUnit.Count, value=1, service="ExampleService") as metric: # highlight-line
with single_metric(name="ColdStart", unit=MetricUnit.Count, value=1, namespace="ExampleApplication") as metric: # highlight-line
metric.add_dimension(name="function_context", value="$LATEST")
...
```
Expand Down Expand Up @@ -115,15 +117,14 @@ def lambda_handler(evt, ctx):
```python:title=lambda_handler_nested_middlewares.py
from aws_lambda_powertools.metrics import Metrics, MetricUnit

metrics = Metrics(service="ExampleService")
metrics = Metrics(namespace="ExampleApplication", service="booking")
metrics.add_metric(name="ColdStart", unit="Count", value=1)

# highlight-start
@metrics.log_metrics
@tracer.capture_lambda_handler
# highlight-end
def lambda_handler(evt, ctx):
metrics.add_dimension(name="service", value="booking")
metrics.add_metric(name="BookingConfirmation", unit="Count", value=1)
...
```
Expand All @@ -136,9 +137,8 @@ If you prefer not to use `log_metrics` because you might want to encapsulate add
import json
from aws_lambda_powertools.metrics import Metrics, MetricUnit

metrics = Metrics(service="ExampleService")
metrics = Metrics(namespace="ExampleApplication", service="booking")
metrics.add_metric(name="ColdStart", unit="Count", value=1)
metrics.add_dimension(name="service", value="booking")

# highlight-start
your_metrics_object = metrics.serialize_metric_set()
Expand All @@ -149,10 +149,11 @@ print(json.dumps(your_metrics_object))

## Testing your code

Use `POWERTOOLS_SERVICE_NAME` env var when unit testing your code to ensure a metric namespace object is created, and your code doesn't fail validation.
Use `POWERTOOLS_METRICS_NAMESPACE` and `POWERTOOLS_SERVICE_NAME` env vars when unit testing your code to ensure metric namespace and dimension objects are created, and your code doesn't fail validation.

```bash:title=pytest_metric_namespace.sh
POWERTOOLS_SERVICE_NAME="Example" python -m pytest

POWERTOOLS_SERVICE_NAME="Example" POWERTOOLS_METRICS_NAMESPACE="Application" python -m pytest
```

You can ignore this if you are explicitly setting namespace by passing a service name when initializing Metrics: `metrics = Metrics(service=ServiceName)`.
You can ignore this if you are explicitly setting namespace/default dimension by passing the `namespace` and `service` parameters when initializing Metrics: `metrics = Metrics(namespace=ApplicationName, service=ServiceName)`.
3 changes: 2 additions & 1 deletion docs/content/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ _`*` Core utilities are Tracer, Logger and Metrics. Optional utilities may vary

Environment variable | Description | Utility
------------------------------------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | -------------------------------------------------
**POWERTOOLS_SERVICE_NAME** | Sets service name used for tracing namespace, metrics namespace and structured logging | all
**POWERTOOLS_SERVICE_NAME** | Sets service name used for tracing namespace, metrics dimension and structured logging | all
**POWERTOOLS_METRICS_NAMESPACE** | Sets namespace used for metrics | [Metrics](./core/metrics)
**POWERTOOLS_TRACE_DISABLED** | Disables tracing | [Tracing](./core/tracer)
**POWERTOOLS_TRACE_MIDDLEWARES** | Creates sub-segment for each custom middleware | [middleware_factory](./utilities/middleware_factory)
**POWERTOOLS_LOGGER_LOG_EVENT** | Logs incoming event | [Logging](./core/logger)
Expand Down
6 changes: 3 additions & 3 deletions example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ This example uses both [tracing](https://github.com/awslabs/aws-lambda-powertool
* **Deploy**: `sam deploy --guided`
* **Unit Tests**: We recommend proceeding with the following commands in a virtual environment
- **Install deps**: `pip install -r hello_world/requirements.txt && pip install -r requirements-dev.txt`
- **Run tests with tracing disabled and namespace set**
- `POWERTOOLS_SERVICE_NAME="Example" POWERTOOLS_TRACE_DISABLED=1 python -m pytest`
- Both are necessary because `app.py` initializes them in the global scope, since both Tracer and Metrics will be initialized and configured during import time. For unit tests, we could always patch and explicitly config but env vars do just fine for this example.
- **Run tests with namespace and service set, and tracing disabled**
- `POWERTOOLS_METRICS_NAMESPACE="Example" POWERTOOLS_SERVICE_NAME="Example" POWERTOOLS_TRACE_DISABLED=1 python -m pytest`
- These are necessary because `app.py` initializes them in the global scope, since both Tracer and Metrics will be initialized and configured during import time. For unit tests, we could always patch and explicitly config but env vars do just fine for this example.

# Example code

Expand Down
1 change: 1 addition & 0 deletions example/template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Resources:
POWERTOOLS_TRACE_DISABLED: "false" # Explicitly disables tracing, default
POWERTOOLS_LOGGER_LOG_EVENT: "false" # Logs incoming event, default
POWERTOOLS_LOGGER_SAMPLE_RATE: "0" # Debug log sampling percentage, default
POWERTOOLS_METRICS_NAMESPACE: "Example" # Metric Namespace
LOG_LEVEL: INFO # Log level for Logger (INFO, DEBUG, etc.), default
Events:
HelloWorld:
Expand Down
Loading