Skip to content

Commit

Permalink
Merge branch 'develop' into fix/metrics-cold-start-split
Browse files Browse the repository at this point in the history
* develop:
  chore: version bump to 1.3.1
  fix(capture_method): should yield inside with (#124)
  chore: bump version to 1.3.0 (#122)
  chore(deps): bump prismjs from 1.20.0 to 1.21.0 in /docs
  chore(deps): bump elliptic from 6.5.2 to 6.5.3 in /docs
  feat: add parameter utility (#96)
  chore: bump version to 1.2.0 (#119)
  feat: add support for tracing of generators using capture_method decorator (#113)
  • Loading branch information
heitorlessa committed Aug 22, 2020
2 parents e6e5b85 + 330c76a commit f51ba7c
Show file tree
Hide file tree
Showing 21 changed files with 3,010 additions and 46 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- **Metrics**: Cold start metric is now completely separate from application metrics dimensions, making it easier and cheaper to visualize

## [1.3.1] - 2020-08-22
### Fixed
- **Tracer**: capture_method decorator did not properly handle nested context managers

## [1.3.0] - 2020-08-21
### Added
- **Utilities**: Add new `parameters` utility to retrieve a single or multiple parameters from SSM Parameter Store, Secrets Manager, DynamoDB, or your very own

## [1.2.0] - 2020-08-20
### Added
- **Tracer**: capture_method decorator now supports generator functions (including context managers)

## [1.1.3] - 2020-08-18
### Fixed
- **Logger**: Logs emitted twice, structured and unstructured, due to Lambda configuring the root handler
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ A suite of utilities for AWS Lambda Functions that makes tracing with AWS X-Ray,
* **[Logging](https://awslabs.github.io/aws-lambda-powertools-python/core/logger/)** - Structured logging made easier, and decorator to enrich structured logging with key Lambda context details
* **[Metrics](https://awslabs.github.io/aws-lambda-powertools-python/core/metrics/)** - Custom Metrics created asynchronously via CloudWatch Embedded Metric Format (EMF)
* **[Bring your own middleware](https://awslabs.github.io/aws-lambda-powertools-python/utilities/middleware_factory/)** - Decorator factory to create your own middleware to run logic before, and after each Lambda invocation
* **[Parameters utility](https://awslabs.github.io/aws-lambda-powertools-python/utilities/parameters/)** - Retrieve and cache parameter values from Parameter Store, Secrets Manager, or DynamoDB

### Installation

Expand Down
147 changes: 115 additions & 32 deletions aws_lambda_powertools/tracing/tracer.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import contextlib
import copy
import functools
import inspect
Expand Down Expand Up @@ -320,6 +321,39 @@ def lambda_handler(event: dict, context: Any) -> Dict:
booking_id = event.get("booking_id")
asyncio.run(confirm_booking(booking_id=booking_id))
**Custom generator function using capture_method decorator**
from aws_lambda_powertools import Tracer
tracer = Tracer(service="booking")
@tracer.capture_method
def bookings_generator(booking_id):
resp = call_to_booking_service()
yield resp[0]
yield resp[1]
def lambda_handler(event: dict, context: Any) -> Dict:
gen = bookings_generator(booking_id=booking_id)
result = list(gen)
**Custom generator context manager using capture_method decorator**
from aws_lambda_powertools import Tracer
tracer = Tracer(service="booking")
@tracer.capture_method
@contextlib.contextmanager
def booking_actions(booking_id):
resp = call_to_booking_service()
yield "example result"
cleanup_stuff()
def lambda_handler(event: dict, context: Any) -> Dict:
booking_id = event.get("booking_id")
with booking_actions(booking_id=booking_id) as booking:
result = booking
**Tracing nested async calls**
from aws_lambda_powertools import Tracer
Expand Down Expand Up @@ -392,43 +426,92 @@ async def async_tasks():
err
Exception raised by method
"""
method_name = f"{method.__name__}"

if inspect.iscoroutinefunction(method):
decorate = self._decorate_async_function(method=method)
elif inspect.isgeneratorfunction(method):
decorate = self._decorate_generator_function(method=method)
elif hasattr(method, "__wrapped__") and inspect.isgeneratorfunction(method.__wrapped__):
decorate = self._decorate_generator_function_with_context_manager(method=method)
else:
decorate = self._decorate_sync_function(method=method)

@functools.wraps(method)
async def decorate(*args, **kwargs):
async with self.provider.in_subsegment_async(name=f"## {method_name}") as subsegment:
try:
logger.debug(f"Calling method: {method_name}")
response = await method(*args, **kwargs)
self._add_response_as_metadata(function_name=method_name, data=response, subsegment=subsegment)
except Exception as err:
logger.exception(f"Exception received from '{method_name}' method")
self._add_full_exception_as_metadata(
function_name=method_name, error=err, subsegment=subsegment
)
raise

return response
return decorate

else:
def _decorate_async_function(self, method: Callable = None):
method_name = f"{method.__name__}"

@functools.wraps(method)
async def decorate(*args, **kwargs):
async with self.provider.in_subsegment_async(name=f"## {method_name}") as subsegment:
try:
logger.debug(f"Calling method: {method_name}")
response = await method(*args, **kwargs)
self._add_response_as_metadata(function_name=method_name, data=response, subsegment=subsegment)
except Exception as err:
logger.exception(f"Exception received from '{method_name}' method")
self._add_full_exception_as_metadata(function_name=method_name, error=err, subsegment=subsegment)
raise

@functools.wraps(method)
def decorate(*args, **kwargs):
with self.provider.in_subsegment(name=f"## {method_name}") as subsegment:
try:
logger.debug(f"Calling method: {method_name}")
response = method(*args, **kwargs)
self._add_response_as_metadata(function_name=method_name, data=response, subsegment=subsegment)
except Exception as err:
logger.exception(f"Exception received from '{method_name}' method")
self._add_full_exception_as_metadata(
function_name=method_name, error=err, subsegment=subsegment
)
raise

return response
return response

return decorate

def _decorate_generator_function(self, method: Callable = None):
method_name = f"{method.__name__}"

@functools.wraps(method)
def decorate(*args, **kwargs):
with self.provider.in_subsegment(name=f"## {method_name}") as subsegment:
try:
logger.debug(f"Calling method: {method_name}")
result = yield from method(*args, **kwargs)
self._add_response_as_metadata(function_name=method_name, data=result, subsegment=subsegment)
except Exception as err:
logger.exception(f"Exception received from '{method_name}' method")
self._add_full_exception_as_metadata(function_name=method_name, error=err, subsegment=subsegment)
raise

return result

return decorate

def _decorate_generator_function_with_context_manager(self, method: Callable = None):
method_name = f"{method.__name__}"

@functools.wraps(method)
@contextlib.contextmanager
def decorate(*args, **kwargs):
with self.provider.in_subsegment(name=f"## {method_name}") as subsegment:
try:
logger.debug(f"Calling method: {method_name}")
with method(*args, **kwargs) as return_val:
result = return_val
yield result
self._add_response_as_metadata(function_name=method_name, data=result, subsegment=subsegment)
except Exception as err:
logger.exception(f"Exception received from '{method_name}' method")
self._add_full_exception_as_metadata(function_name=method_name, error=err, subsegment=subsegment)
raise

return decorate

def _decorate_sync_function(self, method: Callable = None):
method_name = f"{method.__name__}"

@functools.wraps(method)
def decorate(*args, **kwargs):
with self.provider.in_subsegment(name=f"## {method_name}") as subsegment:
try:
logger.debug(f"Calling method: {method_name}")
response = method(*args, **kwargs)
self._add_response_as_metadata(function_name=method_name, data=response, subsegment=subsegment)
except Exception as err:
logger.exception(f"Exception received from '{method_name}' method")
self._add_full_exception_as_metadata(function_name=method_name, error=err, subsegment=subsegment)
raise

return response

return decorate

Expand Down
3 changes: 3 additions & 0 deletions aws_lambda_powertools/utilities/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-

"""General utilities for Powertools"""
23 changes: 23 additions & 0 deletions aws_lambda_powertools/utilities/parameters/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-

"""
Parameter retrieval and caching utility
"""

from .base import BaseProvider
from .dynamodb import DynamoDBProvider
from .exceptions import GetParameterError, TransformParameterError
from .secrets import SecretsProvider, get_secret
from .ssm import SSMProvider, get_parameter, get_parameters

__all__ = [
"BaseProvider",
"GetParameterError",
"DynamoDBProvider",
"SecretsProvider",
"SSMProvider",
"TransformParameterError",
"get_parameter",
"get_parameters",
"get_secret",
]
Loading

0 comments on commit f51ba7c

Please sign in to comment.