diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c6457126..36c2d4a5 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,6 +5,14 @@ CHANGELOG Unreleased ========== +2.13.1 +========== +* improvement: Bump idna from 3.6 to 3.7 in /sample-apps/flask `https://github.com/aws/aws-xray-sdk-python/pull/425` +* bugfix: Fix end_time param type docstring from int to float `https://github.com/aws/aws-xray-sdk-python/pull/426` +* improvement: Bump werkzeug from 3.0.1 to 3.0.3 in /sample-apps/flask `https://github.com/aws/aws-xray-sdk-python/pull/428` +* improvement: [LambdaContext] Create dummy segment when trace header is incomplete `https://github.com/aws/aws-xray-sdk-python/pull/429` +* bugfix: [LambdaContext] Fix logging to only happen inside lambda function `https://github.com/aws/aws-xray-sdk-python/pull/431` + 2.13.0 ========== * bugfix: Fix passing multiple values in testenv.passenv in tox.ini `https://github.com/aws/aws-xray-sdk-python/pull/399` diff --git a/aws_xray_sdk/core/context.py b/aws_xray_sdk/core/context.py index 63e994de..553c85a2 100644 --- a/aws_xray_sdk/core/context.py +++ b/aws_xray_sdk/core/context.py @@ -44,7 +44,7 @@ def end_segment(self, end_time=None): """ End the current active segment. - :param int end_time: epoch in seconds. If not specified the current + :param float end_time: epoch in seconds. If not specified the current system time will be used. """ entity = self.get_trace_entity() @@ -75,7 +75,7 @@ def end_subsegment(self, end_time=None): End the current active segment. Return False if there is no subsegment to end. - :param int end_time: epoch in seconds. If not specified the current + :param float end_time: epoch in seconds. If not specified the current system time will be used. """ subsegment = self.get_trace_entity() diff --git a/aws_xray_sdk/core/lambda_launcher.py b/aws_xray_sdk/core/lambda_launcher.py index adfb50eb..ef6f8986 100644 --- a/aws_xray_sdk/core/lambda_launcher.py +++ b/aws_xray_sdk/core/lambda_launcher.py @@ -3,6 +3,7 @@ import threading from aws_xray_sdk import global_sdk_config +from .models.dummy_entities import DummySegment from .models.facade_segment import FacadeSegment from .models.trace_header import TraceHeader from .context import Context @@ -44,7 +45,7 @@ class LambdaContext(Context): """ Lambda service will generate a segment for each function invocation which cannot be mutated. The context doesn't keep any manually created segment - but instead every time ``get_trace_entity()`` gets called it refresh the facade + but instead every time ``get_trace_entity()`` gets called it refresh the segment based on environment variables set by Lambda worker. """ def __init__(self): @@ -65,13 +66,13 @@ def end_segment(self, end_time=None): def put_subsegment(self, subsegment): """ - Refresh the facade segment every time this function is invoked to prevent + Refresh the segment every time this function is invoked to prevent a new subsegment from being attached to a leaked segment/subsegment. """ current_entity = self.get_trace_entity() - if not self._is_subsegment(current_entity) and current_entity.initializing: - if global_sdk_config.sdk_enabled(): + if not self._is_subsegment(current_entity) and (getattr(current_entity, 'initializing', None) or isinstance(current_entity, DummySegment)): + if global_sdk_config.sdk_enabled() and not os.getenv(LAMBDA_TRACE_HEADER_KEY): log.warning("Subsegment %s discarded due to Lambda worker still initializing" % subsegment.name) return @@ -99,9 +100,9 @@ def get_trace_entity(self): def _refresh_context(self): """ - Get current facade segment. To prevent resource leaking in Lambda worker, + Get current segment. To prevent resource leaking in Lambda worker, every time there is segment present, we compare its trace id to current - environment variables. If it is different we create a new facade segment + environment variables. If it is different we create a new segment and clean up subsegments stored. """ header_str = os.getenv(LAMBDA_TRACE_HEADER_KEY) @@ -136,8 +137,8 @@ def handle_context_missing(self): def _initialize_context(self, trace_header): """ - Create a facade segment based on environment variables - set by AWS Lambda and initialize storage for subsegments. + Create a segment based on environment variables set by + AWS Lambda and initialize storage for subsegments. """ sampled = None if not global_sdk_config.sdk_enabled(): @@ -148,12 +149,17 @@ def _initialize_context(self, trace_header): elif trace_header.sampled == 1: sampled = True - segment = FacadeSegment( - name='facade', - traceid=trace_header.root, - entityid=trace_header.parent, - sampled=sampled, - ) + segment = None + if not trace_header.root or not trace_header.parent or trace_header.sampled is None: + segment = DummySegment() + log.debug("Creating NoOp/Dummy parent segment") + else: + segment = FacadeSegment( + name='facade', + traceid=trace_header.root, + entityid=trace_header.parent, + sampled=sampled, + ) segment.save_origin_trace_header(trace_header) setattr(self._local, 'segment', segment) setattr(self._local, 'entities', []) diff --git a/aws_xray_sdk/core/models/entity.py b/aws_xray_sdk/core/models/entity.py index a2150a5e..9a5d08e7 100644 --- a/aws_xray_sdk/core/models/entity.py +++ b/aws_xray_sdk/core/models/entity.py @@ -64,7 +64,7 @@ def close(self, end_time=None): Close the trace entity by setting `end_time` and flip the in progress flag to False. - :param int end_time: Epoch in seconds. If not specified + :param float end_time: Epoch in seconds. If not specified current time will be used. """ self._check_ended() diff --git a/aws_xray_sdk/core/models/subsegment.py b/aws_xray_sdk/core/models/subsegment.py index 3c4289e9..53f944de 100644 --- a/aws_xray_sdk/core/models/subsegment.py +++ b/aws_xray_sdk/core/models/subsegment.py @@ -133,7 +133,7 @@ def close(self, end_time=None): and flip the in progress flag to False. Also decrement parent segment's ref counter by 1. - :param int end_time: Epoch in seconds. If not specified + :param float end_time: Epoch in seconds. If not specified current time will be used. """ super().close(end_time) diff --git a/aws_xray_sdk/version.py b/aws_xray_sdk/version.py index 8ef56164..19502509 100644 --- a/aws_xray_sdk/version.py +++ b/aws_xray_sdk/version.py @@ -1 +1 @@ -VERSION = '2.13.0' +VERSION = '2.13.1' diff --git a/docs/conf.py b/docs/conf.py index cff4885e..52b40c41 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -62,9 +62,9 @@ # built documents. # # The short X.Y version. -version = u'2.13.0' +version = u'2.13.1' # The full version, including alpha/beta/rc tags. -release = u'2.13.0' +release = u'2.13.1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/sample-apps/flask/requirements.txt b/sample-apps/flask/requirements.txt index 06765afd..a926a76e 100644 --- a/sample-apps/flask/requirements.txt +++ b/sample-apps/flask/requirements.txt @@ -2,10 +2,10 @@ boto3==1.34.26 certifi==2023.11.17 chardet==5.2.0 Flask==2.3.3 -idna==3.6 +idna==3.7 requests==2.31.0 urllib3==1.26.18 -Werkzeug==3.0.1 +Werkzeug==3.0.3 flask-sqlalchemy==2.5.1 SQLAlchemy==1.4 aws_xray_sdk==2.6.0 diff --git a/tests/test_lambda_context.py b/tests/test_lambda_context.py index 92483b04..b7299c72 100644 --- a/tests/test_lambda_context.py +++ b/tests/test_lambda_context.py @@ -3,6 +3,7 @@ from aws_xray_sdk import global_sdk_config import pytest from aws_xray_sdk.core import lambda_launcher +from aws_xray_sdk.core.models.dummy_entities import DummySegment from aws_xray_sdk.core.models.subsegment import Subsegment @@ -67,23 +68,48 @@ def test_disable(): def test_non_initialized(): - # Context that hasn't been initialized by lambda container should not add subsegments to the facade segment. + # Context that hasn't been initialized by lambda container should not add subsegments to the dummy segment. temp_header_var = os.environ[lambda_launcher.LAMBDA_TRACE_HEADER_KEY] del os.environ[lambda_launcher.LAMBDA_TRACE_HEADER_KEY] temp_context = lambda_launcher.LambdaContext() - facade_segment = temp_context.get_trace_entity() - subsegment = Subsegment("TestSubsegment", "local", facade_segment) + dummy_segment = temp_context.get_trace_entity() + subsegment = Subsegment("TestSubsegment", "local", dummy_segment) temp_context.put_subsegment(subsegment) - assert temp_context.get_trace_entity() == facade_segment + assert temp_context.get_trace_entity() == dummy_segment # "Lambda" container added metadata now. Should see subsegment now. + # The following put_segment call will overwrite the dummy segment in the context with an intialized facade segment that accepts a subsegment. os.environ[lambda_launcher.LAMBDA_TRACE_HEADER_KEY] = temp_header_var temp_context.put_subsegment(subsegment) assert temp_context.get_trace_entity() == subsegment +def test_lambda_passthrough(): + # Hold previous environment value + temp_header_var = os.environ[lambda_launcher.LAMBDA_TRACE_HEADER_KEY] + del os.environ[lambda_launcher.LAMBDA_TRACE_HEADER_KEY] + + # Set header to lambda passthrough style header + os.environ[lambda_launcher.LAMBDA_TRACE_HEADER_KEY] = "Root=%s;Lineage=10:1234abcd:3" % TRACE_ID + + temp_context = lambda_launcher.LambdaContext() + dummy_segment = temp_context.get_trace_entity() + subsegment = Subsegment("TestSubsegment", "local", dummy_segment) + temp_context.put_subsegment(subsegment) + + # Resulting entity is not the same dummy segment, so simply check that it is a dummy segment + assert isinstance(temp_context.get_trace_entity(), DummySegment) + + # Reset header value and ensure behaviour returns to normal + del os.environ[lambda_launcher.LAMBDA_TRACE_HEADER_KEY] + os.environ[lambda_launcher.LAMBDA_TRACE_HEADER_KEY] = temp_header_var + temp_context.put_subsegment(subsegment) + + assert temp_context.get_trace_entity() == subsegment + + def test_set_trace_entity(): segment = context.get_trace_entity() diff --git a/tox.ini b/tox.ini index c5d7875e..7dfc1b9b 100644 --- a/tox.ini +++ b/tox.ini @@ -56,7 +56,7 @@ deps = wrapt ; Python 3.5+ only deps - py{37,38,39,310,311,312}: pytest-asyncio == 0.21.1 + py{37,38,39,310,311,312}: pytest-asyncio == 0.21.2 ; For pkg_resources py{37,38,39,310,311,312}: setuptools