Skip to content

Commit 8ab3507

Browse files
committed
improv: add capture_response/error env vars
1 parent 1131541 commit 8ab3507

File tree

1 file changed

+78
-22
lines changed

1 file changed

+78
-22
lines changed

aws_lambda_powertools/tracing/tracer.py

Lines changed: 78 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ class Tracer:
3434
disable tracer (e.g. `"true", "True", "TRUE"`)
3535
POWERTOOLS_SERVICE_NAME : str
3636
service name
37+
POWERTOOLS_TRACER_CAPTURE_RESPONSE : str
38+
disable auto-capture response as metadata (e.g. `"true", "True", "TRUE"`)
39+
POWERTOOLS_TRACER_CAPTURE_ERROR : str
40+
disable auto-capture error as metadata (e.g. `"true", "True", "TRUE"`)
3741
3842
Parameters
3943
----------
@@ -226,7 +230,12 @@ def patch(self, modules: Tuple[str] = None):
226230
else:
227231
aws_xray_sdk.core.patch(modules)
228232

229-
def capture_lambda_handler(self, lambda_handler: Callable[[Dict, Any], Any] = None, capture_response: bool = True):
233+
def capture_lambda_handler(
234+
self,
235+
lambda_handler: Callable[[Dict, Any], Any] = None,
236+
capture_response: bool = True,
237+
capture_error: bool = True,
238+
):
230239
"""Decorator to create subsegment for lambda handlers
231240
232241
As Lambda follows (event, context) signature we can remove some of the boilerplate
@@ -238,6 +247,8 @@ def capture_lambda_handler(self, lambda_handler: Callable[[Dict, Any], Any] = No
238247
Method to annotate on
239248
capture_response : bool, optional
240249
Instructs tracer to not include handler's response as metadata, by default True
250+
capture_error : bool, optional
251+
Instructs tracer to not include handler's error as metadata, by default True
241252
242253
Example
243254
-------
@@ -264,10 +275,17 @@ def handler(event, context):
264275
# Return a partial function with args filled
265276
if lambda_handler is None:
266277
logger.debug("Decorator called with parameters")
267-
return functools.partial(self.capture_lambda_handler, capture_response=capture_response)
278+
return functools.partial(
279+
self.capture_lambda_handler, capture_response=capture_response, capture_error=capture_error
280+
)
268281

269282
lambda_handler_name = lambda_handler.__name__
270283

284+
capture_response_env_option = str(os.getenv("POWERTOOLS_TRACER_CAPTURE_RESPONSE", "false"))
285+
capture_error_env_option = str(os.getenv("POWERTOOLS_TRACER_CAPTURE_ERROR", "false"))
286+
capture_response = strtobool(capture_response_env_option) or capture_response
287+
capture_error = strtobool(capture_error_env_option) or capture_error
288+
271289
@functools.wraps(lambda_handler)
272290
def decorate(event, context):
273291
with self.provider.in_subsegment(name=f"## {lambda_handler_name}") as subsegment:
@@ -290,15 +308,15 @@ def decorate(event, context):
290308
except Exception as err:
291309
logger.exception(f"Exception received from {lambda_handler_name}")
292310
self._add_full_exception_as_metadata(
293-
method_name=lambda_handler_name, error=err, subsegment=subsegment
311+
method_name=lambda_handler_name, error=err, subsegment=subsegment, capture_error=capture_error
294312
)
295313
raise
296314

297315
return response
298316

299317
return decorate
300318

301-
def capture_method(self, method: Callable = None, capture_response: bool = True):
319+
def capture_method(self, method: Callable = None, capture_response: bool = True, capture_error: bool = True):
302320
"""Decorator to create subsegment for arbitrary functions
303321
304322
It also captures both response and exceptions as metadata
@@ -318,6 +336,8 @@ def capture_method(self, method: Callable = None, capture_response: bool = True)
318336
Method to annotate on
319337
capture_response : bool, optional
320338
Instructs tracer to not include method's response as metadata, by default True
339+
capture_error : bool, optional
340+
Instructs tracer to not include handler's error as metadata, by default True
321341
322342
Example
323343
-------
@@ -449,51 +469,62 @@ async def async_tasks():
449469
# Return a partial function with args filled
450470
if method is None:
451471
logger.debug("Decorator called with parameters")
452-
return functools.partial(self.capture_method, capture_response=capture_response)
472+
return functools.partial(
473+
self.capture_method, capture_response=capture_response, capture_error=capture_error
474+
)
453475

454476
method_name = f"{method.__name__}"
455477

456478
if inspect.iscoroutinefunction(method):
457479
return self._decorate_async_function(
458-
method=method, capture_response=capture_response, method_name=method_name
480+
method=method, capture_response=capture_response, capture_error=True, method_name=method_name
459481
)
460482
elif inspect.isgeneratorfunction(method):
461483
return self._decorate_generator_function(
462-
method=method, capture_response=capture_response, method_name=method_name
484+
method=method, capture_response=capture_response, capture_error=True, method_name=method_name
463485
)
464486
elif hasattr(method, "__wrapped__") and inspect.isgeneratorfunction(method.__wrapped__):
465487
return self._decorate_generator_function_with_context_manager(
466-
method=method, capture_response=capture_response, method_name=method_name
488+
method=method, capture_response=capture_response, capture_error=True, method_name=method_name
467489
)
468490
else:
469491
return self._decorate_sync_function(
470-
method=method, capture_response=capture_response, method_name=method_name
492+
method=method, capture_response=capture_response, capture_error=True, method_name=method_name
471493
)
472494

473-
def _decorate_async_function(self, method: Callable = None, capture_response: bool = True, method_name: str = None):
495+
def _decorate_async_function(
496+
self,
497+
method: Callable = None,
498+
capture_response: bool = True,
499+
capture_error: bool = True,
500+
method_name: str = None,
501+
):
474502
@functools.wraps(method)
475503
async def decorate(*args, **kwargs):
476504
async with self.provider.in_subsegment_async(name=f"## {method_name}") as subsegment:
477505
try:
478506
logger.debug(f"Calling method: {method_name}")
479507
response = await method(*args, **kwargs)
480508
self._add_response_as_metadata(
481-
method_name=method_name,
482-
data=response,
483-
subsegment=subsegment,
484-
capture_response=capture_response,
509+
method_name=method_name, data=response, subsegment=subsegment, capture_response=capture_response
485510
)
486511
except Exception as err:
487512
logger.exception(f"Exception received from '{method_name}' method")
488-
self._add_full_exception_as_metadata(method_name=method_name, error=err, subsegment=subsegment)
513+
self._add_full_exception_as_metadata(
514+
method_name=method_name, error=err, subsegment=subsegment, capture_error=capture_error
515+
)
489516
raise
490517

491518
return response
492519

493520
return decorate
494521

495522
def _decorate_generator_function(
496-
self, method: Callable = None, capture_response: bool = True, method_name: str = None
523+
self,
524+
method: Callable = None,
525+
capture_response: bool = True,
526+
capture_error: bool = True,
527+
method_name: str = None,
497528
):
498529
@functools.wraps(method)
499530
def decorate(*args, **kwargs):
@@ -506,15 +537,21 @@ def decorate(*args, **kwargs):
506537
)
507538
except Exception as err:
508539
logger.exception(f"Exception received from '{method_name}' method")
509-
self._add_full_exception_as_metadata(method_name=method_name, error=err, subsegment=subsegment)
540+
self._add_full_exception_as_metadata(
541+
method_name=method_name, error=err, subsegment=subsegment, capture_error=capture_error
542+
)
510543
raise
511544

512545
return result
513546

514547
return decorate
515548

516549
def _decorate_generator_function_with_context_manager(
517-
self, method: Callable = None, capture_response: bool = True, method_name: str = None
550+
self,
551+
method: Callable = None,
552+
capture_response: bool = True,
553+
capture_error: bool = True,
554+
method_name: str = None,
518555
):
519556
@functools.wraps(method)
520557
@contextlib.contextmanager
@@ -530,12 +567,20 @@ def decorate(*args, **kwargs):
530567
)
531568
except Exception as err:
532569
logger.exception(f"Exception received from '{method_name}' method")
533-
self._add_full_exception_as_metadata(method_name=method_name, error=err, subsegment=subsegment)
570+
self._add_full_exception_as_metadata(
571+
method_name=method_name, error=err, subsegment=subsegment, capture_error=capture_error
572+
)
534573
raise
535574

536575
return decorate
537576

538-
def _decorate_sync_function(self, method: Callable = None, capture_response: bool = True, method_name: str = None):
577+
def _decorate_sync_function(
578+
self,
579+
method: Callable = None,
580+
capture_response: bool = True,
581+
capture_error: bool = True,
582+
method_name: str = None,
583+
):
539584
@functools.wraps(method)
540585
def decorate(*args, **kwargs):
541586
with self.provider.in_subsegment(name=f"## {method_name}") as subsegment:
@@ -550,7 +595,9 @@ def decorate(*args, **kwargs):
550595
)
551596
except Exception as err:
552597
logger.exception(f"Exception received from '{method_name}' method")
553-
self._add_full_exception_as_metadata(method_name=method_name, error=err, subsegment=subsegment)
598+
self._add_full_exception_as_metadata(
599+
method_name=method_name, error=err, subsegment=subsegment, capture_error=capture_error
600+
)
554601
raise
555602

556603
return response
@@ -583,7 +630,11 @@ def _add_response_as_metadata(
583630
subsegment.put_metadata(key=f"{method_name} response", value=data, namespace=self._config["service"])
584631

585632
def _add_full_exception_as_metadata(
586-
self, method_name: str = None, error: Exception = None, subsegment: aws_xray_sdk.core.models.subsegment = None
633+
self,
634+
method_name: str = None,
635+
error: Exception = None,
636+
subsegment: aws_xray_sdk.core.models.subsegment = None,
637+
capture_error: bool = True,
587638
):
588639
"""Add full exception object as metadata for given subsegment
589640
@@ -595,7 +646,12 @@ def _add_full_exception_as_metadata(
595646
error to add as subsegment metadata, by default None
596647
subsegment : aws_xray_sdk.core.models.subsegment, optional
597648
existing subsegment to add metadata on, by default None
649+
capture_error : bool, optional
650+
Do not include error as metadata, by default True
598651
"""
652+
if error is None or not capture_error or subsegment is None:
653+
return
654+
599655
subsegment.put_metadata(key=f"{method_name} error", value=error, namespace=self._config["service"])
600656

601657
@staticmethod

0 commit comments

Comments
 (0)