Skip to content

Commit a3ec1c3

Browse files
committed
fix: Runtime Error for nested sync fns #32
1 parent fdb90a8 commit a3ec1c3

File tree

2 files changed

+48
-56
lines changed

2 files changed

+48
-56
lines changed

Diff for: python/aws_lambda_powertools/tracing/tracer.py

+28-56
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ def decorate(event, context):
270270
key="lambda handler response", value=response, namespace=self._config["service"]
271271
)
272272
except Exception as err:
273-
logger.exception("Exception received from lambda handler", exc_info=True)
273+
logger.exception("Exception received from lambda handler")
274274
subsegment.put_metadata(key=f"{self.service} error", value=err, namespace=self._config["service"])
275275
raise
276276

@@ -392,68 +392,40 @@ async def async_tasks():
392392
"""
393393
method_name = f"{method.__name__}"
394394

395-
async def decorate_logic(
396-
decorated_method_with_args: functools.partial = None,
397-
subsegment: aws_xray_sdk.core.models.subsegment = None,
398-
coroutine: bool = False,
399-
) -> Any:
400-
"""Decorate logic runs both sync and async decorated methods
401-
402-
Parameters
403-
----------
404-
decorated_method_with_args : functools.partial
405-
Partial decorated method with arguments/keyword arguments
406-
subsegment : aws_xray_sdk.core.models.subsegment
407-
X-Ray subsegment to reuse
408-
coroutine : bool, optional
409-
Instruct whether partial decorated method is a wrapped coroutine, by default False
410-
411-
Returns
412-
-------
413-
Any
414-
Returns method's response
415-
"""
416-
response = None
417-
try:
418-
logger.debug(f"Calling method: {method_name}")
419-
if coroutine:
420-
response = await decorated_method_with_args()
421-
else:
422-
response = decorated_method_with_args()
423-
logger.debug(f"Received {method_name} response successfully")
424-
logger.debug(response)
425-
except Exception as err:
426-
logger.exception(f"Exception received from '{method_name}' method", exc_info=True)
427-
subsegment.put_metadata(key=f"{method_name} error", value=err, namespace=self._config["service"])
428-
raise
429-
finally:
430-
if response is not None:
431-
subsegment.put_metadata( # pragma: no cover
432-
key=f"{method_name} response", value=response, namespace=self._config["service"]
433-
)
434-
435-
return response
436-
437395
if inspect.iscoroutinefunction(method):
438-
439396
@functools.wraps(method)
440397
async def decorate(*args, **kwargs):
441-
decorated_method_with_args = functools.partial(method, *args, **kwargs)
442-
async with self.provider.in_subsegment_async(name=f"## {method_name}") as subsegment:
443-
return await decorate_logic(
444-
decorated_method_with_args=decorated_method_with_args, subsegment=subsegment, coroutine=True
445-
)
446-
398+
async with self.provider.in_subsegment_async(name=f"## {method_name}") as subsegment:
399+
try:
400+
logger.debug(f"Calling method: {method_name}")
401+
response = await method(*args, **kwargs)
402+
if response is not None:
403+
subsegment.put_metadata( # pragma: no cover
404+
key=f"{method_name} response", value=response, namespace=self._config["service"]
405+
)
406+
except Exception as err:
407+
logger.exception(f"Exception received from '{method_name}' method")
408+
subsegment.put_metadata(key=f"{method_name} error", value=err, namespace=self._config["service"])
409+
raise
410+
411+
return response
447412
else:
448-
449413
@functools.wraps(method)
450414
def decorate(*args, **kwargs):
451-
loop = asyncio.get_event_loop()
452-
decorated_method_with_args = functools.partial(method, *args, **kwargs)
453415
with self.provider.in_subsegment(name=f"## {method_name}") as subsegment:
454-
return loop.run_until_complete(
455-
decorate_logic(decorated_method_with_args=decorated_method_with_args, subsegment=subsegment)
456-
)
416+
try:
417+
logger.debug(f"Calling method: {method_name}")
418+
response = method(*args, **kwargs)
419+
if response is not None:
420+
subsegment.put_metadata( # pragma: no cover
421+
key=f"{method_name} response", value=response, namespace=self._config["service"]
422+
)
423+
except Exception as err:
424+
logger.exception(f"Exception received from '{method_name}' method")
425+
subsegment.put_metadata(key=f"{method_name} error", value=err, namespace=self._config["service"])
426+
raise
427+
428+
return response
457429

458430
return decorate
459431

Diff for: python/tests/functional/test_tracing.py

+20
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,23 @@ def test_tracer_reuse():
124124

125125
assert id(tracer_a) != id(tracer_b)
126126
assert tracer_a.__dict__.items() == tracer_b.__dict__.items()
127+
128+
def test_tracer_method_nested_sync(mocker):
129+
# GIVEN tracer is disabled, decorator is used
130+
# WHEN multiple sync functions are nested
131+
# THEN tracer should not raise a Runtime Error
132+
tracer = Tracer(disabled=True)
133+
134+
@tracer.capture_method
135+
def func_1():
136+
return 1
137+
138+
@tracer.capture_method
139+
def func_2():
140+
return 2
141+
142+
@tracer.capture_method
143+
def sums_values():
144+
return func_1() + func_2()
145+
146+
sums_values()

0 commit comments

Comments
 (0)