Skip to content

Commit e4a1b9d

Browse files
authored
feat(tracer): add service annotation when service is set (#861)
1 parent 1ad18c9 commit e4a1b9d

File tree

4 files changed

+80
-12
lines changed

4 files changed

+80
-12
lines changed

Diff for: Makefile

+3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ test:
2020
poetry run pytest -m "not perf" --cov=aws_lambda_powertools --cov-report=xml
2121
poetry run pytest --cache-clear tests/performance
2222

23+
unit-test:
24+
poetry run pytest tests/unit
25+
2326
coverage-html:
2427
poetry run pytest -m "not perf" --cov=aws_lambda_powertools --cov-report=html
2528

Diff for: aws_lambda_powertools/tracing/tracer.py

+36-10
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
logger = logging.getLogger(__name__)
1818

1919
aws_xray_sdk = LazyLoader(constants.XRAY_SDK_MODULE, globals(), constants.XRAY_SDK_MODULE)
20-
aws_xray_sdk.core = LazyLoader(constants.XRAY_SDK_CORE_MODULE, globals(), constants.XRAY_SDK_CORE_MODULE) # type: ignore # noqa: E501
2120

2221

2322
class Tracer:
@@ -137,7 +136,7 @@ def handler(event: dict, context: Any) -> Dict:
137136
"""
138137

139138
_default_config: Dict[str, Any] = {
140-
"service": "service_undefined",
139+
"service": "",
141140
"disabled": False,
142141
"auto_patch": True,
143142
"patch_modules": None,
@@ -156,7 +155,7 @@ def __init__(
156155
self.__build_config(
157156
service=service, disabled=disabled, auto_patch=auto_patch, patch_modules=patch_modules, provider=provider
158157
)
159-
self.provider: BaseProvider = self._config["provider"]
158+
self.provider = self._config["provider"]
160159
self.disabled = self._config["disabled"]
161160
self.service = self._config["service"]
162161
self.auto_patch = self._config["auto_patch"]
@@ -167,10 +166,8 @@ def __init__(
167166
if self.auto_patch:
168167
self.patch(modules=patch_modules)
169168

170-
# Set the streaming threshold to 0 on the default recorder to force sending
171-
# subsegments individually, rather than batching them.
172-
# See https://github.com/awslabs/aws-lambda-powertools-python/issues/283
173-
aws_xray_sdk.core.xray_recorder.configure(streaming_threshold=0) # noqa: E800
169+
if self._is_xray_provider():
170+
self._disable_xray_trace_batching()
174171

175172
def put_annotation(self, key: str, value: Union[str, numbers.Number, bool]):
176173
"""Adds annotation to existing segment or subsegment
@@ -239,9 +236,9 @@ def patch(self, modules: Optional[Sequence[str]] = None):
239236
return
240237

241238
if modules is None:
242-
aws_xray_sdk.core.patch_all()
239+
self.provider.patch_all()
243240
else:
244-
aws_xray_sdk.core.patch(modules)
241+
self.provider.patch(modules)
245242

246243
def capture_lambda_handler(
247244
self,
@@ -310,6 +307,9 @@ def decorate(event, context, **kwargs):
310307
if is_cold_start:
311308
is_cold_start = False
312309

310+
if self.service:
311+
subsegment.put_annotation(key="Service", value=self.service)
312+
313313
try:
314314
logger.debug("Calling lambda handler")
315315
response = lambda_handler(event, context, **kwargs)
@@ -743,7 +743,8 @@ def __build_config(
743743
is_disabled = disabled if disabled is not None else self._is_tracer_disabled()
744744
is_service = resolve_env_var_choice(choice=service, env=os.getenv(constants.SERVICE_NAME_ENV))
745745

746-
self._config["provider"] = provider or self._config["provider"] or aws_xray_sdk.core.xray_recorder
746+
# Logic: Choose overridden option first, previously cached config, or default if available
747+
self._config["provider"] = provider or self._config["provider"] or self._patch_xray_provider()
747748
self._config["auto_patch"] = auto_patch if auto_patch is not None else self._config["auto_patch"]
748749
self._config["service"] = is_service or self._config["service"]
749750
self._config["disabled"] = is_disabled or self._config["disabled"]
@@ -752,3 +753,28 @@ def __build_config(
752753
@classmethod
753754
def _reset_config(cls):
754755
cls._config = copy.copy(cls._default_config)
756+
757+
def _patch_xray_provider(self):
758+
# Due to Lazy Import, we need to activate `core` attrib via import
759+
# we also need to include `patch`, `patch_all` methods
760+
# to ensure patch calls are done via the provider
761+
from aws_xray_sdk.core import xray_recorder
762+
763+
provider = xray_recorder
764+
provider.patch = aws_xray_sdk.core.patch
765+
provider.patch_all = aws_xray_sdk.core.patch_all
766+
767+
return provider
768+
769+
def _disable_xray_trace_batching(self):
770+
"""Configure X-Ray SDK to send subsegment individually over batching
771+
Known issue: https://github.com/awslabs/aws-lambda-powertools-python/issues/283
772+
"""
773+
if self.disabled:
774+
logger.debug("Tracing has been disabled, aborting streaming override")
775+
return
776+
777+
aws_xray_sdk.core.xray_recorder.configure(streaming_threshold=0)
778+
779+
def _is_xray_provider(self):
780+
return "aws_xray_sdk" in self.provider.__module__

Diff for: docs/core/tracer.md

+1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ You can quickly start by importing the `Tracer` class, initialize it outside the
5858
When using this `capture_lambda_handler` decorator, Tracer performs these additional tasks to ease operations:
5959

6060
* Creates a `ColdStart` annotation to easily filter traces that have had an initialization overhead
61+
* Creates a `Service` annotation if `service` parameter or `POWERTOOLS_SERVICE_NAME` is set
6162
* Captures any response, or full exceptions generated by the handler, and include as tracing metadata
6263

6364
### Annotations & Metadata

Diff for: tests/unit/test_tracing.py

+40-2
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ def in_subsegment(self, *args, **kwargs):
4444
def patch(self, *args, **kwargs):
4545
return self.patch_mock(*args, **kwargs)
4646

47+
def patch_all(self):
48+
...
49+
4750
return CustomProvider
4851

4952

@@ -586,7 +589,42 @@ def handler(event, context):
586589
handler({}, mocker.MagicMock())
587590

588591
# THEN
589-
assert in_subsegment_mock.put_annotation.call_args == mocker.call(key="ColdStart", value=True)
592+
assert in_subsegment_mock.put_annotation.call_args_list[0] == mocker.call(key="ColdStart", value=True)
593+
594+
handler({}, mocker.MagicMock())
595+
assert in_subsegment_mock.put_annotation.call_args_list[2] == mocker.call(key="ColdStart", value=False)
596+
597+
598+
def test_tracer_lambda_handler_add_service_annotation(mocker, dummy_response, provider_stub, in_subsegment_mock):
599+
# GIVEN
600+
provider = provider_stub(in_subsegment=in_subsegment_mock.in_subsegment)
601+
tracer = Tracer(provider=provider, service="booking")
602+
603+
# WHEN
604+
@tracer.capture_lambda_handler
605+
def handler(event, context):
606+
return dummy_response
590607

591608
handler({}, mocker.MagicMock())
592-
assert in_subsegment_mock.put_annotation.call_args == mocker.call(key="ColdStart", value=False)
609+
610+
# THEN
611+
assert in_subsegment_mock.put_annotation.call_args == mocker.call(key="Service", value="booking")
612+
613+
614+
def test_tracer_lambda_handler_do_not_add_service_annotation_when_missing(
615+
mocker, dummy_response, provider_stub, in_subsegment_mock
616+
):
617+
# GIVEN
618+
provider = provider_stub(in_subsegment=in_subsegment_mock.in_subsegment)
619+
tracer = Tracer(provider=provider)
620+
621+
# WHEN
622+
@tracer.capture_lambda_handler
623+
def handler(event, context):
624+
return dummy_response
625+
626+
handler({}, mocker.MagicMock())
627+
628+
# THEN
629+
assert in_subsegment_mock.put_annotation.call_count == 1
630+
assert in_subsegment_mock.put_annotation.call_args == mocker.call(key="ColdStart", value=True)

0 commit comments

Comments
 (0)