Skip to content

Commit 1f0a6c9

Browse files
committed
improv: address nicolas feedback
1 parent bfe3404 commit 1f0a6c9

File tree

6 files changed

+78
-147
lines changed

6 files changed

+78
-147
lines changed

python/README.md

+11-6
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ def handler(event, context)
108108

109109
# another_file.py
110110
from aws_lambda_powertools.tracing import Tracer
111-
tracer = Tracer.instance()
111+
tracer = Tracer(auto_patch=False) # new instance using existing configuration with auto patching overriden
112112
```
113113

114114
### Logging
@@ -256,8 +256,14 @@ def middleware_before_after(handler, event, context):
256256
logic_after_handler_execution()
257257
return response
258258

259-
@middleware_before_after
260-
@middleware_name
259+
260+
# middleware_name will wrap Lambda handler
261+
# and simply return the handler as we're not pre/post-processing anything
262+
# then middleware_before_after will wrap middleware_name
263+
# run some code before/after calling the handler returned by middleware_name
264+
# This way, lambda_handler is only actually called once (top-down)
265+
@middleware_before_after # This will run last
266+
@middleware_name # This will run first
261267
def lambda_handler(event, context):
262268
return True
263269
```
@@ -273,8 +279,7 @@ def obfuscate_sensitive_data(handler, event, context, fields=None):
273279
field = event.get(field, "")
274280
event[field] = obfuscate_pii(field)
275281

276-
response = handler(event, context)
277-
return response
282+
return handler(event, context)
278283

279284
@obfuscate_sensitive_data(fields=["email"])
280285
def lambda_handler(event, context):
@@ -305,7 +310,7 @@ from aws_lambda_powertools.tracing import Tracer
305310

306311
@lambda_handler_decorator(trace_execution=True)
307312
def middleware_name(handler, event, context):
308-
tracer = Tracer.instance() # Takes a copy of an existing tracer instance
313+
tracer = Tracer() # Takes a copy of an existing tracer instance
309314
tracer.add_anotation...
310315
tracer.metadata...
311316
return handler(event, context)

python/aws_lambda_powertools/helper/register.py

-65
This file was deleted.

python/aws_lambda_powertools/middleware_factory/factory.py

+6-15
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22
import inspect
33
import logging
44
import os
5-
from contextlib import contextmanager
65
from distutils.util import strtobool
76
from typing import Callable
87

8+
from ..tracing import Tracer
9+
910
logger = logging.getLogger(__name__)
1011
logger.setLevel(os.getenv("LOG_LEVEL", "INFO"))
1112

@@ -117,8 +118,10 @@ def wrapper(event, context):
117118
try:
118119
middleware = functools.partial(decorator, func, event, context, **kwargs)
119120
if trace_execution:
120-
with _trace_middleware(middleware=decorator):
121-
response = middleware()
121+
tracer = Tracer(auto_patch=False)
122+
tracer.create_subsegment(name=f"## middleware {decorator.__qualname__}")
123+
response = middleware()
124+
tracer.end_subsegment()
122125
else:
123126
response = middleware()
124127
return response
@@ -129,15 +132,3 @@ def wrapper(event, context):
129132
return wrapper
130133

131134
return final_decorator
132-
133-
134-
@contextmanager
135-
def _trace_middleware(middleware):
136-
try:
137-
from ..tracing import Tracer
138-
139-
tracer = Tracer.instance()
140-
tracer.create_subsegment(name=f"## middleware {middleware.__qualname__}")
141-
yield
142-
finally:
143-
tracer.end_subsegment()

python/aws_lambda_powertools/tracing/tracer.py

+42-26
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import copy
12
import functools
23
import logging
34
import os
@@ -6,25 +7,25 @@
67

78
from aws_xray_sdk.core import models, patch_all, xray_recorder
89

9-
from ..helper.register import RegisterMeta
10-
1110
is_cold_start = True
1211
logger = logging.getLogger(__name__)
1312
logger.setLevel(os.getenv("LOG_LEVEL", "INFO"))
1413

1514

16-
class Tracer(metaclass=RegisterMeta):
15+
class Tracer:
1716
"""Tracer using AWS-XRay to provide decorators with known defaults for Lambda functions
1817
19-
When running locally, it honours `POWERTOOLS_TRACE_DISABLED` environment variable
20-
so end user code doesn't have to be modified to run it locally
21-
instead Tracer returns dummy segments/subsegments.
22-
23-
Tracing is automatically disabled when running locally via via SAM CLI.
18+
When running locally, it detects whether it's running via SAM CLI,
19+
and if it is it returns dummy segments/subsegments instead.
2420
25-
By default, it patches all available libraries supported by X-Ray SDK. \n
21+
By default, it patches all available libraries supported by X-Ray SDK. Patching is
22+
automatically disabled when running locally via SAM CLI or by any other means. \n
2623
Ref: https://docs.aws.amazon.com/xray-sdk-for-python/latest/reference/thirdparty.html
2724
25+
Tracer keeps a copy of its configuration as it can be instantiated more than once. This
26+
is useful when you are using your own middlewares and want to utilize an existing Tracer.
27+
Make sure to set `auto_patch=False` in subsequent Tracer instances to avoid double patching.
28+
2829
Environment variables
2930
---------------------
3031
POWERTOOLS_TRACE_DISABLED : str
@@ -39,7 +40,7 @@ class Tracer(metaclass=RegisterMeta):
3940
auto_patch: bool
4041
Patch existing imported modules during initialization, by default True
4142
disabled: bool
42-
Flag to explicitly disable tracing, useful when running locally.
43+
Flag to explicitly disable tracing, useful when running/testing locally.
4344
`Env POWERTOOLS_TRACE_DISABLED="true"`
4445
4546
Example
@@ -105,7 +106,7 @@ def handler(event: dict, context: Any) -> Dict:
105106
106107
# utils.py
107108
from aws_lambda_powertools.tracing import Tracer
108-
tracer = Tracer.instance()
109+
tracer = Tracer()
109110
...
110111
111112
Returns
@@ -119,22 +120,22 @@ def handler(event: dict, context: Any) -> Dict:
119120
120121
"""
121122

123+
_default_config = {"service": "service_undefined", "disabled": False, "provider": xray_recorder, "auto_patch": True}
124+
_config = copy.copy(_default_config)
125+
122126
def __init__(
123-
self,
124-
service: str = "service_undefined",
125-
disabled: bool = False,
126-
provider: xray_recorder = xray_recorder,
127-
auto_patch: bool = True,
127+
self, service: str = None, disabled: bool = None, provider: xray_recorder = None, auto_patch: bool = None
128128
):
129-
self.provider = provider
130-
self.disabled = self.__is_trace_disabled() or disabled
131-
self.service = os.getenv("POWERTOOLS_SERVICE_NAME") or service
132-
self.auto_patch = auto_patch
129+
self.__build_config(service=service, disabled=disabled, provider=provider, auto_patch=auto_patch)
130+
self.provider = self._config["provider"]
131+
self.disabled = self._config["disabled"]
132+
self.service = self._config["service"]
133+
self.auto_patch = self._config["auto_patch"]
133134

134135
if self.disabled:
135136
self.__disable_tracing_provider()
136137

137-
if auto_patch:
138+
if self.auto_patch:
138139
self.patch()
139140

140141
def capture_lambda_handler(self, lambda_handler: Callable[[Dict, Any], Any] = None):
@@ -340,18 +341,17 @@ def end_subsegment(self):
340341
self.provider.end_subsegment()
341342

342343
def patch(self):
343-
"""Patch modules for instrumentation
344-
"""
344+
"""Patch modules for instrumentation"""
345345
logger.debug("Patching modules...")
346346

347-
is_lambda_emulator = os.getenv("AWS_SAM_LOCAL")
348-
is_lambda_env = os.getenv("LAMBDA_TASK_ROOT")
347+
is_lambda_emulator = os.getenv("AWS_SAM_LOCAL", False)
348+
is_lambda_env = os.getenv("LAMBDA_TASK_ROOT", False)
349349

350350
if self.disabled:
351351
logger.debug("Tracing has been disabled, aborting patch")
352352
return
353353

354-
if is_lambda_emulator or not is_lambda_env:
354+
if is_lambda_emulator or is_lambda_env:
355355
logger.debug("Running under SAM CLI env or not in Lambda; aborting patch")
356356
return
357357

@@ -390,3 +390,19 @@ def __is_trace_disabled(self) -> bool:
390390
return is_lambda_emulator
391391

392392
return False
393+
394+
def __build_config(
395+
self, service: str = None, disabled: bool = None, provider: xray_recorder = None, auto_patch: bool = None
396+
):
397+
""" Populates Tracer config for new and existing initializations """
398+
is_disabled = disabled if disabled is not None else self.__is_trace_disabled()
399+
is_service = service if service is not None else os.getenv("POWERTOOLS_SERVICE_NAME")
400+
401+
self._config["provider"] = provider if provider is not None else self._config["provider"]
402+
self._config["auto_patch"] = auto_patch if auto_patch is not None else self._config["auto_patch"]
403+
self._config["service"] = is_service if is_service else self._config["service"]
404+
self._config["disabled"] = is_disabled if is_disabled else self._config["disabled"]
405+
406+
@classmethod
407+
def _reset_config(cls):
408+
cls._config = copy.copy(cls._default_config)

python/tests/functional/test_tracing.py

+8-24
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ def dummy_response():
99

1010

1111
@pytest.fixture(scope="function", autouse=True)
12-
def reset_singleton():
12+
def reset_tracing_config():
13+
Tracer._reset_config()
1314
yield
14-
Tracer.clear_instance()
1515

1616

1717
def test_capture_lambda_handler(dummy_response):
@@ -53,6 +53,7 @@ def handler(event, context):
5353
return dummy_response
5454

5555
handler({}, {})
56+
monkeypatch.delenv("AWS_SAM_LOCAL")
5657

5758

5859
def test_tracer_metadata_disabled(dummy_response):
@@ -114,29 +115,12 @@ def greeting(name, message):
114115

115116

116117
def test_tracer_reuse():
117-
# GIVEN tracer A, B and C were initialized
118-
# WHEN tracer B explicitly reuses A instance
118+
# GIVEN tracer A, B were initialized
119+
# WHEN tracer B explicitly reuses A config
119120
# THEN tracer B attributes should be equal to tracer A
120-
# and tracer C should use have defaults instead
121121
service_name = "booking"
122122
tracer_a = Tracer(disabled=True, service=service_name)
123-
tracer_b = Tracer.instance()
124-
tracer_c = Tracer()
123+
tracer_b = Tracer()
125124

126-
assert id(tracer_a) == id(tracer_b)
127-
assert id(tracer_a) != id(tracer_c)
128-
assert id(tracer_b) != id(tracer_c)
129-
130-
131-
def test_tracer_reuse_no_instance():
132-
# GIVEN tracer A and B instance were not registered
133-
# WHEN instance is method is explicitly called
134-
# THEN we initialize a new instance and register it
135-
# so that tracer B receives a copy of the instance
136-
# and only one __init__ is executed
137-
tracer_a = Tracer.instance()
138-
tracer_b = Tracer.instance()
139-
140-
assert id(tracer_a) == id(tracer_b)
141-
142-
Tracer.clear_instance()
125+
assert id(tracer_a) != id(tracer_b)
126+
assert tracer_a.__dict__.items() == tracer_b.__dict__.items()

python/tests/unit/test_tracing.py

+11-11
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ def end_subsegment(self, *args, **kwargs):
4040
return XRayStub
4141

4242

43+
@pytest.fixture(scope="function", autouse=True)
44+
def reset_tracing_config():
45+
Tracer._reset_config()
46+
yield
47+
48+
4349
def test_tracer_lambda_handler(mocker, dummy_response, xray_stub):
4450
put_metadata_mock = mocker.MagicMock()
4551
begin_subsegment_mock = mocker.MagicMock()
@@ -130,26 +136,20 @@ def handler(event, context):
130136
assert put_annotation_mock.call_count == 1
131137
assert put_annotation_mock.call_args == mocker.call(key=annotation_key, value=annotation_value)
132138

133-
@mock.patch('aws_lambda_powertools.tracing.Tracer.patch')
139+
140+
@mock.patch("aws_lambda_powertools.tracing.Tracer.patch")
134141
def test_tracer_autopatch(patch_mock):
135142
# GIVEN tracer is instantiated
136143
# WHEN default options were used, or patch() was called
137144
# THEN tracer should patch all modules
138-
tracer_a = Tracer(disabled=True)
139-
145+
Tracer(disabled=True)
140146
assert patch_mock.call_count == 1
141147

142-
tracer_b = Tracer(disabled=True, auto_patch=False)
143-
tracer_b.patch()
144148

145-
assert patch_mock.call_count == 2
146-
147-
@mock.patch('aws_lambda_powertools.tracing.Tracer.patch')
149+
@mock.patch("aws_lambda_powertools.tracing.Tracer.patch")
148150
def test_tracer_no_autopatch(patch_mock):
149151
# GIVEN tracer is instantiated
150152
# WHEN auto_patch is disabled
151153
# THEN tracer should not patch any module
152-
tracer_a = Tracer(disabled=True, auto_patch=False)
153-
tracer_b = tracer_a.instance()
154-
154+
Tracer(disabled=True, auto_patch=False)
155155
assert patch_mock.call_count == 0

0 commit comments

Comments
 (0)