Skip to content

Commit f51ba7c

Browse files
committed
Merge branch 'develop' into fix/metrics-cold-start-split
* 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)
2 parents e6e5b85 + 330c76a commit f51ba7c

File tree

21 files changed

+3010
-46
lines changed

21 files changed

+3010
-46
lines changed

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99
### Fixed
1010
- **Metrics**: Cold start metric is now completely separate from application metrics dimensions, making it easier and cheaper to visualize
1111

12+
## [1.3.1] - 2020-08-22
13+
### Fixed
14+
- **Tracer**: capture_method decorator did not properly handle nested context managers
15+
16+
## [1.3.0] - 2020-08-21
17+
### Added
18+
- **Utilities**: Add new `parameters` utility to retrieve a single or multiple parameters from SSM Parameter Store, Secrets Manager, DynamoDB, or your very own
19+
20+
## [1.2.0] - 2020-08-20
21+
### Added
22+
- **Tracer**: capture_method decorator now supports generator functions (including context managers)
23+
1224
## [1.1.3] - 2020-08-18
1325
### Fixed
1426
- **Logger**: Logs emitted twice, structured and unstructured, due to Lambda configuring the root handler

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ A suite of utilities for AWS Lambda Functions that makes tracing with AWS X-Ray,
1313
* **[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
1414
* **[Metrics](https://awslabs.github.io/aws-lambda-powertools-python/core/metrics/)** - Custom Metrics created asynchronously via CloudWatch Embedded Metric Format (EMF)
1515
* **[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
16+
* **[Parameters utility](https://awslabs.github.io/aws-lambda-powertools-python/utilities/parameters/)** - Retrieve and cache parameter values from Parameter Store, Secrets Manager, or DynamoDB
1617

1718
### Installation
1819

aws_lambda_powertools/tracing/tracer.py

Lines changed: 115 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import contextlib
12
import copy
23
import functools
34
import inspect
@@ -320,6 +321,39 @@ def lambda_handler(event: dict, context: Any) -> Dict:
320321
booking_id = event.get("booking_id")
321322
asyncio.run(confirm_booking(booking_id=booking_id))
322323
324+
**Custom generator function using capture_method decorator**
325+
326+
from aws_lambda_powertools import Tracer
327+
tracer = Tracer(service="booking")
328+
329+
@tracer.capture_method
330+
def bookings_generator(booking_id):
331+
resp = call_to_booking_service()
332+
yield resp[0]
333+
yield resp[1]
334+
335+
def lambda_handler(event: dict, context: Any) -> Dict:
336+
gen = bookings_generator(booking_id=booking_id)
337+
result = list(gen)
338+
339+
**Custom generator context manager using capture_method decorator**
340+
341+
from aws_lambda_powertools import Tracer
342+
tracer = Tracer(service="booking")
343+
344+
@tracer.capture_method
345+
@contextlib.contextmanager
346+
def booking_actions(booking_id):
347+
resp = call_to_booking_service()
348+
yield "example result"
349+
cleanup_stuff()
350+
351+
def lambda_handler(event: dict, context: Any) -> Dict:
352+
booking_id = event.get("booking_id")
353+
354+
with booking_actions(booking_id=booking_id) as booking:
355+
result = booking
356+
323357
**Tracing nested async calls**
324358
325359
from aws_lambda_powertools import Tracer
@@ -392,43 +426,92 @@ async def async_tasks():
392426
err
393427
Exception raised by method
394428
"""
395-
method_name = f"{method.__name__}"
396429

397430
if inspect.iscoroutinefunction(method):
431+
decorate = self._decorate_async_function(method=method)
432+
elif inspect.isgeneratorfunction(method):
433+
decorate = self._decorate_generator_function(method=method)
434+
elif hasattr(method, "__wrapped__") and inspect.isgeneratorfunction(method.__wrapped__):
435+
decorate = self._decorate_generator_function_with_context_manager(method=method)
436+
else:
437+
decorate = self._decorate_sync_function(method=method)
398438

399-
@functools.wraps(method)
400-
async def decorate(*args, **kwargs):
401-
async with self.provider.in_subsegment_async(name=f"## {method_name}") as subsegment:
402-
try:
403-
logger.debug(f"Calling method: {method_name}")
404-
response = await method(*args, **kwargs)
405-
self._add_response_as_metadata(function_name=method_name, data=response, subsegment=subsegment)
406-
except Exception as err:
407-
logger.exception(f"Exception received from '{method_name}' method")
408-
self._add_full_exception_as_metadata(
409-
function_name=method_name, error=err, subsegment=subsegment
410-
)
411-
raise
412-
413-
return response
439+
return decorate
414440

415-
else:
441+
def _decorate_async_function(self, method: Callable = None):
442+
method_name = f"{method.__name__}"
443+
444+
@functools.wraps(method)
445+
async def decorate(*args, **kwargs):
446+
async with self.provider.in_subsegment_async(name=f"## {method_name}") as subsegment:
447+
try:
448+
logger.debug(f"Calling method: {method_name}")
449+
response = await method(*args, **kwargs)
450+
self._add_response_as_metadata(function_name=method_name, data=response, subsegment=subsegment)
451+
except Exception as err:
452+
logger.exception(f"Exception received from '{method_name}' method")
453+
self._add_full_exception_as_metadata(function_name=method_name, error=err, subsegment=subsegment)
454+
raise
416455

417-
@functools.wraps(method)
418-
def decorate(*args, **kwargs):
419-
with self.provider.in_subsegment(name=f"## {method_name}") as subsegment:
420-
try:
421-
logger.debug(f"Calling method: {method_name}")
422-
response = method(*args, **kwargs)
423-
self._add_response_as_metadata(function_name=method_name, data=response, subsegment=subsegment)
424-
except Exception as err:
425-
logger.exception(f"Exception received from '{method_name}' method")
426-
self._add_full_exception_as_metadata(
427-
function_name=method_name, error=err, subsegment=subsegment
428-
)
429-
raise
430-
431-
return response
456+
return response
457+
458+
return decorate
459+
460+
def _decorate_generator_function(self, method: Callable = None):
461+
method_name = f"{method.__name__}"
462+
463+
@functools.wraps(method)
464+
def decorate(*args, **kwargs):
465+
with self.provider.in_subsegment(name=f"## {method_name}") as subsegment:
466+
try:
467+
logger.debug(f"Calling method: {method_name}")
468+
result = yield from method(*args, **kwargs)
469+
self._add_response_as_metadata(function_name=method_name, data=result, subsegment=subsegment)
470+
except Exception as err:
471+
logger.exception(f"Exception received from '{method_name}' method")
472+
self._add_full_exception_as_metadata(function_name=method_name, error=err, subsegment=subsegment)
473+
raise
474+
475+
return result
476+
477+
return decorate
478+
479+
def _decorate_generator_function_with_context_manager(self, method: Callable = None):
480+
method_name = f"{method.__name__}"
481+
482+
@functools.wraps(method)
483+
@contextlib.contextmanager
484+
def decorate(*args, **kwargs):
485+
with self.provider.in_subsegment(name=f"## {method_name}") as subsegment:
486+
try:
487+
logger.debug(f"Calling method: {method_name}")
488+
with method(*args, **kwargs) as return_val:
489+
result = return_val
490+
yield result
491+
self._add_response_as_metadata(function_name=method_name, data=result, subsegment=subsegment)
492+
except Exception as err:
493+
logger.exception(f"Exception received from '{method_name}' method")
494+
self._add_full_exception_as_metadata(function_name=method_name, error=err, subsegment=subsegment)
495+
raise
496+
497+
return decorate
498+
499+
def _decorate_sync_function(self, method: Callable = None):
500+
method_name = f"{method.__name__}"
501+
502+
@functools.wraps(method)
503+
def decorate(*args, **kwargs):
504+
with self.provider.in_subsegment(name=f"## {method_name}") as subsegment:
505+
try:
506+
logger.debug(f"Calling method: {method_name}")
507+
response = method(*args, **kwargs)
508+
self._add_response_as_metadata(function_name=method_name, data=response, subsegment=subsegment)
509+
except Exception as err:
510+
logger.exception(f"Exception received from '{method_name}' method")
511+
self._add_full_exception_as_metadata(function_name=method_name, error=err, subsegment=subsegment)
512+
raise
513+
514+
return response
432515

433516
return decorate
434517

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""General utilities for Powertools"""
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""
4+
Parameter retrieval and caching utility
5+
"""
6+
7+
from .base import BaseProvider
8+
from .dynamodb import DynamoDBProvider
9+
from .exceptions import GetParameterError, TransformParameterError
10+
from .secrets import SecretsProvider, get_secret
11+
from .ssm import SSMProvider, get_parameter, get_parameters
12+
13+
__all__ = [
14+
"BaseProvider",
15+
"GetParameterError",
16+
"DynamoDBProvider",
17+
"SecretsProvider",
18+
"SSMProvider",
19+
"TransformParameterError",
20+
"get_parameter",
21+
"get_parameters",
22+
"get_secret",
23+
]

0 commit comments

Comments
 (0)