Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…tools-python into develop

* 'develop' of https://github.com/awslabs/aws-lambda-powertools-python:
  feat(logging): Include exception_name (#320)
  chore: remove gatsby mention as migrated completed
  refactor(parameters): Consistently reference env (#319)
  docs(metrics): remove minimum dimensions
  docs: Correct code examples (#317)
  docs(metrics): Correct code examples in markdown (#316)
  fix(idempotency): TypeError when calling is_missing_idempotency_key with an int (#315)
  docs(metrics): Corrections to the code examples (#314)
  fix(idempotency): Correctly handle save_inprogress errors (#313)
  • Loading branch information
heitorlessa committed Mar 9, 2021
2 parents 25facef + 0ec47dc commit 755d9bb
Show file tree
Hide file tree
Showing 21 changed files with 144 additions and 53 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ With [pip](https://pip.pypa.io/en/latest/index.html) installed, run: ``pip insta

* Structured logging initial implementation from [aws-lambda-logging](https://gitlab.com/hadrien/aws_lambda_logging)
* Powertools idea [DAZN Powertools](https://github.com/getndazn/dazn-lambda-powertools/)
* [Gatsby Apollo Theme for Docs](https://github.com/apollographql/gatsby-theme-apollo/tree/master/packages/gatsby-theme-apollo-docs)

## License

Expand Down
19 changes: 19 additions & 0 deletions aws_lambda_powertools/logging/formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,24 @@ def _extract_log_exception(self, log_record: logging.LogRecord) -> Optional[str]

return None

def _extract_log_exception_name(self, log_record: logging.LogRecord) -> Optional[str]:
"""Extract the exception name, if available
Parameters
----------
log_record : logging.LogRecord
Log record to extract exception name from
Returns
-------
log_record: Optional[str]
Log record with exception name
"""
if log_record.exc_info:
return log_record.exc_info[0].__name__

return None

def _extract_log_keys(self, log_record: logging.LogRecord) -> Dict:
"""Extract and parse custom and reserved log keys
Expand Down Expand Up @@ -164,6 +182,7 @@ def _extract_log_keys(self, log_record: logging.LogRecord) -> Dict:
def format(self, record): # noqa: A003
formatted_log = self._extract_log_keys(log_record=record)
formatted_log["message"] = self._extract_log_message(log_record=record)
formatted_log["exception_name"] = self._extract_log_exception_name(log_record=record)
formatted_log["exception"] = self._extract_log_exception(log_record=record)
formatted_log.update({"xray_trace_id": self._get_latest_trace_id()}) # fetch latest Trace ID, if any

Expand Down
2 changes: 1 addition & 1 deletion aws_lambda_powertools/logging/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class Logger(logging.Logger): # lgtm [py/missing-call-to-init]
LOG_LEVEL: str
logging level (e.g. INFO, DEBUG)
POWERTOOLS_LOGGER_SAMPLE_RATE: float
samping rate ranging from 0 to 1, 1 being 100% sampling
sampling rate ranging from 0 to 1, 1 being 100% sampling
Parameters
----------
Expand Down
4 changes: 1 addition & 3 deletions aws_lambda_powertools/metrics/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ def serialize_metric_set(self, metrics: Dict = None, dimensions: Dict = None, me
metric_names_and_units.append({"Name": metric_name, "Unit": metric_unit})
metric_names_and_values.update({metric_name: metric_value})

embedded_metrics_object = {
return {
"_aws": {
"Timestamp": int(datetime.datetime.now().timestamp() * 1000), # epoch
"CloudWatchMetrics": [
Expand All @@ -213,8 +213,6 @@ def serialize_metric_set(self, metrics: Dict = None, dimensions: Dict = None, me
**metric_names_and_values, # "single_metric": 1.0
}

return embedded_metrics_object

def add_dimension(self, name: str, value: str):
"""Adds given dimension to all metrics
Expand Down
8 changes: 4 additions & 4 deletions aws_lambda_powertools/metrics/metric.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ class SingleMetric(MetricManager):
-------
**Creates cold start metric with function_version as dimension**
from aws_lambda_powertools.metrics import SingleMetric, MetricUnit
import json
metric = Single_Metric(namespace="ServerlessAirline")
from aws_lambda_powertools.metrics import single_metric, MetricUnit
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 @@ -72,7 +72,7 @@ def single_metric(name: str, unit: MetricUnit, value: float, namespace: str = No
from aws_lambda_powertools.metrics import MetricUnit
with single_metric(name="ColdStart", unit=MetricUnit.Count, value=1, namespace="ServerlessAirline") as metric:
metric.add_dimension(name="function_version", value=47)
metric.add_dimension(name="function_version", value="47")
**Same as above but set namespace using environment variable**
Expand All @@ -82,7 +82,7 @@ def single_metric(name: str, unit: MetricUnit, value: float, namespace: str = No
from aws_lambda_powertools.metrics import MetricUnit
with single_metric(name="ColdStart", unit=MetricUnit.Count, value=1) as metric:
metric.add_dimension(name="function_version", value=47)
metric.add_dimension(name="function_version", value="47")
Parameters
----------
Expand Down
11 changes: 6 additions & 5 deletions aws_lambda_powertools/metrics/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,13 @@ class Metrics(MetricManager):
metrics.add_dimension(name="function_version", value="$LATEST")
...
@tracer.capture_lambda_handler
@metrics.log_metrics()
def lambda_handler():
do_something()
return True
do_something()
return True
def do_something():
metrics.add_metric(name="Something", unit="Count", value=1)
metrics.add_metric(name="Something", unit="Count", value=1)
Environment variables
---------------------
Expand Down Expand Up @@ -111,12 +110,14 @@ def log_metrics(
-------
**Lambda function using tracer and metrics decorators**
from aws_lambda_powertools import Metrics, Tracer
metrics = Metrics(service="payment")
tracer = Tracer(service="payment")
@tracer.capture_lambda_handler
@metrics.log_metrics
def handler(event, context):
def handler(event, context):
...
Parameters
Expand Down
2 changes: 0 additions & 2 deletions aws_lambda_powertools/middleware_factory/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
class MiddlewareInvalidArgumentError(Exception):
"""When middleware receives non keyword=arguments"""

pass
4 changes: 2 additions & 2 deletions aws_lambda_powertools/tracing/tracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from typing import Any, Callable, Dict, List, Optional, Tuple, Union

from ..shared import constants
from ..shared.functions import resolve_truthy_env_var_choice
from ..shared.functions import resolve_env_var_choice, resolve_truthy_env_var_choice
from ..shared.lazy_import import LazyLoader
from .base import BaseProvider, BaseSegment

Expand Down Expand Up @@ -720,7 +720,7 @@ def __build_config(
):
""" Populates Tracer config for new and existing initializations """
is_disabled = disabled if disabled is not None else self._is_tracer_disabled()
is_service = service if service is not None else os.getenv(constants.SERVICE_NAME_ENV)
is_service = resolve_env_var_choice(choice=service, env=os.getenv(constants.SERVICE_NAME_ENV))

self._config["provider"] = provider or self._config["provider"] or aws_xray_sdk.core.xray_recorder
self._config["auto_patch"] = auto_patch if auto_patch is not None else self._config["auto_patch"]
Expand Down
1 change: 1 addition & 0 deletions aws_lambda_powertools/utilities/batch/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ def batch_processor(
Examples
--------
**Processes Lambda's event with PartialSQSProcessor**
>>> from aws_lambda_powertools.utilities.batch import batch_processor, PartialSQSProcessor
>>>
>>> def record_handler(record):
Expand Down
4 changes: 3 additions & 1 deletion aws_lambda_powertools/utilities/batch/sqs.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class PartialSQSProcessor(BasePartialProcessor):
>>> # have been deleted from the queue after context's exit.
>>>
>>> return result
"""

def __init__(self, config: Optional[Config] = None, suppress_exception: bool = False):
Expand Down Expand Up @@ -163,10 +164,11 @@ def sqs_batch_processor(
Examples
--------
**Processes Lambda's event with PartialSQSProcessor**
>>> from aws_lambda_powertools.utilities.batch import sqs_batch_processor
>>>
>>> def record_handler(record):
>>> return record["body"]
>>> return record["body"]
>>>
>>> @sqs_batch_processor(record_handler=record_handler)
>>> def handler(event, context):
Expand Down
3 changes: 3 additions & 0 deletions aws_lambda_powertools/utilities/idempotency/idempotency.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ def idempotent(
Examples
--------
**Processes Lambda's event in an idempotent manner**
>>> from aws_lambda_powertools.utilities.idempotency import (
>>> idempotent, DynamoDBPersistenceLayer, IdempotencyConfig
>>> )
Expand Down Expand Up @@ -135,6 +136,8 @@ def handle(self) -> Any:
# Now we know the item already exists, we can retrieve it
record = self._get_idempotency_record()
return self._handle_for_status(record)
except Exception as exc:
raise IdempotencyPersistenceLayerError("Failed to save in progress record to idempotency store") from exc

return self._call_lambda_handler()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,9 @@ def _get_hashed_idempotency_key(self, lambda_event: Dict[str, Any]) -> str:

@staticmethod
def is_missing_idempotency_key(data) -> bool:
return data is None or not data or all(x is None for x in data)
if type(data).__name__ in ("tuple", "list", "dict"):
return all(x is None for x in data)
return not data

def _get_hashed_payload(self, lambda_event: Dict[str, Any]) -> str:
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,14 @@ def __init__(
Examples
--------
**Create a DynamoDB persistence layer with custom settings**
>>> from aws_lambda_powertools.utilities.idempotency import idempotent, DynamoDBPersistenceLayer
>>> from aws_lambda_powertools.utilities.idempotency import (
>>> idempotent, DynamoDBPersistenceLayer
>>> )
>>>
>>> persistence_store = DynamoDBPersistenceLayer(table_name="idempotency_store")
>>>
>>> @idempotent(persistence_store=persistence_store, event_key="body")
>>> @idempotent(persistence_store=persistence_store)
>>> def handler(event, context):
>>> return {"StatusCode": 200}
"""
Expand Down
7 changes: 6 additions & 1 deletion aws_lambda_powertools/utilities/parameters/appconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import boto3
from botocore.config import Config

from ...shared import constants
from ...shared.functions import resolve_env_var_choice
from .base import DEFAULT_PROVIDERS, BaseProvider

CLIENT_ID = str(uuid4())
Expand All @@ -33,6 +35,7 @@ class AppConfigProvider(BaseProvider):
**Retrieves the latest configuration value from App Config**
>>> from aws_lambda_powertools.utilities import parameters
>>>
>>> appconf_provider = parameters.AppConfigProvider(environment="my_env", application="my_app")
>>>
>>> value : bytes = appconf_provider.get("my_conf")
Expand Down Expand Up @@ -66,7 +69,9 @@ def __init__(

config = config or Config()
self.client = boto3.client("appconfig", config=config)
self.application = application or os.getenv("POWERTOOLS_SERVICE_NAME") or "application_undefined"
self.application = resolve_env_var_choice(
choice=application, env=os.getenv(constants.SERVICE_NAME_ENV, "service_undefined")
)
self.environment = environment
self.current_version = ""

Expand Down
3 changes: 2 additions & 1 deletion docs/core/logger.md
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ You can also change the order of the following log record keys via the `log_reco

#### Logging exceptions

When logging exceptions, Logger will add a new key named `exception`, and will serialize the full traceback as a string.
When logging exceptions, Logger will add new keys named `exception_name` and `exception` with the full traceback as a string.

=== "logging_an_exception.py"

Expand All @@ -475,6 +475,7 @@ When logging exceptions, Logger will add a new key named `exception`, and will s
"timestamp": "2020-08-28 18:11:38,886",
"service": "service_undefined",
"sampling_rate": 0.0,
"exception_name":"ValueError",
"exception": "Traceback (most recent call last):\n File \"<input>\", line 2, in <module>\nValueError: something went wrong"
}
```
Expand Down
12 changes: 6 additions & 6 deletions docs/core/metrics.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ This decorator also **validates**, **serializes**, and **flushes** all your metr

@metrics.log_metrics
def lambda_handler(evt, ctx):
metrics.add_metric(name="BookingConfirmation", unit="Count", value=1)
metrics.add_metric(name="BookingConfirmation", unit=MetricUnit.Count, value=1)
...
```
=== "Example CloudWatch Logs excerpt"
Expand Down Expand Up @@ -148,7 +148,6 @@ This decorator also **validates**, **serializes**, and **flushes** all your metr
!!! tip "Metric validation"
If metrics are provided, and any of the following criteria are not met, **`SchemaValidationError`** exception will be raised:

* Minimum of 1 dimension
* Maximum of 9 dimensions
* Namespace is set, and no more than one
* Metric units must be [supported by CloudWatch](https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_MetricDatum.html)
Expand All @@ -162,6 +161,8 @@ If you want to ensure that at least one metric is emitted, you can pass `raise_o
```python hl_lines="3"
from aws_lambda_powertools.metrics import Metrics

metrics = Metrics()

@metrics.log_metrics(raise_on_empty_metrics=True)
def lambda_handler(evt, ctx):
...
Expand All @@ -183,12 +184,12 @@ When using multiple middlewares, use `log_metrics` as your **last decorator** wr
tracer = Tracer(service="booking")
metrics = Metrics(namespace="ExampleApplication", service="booking")

metrics.add_metric(name="ColdStart", unit="Count", value=1)
metrics.add_metric(name="ColdStart", unit=MetricUnit.Count, value=1)

@metrics.log_metrics
@tracer.capture_lambda_handler
def lambda_handler(evt, ctx):
metrics.add_metric(name="BookingConfirmation", unit="Count", value=1)
metrics.add_metric(name="BookingConfirmation", unit=MetricUnit.Count, value=1)
...
```

Expand All @@ -200,7 +201,6 @@ You can optionally capture cold start metrics with `log_metrics` decorator via `

```python hl_lines="6"
from aws_lambda_powertools import Metrics
from aws_lambda_powertools.metrics import MetricUnit

metrics = Metrics(service="ExampleService")

Expand Down Expand Up @@ -300,7 +300,7 @@ If you prefer not to use `log_metrics` because you might want to encapsulate add
from aws_lambda_powertools.metrics import MetricUnit

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

your_metrics_object = metrics.serialize_metric_set()
metrics.clear_metrics()
Expand Down
Loading

0 comments on commit 755d9bb

Please sign in to comment.