5
5
import logging
6
6
import os
7
7
from distutils .util import strtobool
8
- from typing import Any , Callable , Dict , List , Tuple
8
+ from typing import Any , Callable , Dict , List , Optional , Tuple
9
9
10
10
import aws_xray_sdk
11
11
import aws_xray_sdk .core
12
12
13
+ from aws_lambda_powertools .shared .constants import TRACER_CAPTURE_ERROR_ENV , TRACER_CAPTURE_RESPONSE_ENV
14
+ from aws_lambda_powertools .shared .functions import resolve_env_var_choice
15
+
13
16
is_cold_start = True
14
17
logger = logging .getLogger (__name__ )
15
18
@@ -34,6 +37,10 @@ class Tracer:
34
37
disable tracer (e.g. `"true", "True", "TRUE"`)
35
38
POWERTOOLS_SERVICE_NAME : str
36
39
service name
40
+ POWERTOOLS_TRACER_CAPTURE_RESPONSE : str
41
+ disable auto-capture response as metadata (e.g. `"true", "True", "TRUE"`)
42
+ POWERTOOLS_TRACER_CAPTURE_ERROR : str
43
+ disable auto-capture error as metadata (e.g. `"true", "True", "TRUE"`)
37
44
38
45
Parameters
39
46
----------
@@ -226,7 +233,12 @@ def patch(self, modules: Tuple[str] = None):
226
233
else :
227
234
aws_xray_sdk .core .patch (modules )
228
235
229
- def capture_lambda_handler (self , lambda_handler : Callable [[Dict , Any ], Any ] = None , capture_response : bool = True ):
236
+ def capture_lambda_handler (
237
+ self ,
238
+ lambda_handler : Callable [[Dict , Any ], Any ] = None ,
239
+ capture_response : Optional [bool ] = None ,
240
+ capture_error : Optional [bool ] = None ,
241
+ ):
230
242
"""Decorator to create subsegment for lambda handlers
231
243
232
244
As Lambda follows (event, context) signature we can remove some of the boilerplate
@@ -237,7 +249,9 @@ def capture_lambda_handler(self, lambda_handler: Callable[[Dict, Any], Any] = No
237
249
lambda_handler : Callable
238
250
Method to annotate on
239
251
capture_response : bool, optional
240
- Instructs tracer to not include handler's response as metadata, by default True
252
+ Instructs tracer to not include handler's response as metadata
253
+ capture_error : bool, optional
254
+ Instructs tracer to not include handler's error as metadata, by default True
241
255
242
256
Example
243
257
-------
@@ -264,10 +278,15 @@ def handler(event, context):
264
278
# Return a partial function with args filled
265
279
if lambda_handler is None :
266
280
logger .debug ("Decorator called with parameters" )
267
- return functools .partial (self .capture_lambda_handler , capture_response = capture_response )
281
+ return functools .partial (
282
+ self .capture_lambda_handler , capture_response = capture_response , capture_error = capture_error
283
+ )
268
284
269
285
lambda_handler_name = lambda_handler .__name__
270
286
287
+ capture_response = resolve_env_var_choice (env = TRACER_CAPTURE_RESPONSE_ENV , choice = capture_response )
288
+ capture_error = resolve_env_var_choice (env = TRACER_CAPTURE_ERROR_ENV , choice = capture_error )
289
+
271
290
@functools .wraps (lambda_handler )
272
291
def decorate (event , context ):
273
292
with self .provider .in_subsegment (name = f"## { lambda_handler_name } " ) as subsegment :
@@ -290,15 +309,17 @@ def decorate(event, context):
290
309
except Exception as err :
291
310
logger .exception (f"Exception received from { lambda_handler_name } " )
292
311
self ._add_full_exception_as_metadata (
293
- method_name = lambda_handler_name , error = err , subsegment = subsegment
312
+ method_name = lambda_handler_name , error = err , subsegment = subsegment , capture_error = capture_error
294
313
)
295
314
raise
296
315
297
316
return response
298
317
299
318
return decorate
300
319
301
- def capture_method (self , method : Callable = None , capture_response : bool = True ):
320
+ def capture_method (
321
+ self , method : Callable = None , capture_response : Optional [bool ] = None , capture_error : Optional [bool ] = None
322
+ ):
302
323
"""Decorator to create subsegment for arbitrary functions
303
324
304
325
It also captures both response and exceptions as metadata
@@ -317,7 +338,9 @@ def capture_method(self, method: Callable = None, capture_response: bool = True)
317
338
method : Callable
318
339
Method to annotate on
319
340
capture_response : bool, optional
320
- Instructs tracer to not include method's response as metadata, by default True
341
+ Instructs tracer to not include method's response as metadata
342
+ capture_error : bool, optional
343
+ Instructs tracer to not include handler's error as metadata, by default True
321
344
322
345
Example
323
346
-------
@@ -449,51 +472,65 @@ async def async_tasks():
449
472
# Return a partial function with args filled
450
473
if method is None :
451
474
logger .debug ("Decorator called with parameters" )
452
- return functools .partial (self .capture_method , capture_response = capture_response )
475
+ return functools .partial (
476
+ self .capture_method , capture_response = capture_response , capture_error = capture_error
477
+ )
453
478
454
479
method_name = f"{ method .__name__ } "
455
480
481
+ capture_response = resolve_env_var_choice (env = TRACER_CAPTURE_RESPONSE_ENV , choice = capture_response )
482
+ capture_error = resolve_env_var_choice (env = TRACER_CAPTURE_ERROR_ENV , choice = capture_error )
483
+
456
484
if inspect .iscoroutinefunction (method ):
457
485
return self ._decorate_async_function (
458
- method = method , capture_response = capture_response , method_name = method_name
486
+ method = method , capture_response = capture_response , capture_error = capture_error , method_name = method_name
459
487
)
460
488
elif inspect .isgeneratorfunction (method ):
461
489
return self ._decorate_generator_function (
462
- method = method , capture_response = capture_response , method_name = method_name
490
+ method = method , capture_response = capture_response , capture_error = capture_error , method_name = method_name
463
491
)
464
492
elif hasattr (method , "__wrapped__" ) and inspect .isgeneratorfunction (method .__wrapped__ ):
465
493
return self ._decorate_generator_function_with_context_manager (
466
- method = method , capture_response = capture_response , method_name = method_name
494
+ method = method , capture_response = capture_response , capture_error = capture_error , method_name = method_name
467
495
)
468
496
else :
469
497
return self ._decorate_sync_function (
470
- method = method , capture_response = capture_response , method_name = method_name
498
+ method = method , capture_response = capture_response , capture_error = capture_error , method_name = method_name
471
499
)
472
500
473
- def _decorate_async_function (self , method : Callable = None , capture_response : bool = True , method_name : str = None ):
501
+ def _decorate_async_function (
502
+ self ,
503
+ method : Callable = None ,
504
+ capture_response : Optional [bool ] = None ,
505
+ capture_error : Optional [bool ] = None ,
506
+ method_name : str = None ,
507
+ ):
474
508
@functools .wraps (method )
475
509
async def decorate (* args , ** kwargs ):
476
510
async with self .provider .in_subsegment_async (name = f"## { method_name } " ) as subsegment :
477
511
try :
478
512
logger .debug (f"Calling method: { method_name } " )
479
513
response = await method (* args , ** kwargs )
480
514
self ._add_response_as_metadata (
481
- method_name = method_name ,
482
- data = response ,
483
- subsegment = subsegment ,
484
- capture_response = capture_response ,
515
+ method_name = method_name , data = response , subsegment = subsegment , capture_response = capture_response
485
516
)
486
517
except Exception as err :
487
518
logger .exception (f"Exception received from '{ method_name } ' method" )
488
- self ._add_full_exception_as_metadata (method_name = method_name , error = err , subsegment = subsegment )
519
+ self ._add_full_exception_as_metadata (
520
+ method_name = method_name , error = err , subsegment = subsegment , capture_error = capture_error
521
+ )
489
522
raise
490
523
491
524
return response
492
525
493
526
return decorate
494
527
495
528
def _decorate_generator_function (
496
- self , method : Callable = None , capture_response : bool = True , method_name : str = None
529
+ self ,
530
+ method : Callable = None ,
531
+ capture_response : Optional [bool ] = None ,
532
+ capture_error : Optional [bool ] = None ,
533
+ method_name : str = None ,
497
534
):
498
535
@functools .wraps (method )
499
536
def decorate (* args , ** kwargs ):
@@ -506,15 +543,21 @@ def decorate(*args, **kwargs):
506
543
)
507
544
except Exception as err :
508
545
logger .exception (f"Exception received from '{ method_name } ' method" )
509
- self ._add_full_exception_as_metadata (method_name = method_name , error = err , subsegment = subsegment )
546
+ self ._add_full_exception_as_metadata (
547
+ method_name = method_name , error = err , subsegment = subsegment , capture_error = capture_error
548
+ )
510
549
raise
511
550
512
551
return result
513
552
514
553
return decorate
515
554
516
555
def _decorate_generator_function_with_context_manager (
517
- self , method : Callable = None , capture_response : bool = True , method_name : str = None
556
+ self ,
557
+ method : Callable = None ,
558
+ capture_response : Optional [bool ] = None ,
559
+ capture_error : Optional [bool ] = None ,
560
+ method_name : str = None ,
518
561
):
519
562
@functools .wraps (method )
520
563
@contextlib .contextmanager
@@ -530,12 +573,20 @@ def decorate(*args, **kwargs):
530
573
)
531
574
except Exception as err :
532
575
logger .exception (f"Exception received from '{ method_name } ' method" )
533
- self ._add_full_exception_as_metadata (method_name = method_name , error = err , subsegment = subsegment )
576
+ self ._add_full_exception_as_metadata (
577
+ method_name = method_name , error = err , subsegment = subsegment , capture_error = capture_error
578
+ )
534
579
raise
535
580
536
581
return decorate
537
582
538
- def _decorate_sync_function (self , method : Callable = None , capture_response : bool = True , method_name : str = None ):
583
+ def _decorate_sync_function (
584
+ self ,
585
+ method : Callable = None ,
586
+ capture_response : Optional [bool ] = None ,
587
+ capture_error : Optional [bool ] = None ,
588
+ method_name : str = None ,
589
+ ):
539
590
@functools .wraps (method )
540
591
def decorate (* args , ** kwargs ):
541
592
with self .provider .in_subsegment (name = f"## { method_name } " ) as subsegment :
@@ -550,7 +601,9 @@ def decorate(*args, **kwargs):
550
601
)
551
602
except Exception as err :
552
603
logger .exception (f"Exception received from '{ method_name } ' method" )
553
- self ._add_full_exception_as_metadata (method_name = method_name , error = err , subsegment = subsegment )
604
+ self ._add_full_exception_as_metadata (
605
+ method_name = method_name , error = err , subsegment = subsegment , capture_error = capture_error
606
+ )
554
607
raise
555
608
556
609
return response
@@ -562,7 +615,7 @@ def _add_response_as_metadata(
562
615
method_name : str = None ,
563
616
data : Any = None ,
564
617
subsegment : aws_xray_sdk .core .models .subsegment = None ,
565
- capture_response : bool = True ,
618
+ capture_response : Optional [ bool ] = None ,
566
619
):
567
620
"""Add response as metadata for given subsegment
568
621
@@ -575,15 +628,19 @@ def _add_response_as_metadata(
575
628
subsegment : aws_xray_sdk.core.models.subsegment, optional
576
629
existing subsegment to add metadata on, by default None
577
630
capture_response : bool, optional
578
- Do not include response as metadata, by default True
631
+ Do not include response as metadata
579
632
"""
580
633
if data is None or not capture_response or subsegment is None :
581
634
return
582
635
583
636
subsegment .put_metadata (key = f"{ method_name } response" , value = data , namespace = self ._config ["service" ])
584
637
585
638
def _add_full_exception_as_metadata (
586
- self , method_name : str = None , error : Exception = None , subsegment : aws_xray_sdk .core .models .subsegment = None
639
+ self ,
640
+ method_name : str = None ,
641
+ error : Exception = None ,
642
+ subsegment : aws_xray_sdk .core .models .subsegment = None ,
643
+ capture_error : Optional [bool ] = None ,
587
644
):
588
645
"""Add full exception object as metadata for given subsegment
589
646
@@ -595,7 +652,12 @@ def _add_full_exception_as_metadata(
595
652
error to add as subsegment metadata, by default None
596
653
subsegment : aws_xray_sdk.core.models.subsegment, optional
597
654
existing subsegment to add metadata on, by default None
655
+ capture_error : bool, optional
656
+ Do not include error as metadata, by default True
598
657
"""
658
+ if not capture_error :
659
+ return
660
+
599
661
subsegment .put_metadata (key = f"{ method_name } error" , value = error , namespace = self ._config ["service" ])
600
662
601
663
@staticmethod
0 commit comments