From c374fd825710c27f6a292441b2212cd567300944 Mon Sep 17 00:00:00 2001 From: Wu Date: Thu, 1 Mar 2018 11:23:34 -0800 Subject: [PATCH] release commit for 0.96 --- CHANGELOG.rst | 8 +++++--- aws_xray_sdk/core/recorder.py | 21 +++++++++++++++++++-- aws_xray_sdk/ext/django/apps.py | 1 + aws_xray_sdk/ext/django/conf.py | 1 + aws_xray_sdk/ext/httplib/patch.py | 24 ++++++++++++------------ docs/frameworks.rst | 1 + setup.py | 2 +- tests/ext/botocore/test_botocore.py | 22 ++++++++++++++++++++++ tests/test_recorder.py | 9 +++++---- 9 files changed, 67 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d8eb4fdd..441274f3 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,13 +2,15 @@ CHANGELOG ========= -Unreleased -========== +0.96 +==== * feature: Add support for SQLAlchemy and Flask-SQLAlcemy. `PR14 `_. * feature: Add support for PynamoDB calls to DynamoDB. `PR13 `_. -* feature: Add support for httplib calls `PR19 `_. +* feature: Add support for httplib calls. `PR19 `_. +* feature: Make streaming threshold configurable through public interface. `ISSUE21 `_. * bugfix: Drop invalid annotation keys and log a warning. `PR22 `_. * bugfix: Respect `with` statement on cursor objects in dbapi2 patcher. `PR17 `_. +* bugfix: Don't throw error from built in subsegment capture when `LOG_ERROR` is set. `ISSUE4 `_. 0.95 ==== diff --git a/aws_xray_sdk/core/recorder.py b/aws_xray_sdk/core/recorder.py index 86b03daf..ac84ccbb 100644 --- a/aws_xray_sdk/core/recorder.py +++ b/aws_xray_sdk/core/recorder.py @@ -57,7 +57,7 @@ def configure(self, sampling=None, plugins=None, context_missing=None, sampling_rules=None, daemon_address=None, service=None, context=None, emitter=None, - dynamic_naming=None): + dynamic_naming=None, streaming_threshold=None): """Configure global X-Ray recorder. Configure needs to run before patching thrid party libraries @@ -90,6 +90,9 @@ def configure(self, sampling=None, plugins=None, :param dynamic_naming: a string that defines a pattern that host names should match. Alternatively you can pass a module which overrides ``DefaultDynamicNaming`` module. + :param streaming_threshold: If breaks within a single segment it will + start streaming out children subsegments. By default it is the + maximum number of subsegments within a segment. Environment variables AWS_XRAY_DAEMON_ADDRESS, AWS_XRAY_CONTEXT_MISSING and AWS_XRAY_TRACING_NAME respectively overrides arguments @@ -111,6 +114,8 @@ def configure(self, sampling=None, plugins=None, self.context.context_missing = os.getenv(CONTEXT_MISSING_KEY, context_missing) if dynamic_naming: self.dynamic_naming = dynamic_naming + if streaming_threshold: + self.streaming_threshold = streaming_threshold plugin_modules = None if plugins: @@ -259,7 +264,7 @@ def stream_subsegments(self): if not segment or not segment.sampled: return - if segment.get_total_subsegments_size() <= self._max_subsegments: + if segment.get_total_subsegments_size() <= self.streaming_threshold: return # find all subsegments that has no open child subsegments and @@ -326,6 +331,10 @@ def record_subsegment(self, wrapped, instance, args, kwargs, name, stack = traceback.extract_stack(limit=self._max_trace_back) raise finally: + # No-op if subsegment is `None` due to `LOG_ERROR`. + if subsegment is None: + return + end_time = time.time() if callable(meta_processor): meta_processor( @@ -440,3 +449,11 @@ def emitter(self): @emitter.setter def emitter(self, value): self._emitter = value + + @property + def streaming_threshold(self): + return self._max_subsegments + + @streaming_threshold.setter + def streaming_threshold(self, value): + self._max_subsegments = value diff --git a/aws_xray_sdk/ext/django/apps.py b/aws_xray_sdk/ext/django/apps.py index 03392e43..9de55525 100644 --- a/aws_xray_sdk/ext/django/apps.py +++ b/aws_xray_sdk/ext/django/apps.py @@ -34,6 +34,7 @@ def ready(self): plugins=settings.PLUGINS, service=settings.AWS_XRAY_TRACING_NAME, dynamic_naming=settings.DYNAMIC_NAMING, + streaming_threshold=settings.STREAMING_THRESHOLD, ) # if turned on subsegment will be generated on diff --git a/aws_xray_sdk/ext/django/conf.py b/aws_xray_sdk/ext/django/conf.py index 5baa0c23..4321f4e9 100644 --- a/aws_xray_sdk/ext/django/conf.py +++ b/aws_xray_sdk/ext/django/conf.py @@ -12,6 +12,7 @@ 'SAMPLING_RULES': None, 'AWS_XRAY_TRACING_NAME': None, 'DYNAMIC_NAMING': None, + 'STREAMING_THRESHOLD': None, } XRAY_NAMESPACE = 'XRAY_RECORDER' diff --git a/aws_xray_sdk/ext/httplib/patch.py b/aws_xray_sdk/ext/httplib/patch.py index 55b34bf6..e33ad18c 100644 --- a/aws_xray_sdk/ext/httplib/patch.py +++ b/aws_xray_sdk/ext/httplib/patch.py @@ -20,6 +20,8 @@ _XRAY_PROP = '_xray_prop' _XRay_Data = namedtuple('xray_data', ['method', 'host', 'url']) +# A flag indicates whether this module is X-Ray patched or not +PATCH_FLAG = '__xray_patched' def http_response_processor(wrapped, instance, args, kwargs, return_value, @@ -113,11 +115,10 @@ def _xray_traced_http_client_read(wrapped, instance, args, kwargs): def patch(): """ patch the built-in urllib/httplib/httplib.client methods for tracing""" - - # we set an attribute to avoid double unwrapping - if getattr(httplib, '__xray_patch', False): + if getattr(httplib, PATCH_FLAG, False): return - setattr(httplib, '__xray_patch', True) + # we set an attribute to avoid multiple wrapping + setattr(httplib, PATCH_FLAG, True) wrapt.wrap_function_wrapper( httplib_client_module, @@ -139,13 +140,12 @@ def patch(): def unpatch(): - """ unpatch any previously patched modules """ - if not getattr(httplib, '__xray_patch', False): - return - setattr(httplib, '__xray_patch', False) - - # send_request encapsulates putrequest, putheader[s], and endheaders - # NOTE that requests + """ + Unpatch any previously patched modules. + This operation is idempotent. + """ + setattr(httplib, PATCH_FLAG, False) + # _send_request encapsulates putrequest, putheader[s], and endheaders unwrap(httplib.HTTPConnection, '_send_request') unwrap(httplib.HTTPConnection, 'getresponse') - unwrap(httplib.HTTPConnection, 'read') + unwrap(httplib.HTTPResponse, 'read') diff --git a/docs/frameworks.rst b/docs/frameworks.rst index 09bd2b71..e04a7369 100644 --- a/docs/frameworks.rst +++ b/docs/frameworks.rst @@ -45,6 +45,7 @@ The default values are as follows:: 'SAMPLING_RULES': None, 'AWS_XRAY_TRACING_NAME': None, # the segment name for segments generated from incoming requests 'DYNAMIC_NAMING': None, # defines a pattern that host names should match + 'STREAMING_THRESHOLD': None, # defines when a segment starts to stream out its children subsegments } Environment variables have higher precedence over user settings. diff --git a/setup.py b/setup.py index a4ceff35..1e51c284 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ setup( name='aws-xray-sdk', - version='0.95', + version='0.96', description='The AWS X-Ray SDK for Python (the SDK) enables Python developers to record' ' and emit information from within their applications to the AWS X-Ray service.', diff --git a/tests/ext/botocore/test_botocore.py b/tests/ext/botocore/test_botocore.py index a2d30a72..b9dd9c79 100644 --- a/tests/ext/botocore/test_botocore.py +++ b/tests/ext/botocore/test_botocore.py @@ -103,3 +103,25 @@ def test_map_parameter_grouping(): aws_meta = subsegment.aws assert sorted(aws_meta['table_names']) == ['table1', 'table2'] + +def test_pass_through_on_context_missing(): + """ + The built-in patcher or subsegment capture logic should not throw + any error when a `None` subsegment created from `LOG_ERROR` missing context. + """ + xray_recorder.configure(context_missing='LOG_ERROR') + xray_recorder.clear_trace_entities() + + ddb = session.create_client('dynamodb', region_name='us-west-2') + response = { + 'ResponseMetadata': { + 'RequestId': REQUEST_ID, + 'HTTPStatusCode': 200, + } + } + + with Stubber(ddb) as stubber: + stubber.add_response('describe_table', response, {'TableName': 'mytable'}) + ddb.describe_table(TableName='mytable') + + xray_recorder.configure(context_missing='RUNTIME_ERROR') diff --git a/tests/test_recorder.py b/tests/test_recorder.py index 24746293..16c58126 100644 --- a/tests/test_recorder.py +++ b/tests/test_recorder.py @@ -30,12 +30,13 @@ def test_subsegment_parenting(): def test_subsegments_streaming(): - + xray_recorder.configure(streaming_threshold=10) segment = xray_recorder.begin_segment('name') - for i in range(0, 50): + for i in range(0, 11): xray_recorder.begin_subsegment(name=str(i)) - for i in range(0, 40): + for i in range(0, 1): + # subsegment '10' will be streamed out upon close xray_recorder.end_subsegment() - assert segment.get_total_subsegments_size() < 50 + assert segment.get_total_subsegments_size() == 10 assert xray_recorder.current_subsegment().name == '9'