Skip to content

Commit

Permalink
Merge branch 'develop' into feat/precommit-hooks
Browse files Browse the repository at this point in the history
* develop:
  improv: include example tests in `make tests` (#63)
  chore: rename Makefile target docs-dev to docs-local (#65)
  improv: better namespace/dimension handling for Metrics (#62)
  • Loading branch information
heitorlessa committed Jun 7, 2020
2 parents dee4fab + 993e8e5 commit 11463fe
Show file tree
Hide file tree
Showing 14 changed files with 187 additions and 79 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ build-docs-website: dev-docs
cd docs && npm run build
cp -R docs/public/* dist/

docs-dev:
docs-local:
cd docs && npm run start

docs-api-dev:
docs-api-local:
poetry run pdoc --http : aws_lambda_powertools

security-baseline:
Expand Down
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
39 changes: 20 additions & 19 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
metrics = Metrics() # highlight-line
# 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
10 changes: 5 additions & 5 deletions example/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Summary

This example uses both [tracing](https://github.com/awslabs/aws-lambda-powertools/tree/develop/python#tracing) and [logging](https://github.com/awslabs/aws-lambda-powertools/tree/develop/python#logging) features, includes all environment variables that can be used, and demonstrates how to explicitly disable tracing while running unit tests - That is not necessary when running within SAM CLI as it detects the local env automatically.
This example uses [tracer](https://awslabs.github.io/aws-lambda-powertools-python/core/tracer/), [metrics](https://awslabs.github.io/aws-lambda-powertools-python/core/metrics/),and [logger](https://awslabs.github.io/aws-lambda-powertools-python/core/logger/) features, includes all environment variables that can be used, and demonstrates how to explicitly disable tracing while running unit tests - That is not necessary when running within SAM CLI as it detects the local env automatically.

**Quick commands**

Expand All @@ -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 Expand Up @@ -118,7 +118,7 @@ Tests are defined in the `tests` folder in this project. Use PIP to install the
```bash
example$ pip install -r hello_world/requirements.txt
example$ pip install -r requirements-dev.txt
example$ POWERTOOLS_TRACE_DISABLED=1 python -m pytest tests/ -v
example$ pytest -v
```

## Cleanup
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
Empty file added example/tests/__init__.py
Empty file.
22 changes: 19 additions & 3 deletions example/tests/test_handler.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
import json
import os
import sys
from dataclasses import dataclass

import pytest

from hello_world import app

@pytest.fixture()
def env_vars(monkeypatch):
monkeypatch.setenv("POWERTOOLS_METRICS_NAMESPACE", "example_namespace")
monkeypatch.setenv("POWERTOOLS_SERVICE_NAME", "example_service")
monkeypatch.setenv("POWERTOOLS_TRACE_DISABLED", "1")


@pytest.fixture()
def lambda_handler(env_vars):
from hello_world import app

return app.lambda_handler



@pytest.fixture()
Expand Down Expand Up @@ -71,8 +86,9 @@ class Context:
aws_request_id: str = "5b441b59-a550-11c8-6564-f1c833cf438c"


def test_lambda_handler(apigw_event, mocker, capsys):
ret = app.lambda_handler(apigw_event, Context())
@pytest.mark.skipif(sys.version_info < (3, 7), reason="requires python3.7 or higher")
def test_lambda_handler(lambda_handler, apigw_event, mocker, capsys):
ret = lambda_handler(apigw_event, Context())
data = json.loads(ret["body"])

output = capsys.readouterr()
Expand Down
18 changes: 10 additions & 8 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ radon = "^4.1.0"
xenon = "^0.7.0"
flake8-bugbear = "^20.1.4"
flake8-eradicate = "^0.3.0"
pre-commit = "^2.4.0"
dataclasses = {version = "*", python = "~3.6"}

[tool.coverage.run]
source = ["aws_lambda_powertools"]
Expand Down
2 changes: 1 addition & 1 deletion pytest.ini
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[pytest]
addopts = -ra --cov --cov-config=.coveragerc
testpaths = ./tests
testpaths = ./tests ./example/tests
Loading

0 comments on commit 11463fe

Please sign in to comment.