From 37360e82f9562407772ca4ef3fd544899af4bc47 Mon Sep 17 00:00:00 2001 From: Wu Date: Wed, 28 Mar 2018 11:43:28 -0700 Subject: [PATCH] releasing 0.97 --- CHANGELOG.rst | 11 +++--- README.md | 9 +++-- aws_xray_sdk/core/async_recorder.py | 34 +++++++++-------- aws_xray_sdk/core/models/entity.py | 6 +-- aws_xray_sdk/core/recorder.py | 38 +++++++++---------- .../ext/sqlalchemy/util/decerators.py | 3 -- docs/conf.py | 4 +- setup.py | 12 ++++-- tests/ext/aiobotocore/test_aiobotocore.py | 28 ++++++++++++++ tests/ext/botocore/test_botocore.py | 2 +- tests/test_recorder.py | 26 +++++++++++++ tests/test_trace_entities.py | 12 ++++++ 12 files changed, 127 insertions(+), 58 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 79a5a2f2..b6cfb3e5 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,14 +2,15 @@ CHANGELOG ========= -unreleased -========== -* feature: Aiohttp client tracing for aiohttp versions > 3. `PR42 `_. +0.97 +==== +* feature: Support aiohttp client tracing for aiohttp 3.x. `PR42 `_. * feature: Use the official middleware pattern for Aiohttp ext. `PR29 `_. -* bugfix: SQLAlcemy plugin would cause warning messages with some db connection strings that contained invalid characters for a segment/subsegment name. * bugfix: Aiohttp middleware serialized URL values incorrectly. `PR37 `_ * bugfix: Don't overwrite plugins list on each `.configure` call. `PR38 `_ -* bugfix: Do not swallow `return_value`. `PR44 `_ +* bugfix: Do not swallow `return_value` when context is missing and `LOG_ERROR` is set. `PR44 `_ +* bugfix: Loose entity name validation. `ISSUE36 `_ +* bugfix: Fix PyPI project page being rendered incorrectly. `ISSUE30 `_ 0.96 ==== diff --git a/README.md b/README.md index 1bcbbe9a..d56eda25 100644 --- a/README.md +++ b/README.md @@ -160,7 +160,10 @@ xray_recorder.configure(service='fallback_name', dynamic_naming='*mysite.com*') XRayMiddleware(app, xray_recorder) ``` -### Add aiohttp middleware +### Working with aiohttp + +Adding aiohttp middleware. Support aiohttp >= 2.3. + ```python from aiohttp import web @@ -176,9 +179,7 @@ app.router.add_get("/", handler) web.run_app(app) ``` -### Trace aiohttp client requests - -Only available using Aiohttp releases greater than 3.X. +Tracing aiohttp client. Support aiohttp >=3. ```python from aws_xray_sdk.ext.aiohttp.client import aws_xray_trace_config diff --git a/aws_xray_sdk/core/async_recorder.py b/aws_xray_sdk/core/async_recorder.py index 74ec2c46..db697f82 100644 --- a/aws_xray_sdk/core/async_recorder.py +++ b/aws_xray_sdk/core/async_recorder.py @@ -50,20 +50,22 @@ async def record_subsegment_async(self, wrapped, instance, args, kwargs, name, stack = traceback.extract_stack(limit=self._max_trace_back) raise finally: - end_time = time.time() - if callable(meta_processor): - meta_processor( - wrapped=wrapped, - instance=instance, - args=args, - kwargs=kwargs, - return_value=return_value, - exception=exception, - subsegment=subsegment, - stack=stack, - ) - elif exception: - if subsegment: - subsegment.add_exception(exception, stack) + # No-op if subsegment is `None` due to `LOG_ERROR`. + if subsegment is not None: + end_time = time.time() + if callable(meta_processor): + meta_processor( + wrapped=wrapped, + instance=instance, + args=args, + kwargs=kwargs, + return_value=return_value, + exception=exception, + subsegment=subsegment, + stack=stack, + ) + elif exception: + if subsegment: + subsegment.add_exception(exception, stack) - self.end_subsegment(end_time) + self.end_subsegment(end_time) diff --git a/aws_xray_sdk/core/models/entity.py b/aws_xray_sdk/core/models/entity.py index 58df0fba..53cf2d5b 100644 --- a/aws_xray_sdk/core/models/entity.py +++ b/aws_xray_sdk/core/models/entity.py @@ -14,8 +14,8 @@ log = logging.getLogger(__name__) -# List of valid characters found at http://docs.aws.amazon.com/xray/latest/devguide/xray-api-segmentdocuments.html -_valid_name_characters = string.ascii_letters + string.digits + '_.:/%&#=+\-@ ' +# Valid characters can be found at http://docs.aws.amazon.com/xray/latest/devguide/xray-api-segmentdocuments.html +_common_invalid_name_characters = '?;*()!$~^<>' _valid_annotation_key_characters = string.ascii_letters + string.digits + '_' @@ -30,7 +30,7 @@ def __init__(self, name): # required attributes self.id = self._generate_random_id() self.name = name - self.name = ''.join([c for c in name if c in _valid_name_characters]) + self.name = ''.join([c for c in name if c not in _common_invalid_name_characters]) self.start_time = time.time() self.parent_id = None diff --git a/aws_xray_sdk/core/recorder.py b/aws_xray_sdk/core/recorder.py index 963cf4b2..c9fe5292 100644 --- a/aws_xray_sdk/core/recorder.py +++ b/aws_xray_sdk/core/recorder.py @@ -335,26 +335,24 @@ def record_subsegment(self, wrapped, instance, args, kwargs, name, raise finally: # No-op if subsegment is `None` due to `LOG_ERROR`. - if subsegment is None: - return return_value - - end_time = time.time() - if callable(meta_processor): - meta_processor( - wrapped=wrapped, - instance=instance, - args=args, - kwargs=kwargs, - return_value=return_value, - exception=exception, - subsegment=subsegment, - stack=stack, - ) - elif exception: - if subsegment: - subsegment.add_exception(exception, stack) - - self.end_subsegment(end_time) + if subsegment is not None: + end_time = time.time() + if callable(meta_processor): + meta_processor( + wrapped=wrapped, + instance=instance, + args=args, + kwargs=kwargs, + return_value=return_value, + exception=exception, + subsegment=subsegment, + stack=stack, + ) + elif exception: + if subsegment: + subsegment.add_exception(exception, stack) + + self.end_subsegment(end_time) def _populate_runtime_context(self, segment): if not self._plugins: diff --git a/aws_xray_sdk/ext/sqlalchemy/util/decerators.py b/aws_xray_sdk/ext/sqlalchemy/util/decerators.py index 01c65e98..872e432a 100644 --- a/aws_xray_sdk/ext/sqlalchemy/util/decerators.py +++ b/aws_xray_sdk/ext/sqlalchemy/util/decerators.py @@ -1,7 +1,6 @@ import re from aws_xray_sdk.core import xray_recorder from aws_xray_sdk.ext.util import strip_url -from aws_xray_sdk.core.models.entity import _valid_name_characters from future.standard_library import install_aliases install_aliases() from urllib.parse import urlparse, uses_netloc @@ -49,8 +48,6 @@ def wrapper(*args, **kw): if getattr(c._local, 'entities', None) is not None: # Strip URL of ? and following text sub_name = strip_url(sql['url']) - # Ensure url has a valid characters. - sub_name = ''.join([c for c in sub_name if c in _valid_name_characters]) subsegment = xray_recorder.begin_subsegment(sub_name, namespace='remote') else: subsegment = None diff --git a/docs/conf.py b/docs/conf.py index 8176cf59..752f9dfb 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -62,9 +62,9 @@ # built documents. # # The short X.Y version. -version = u'0.96' +version = u'0.97' # The full version, including alpha/beta/rc tags. -release = u'0.96' +release = u'0.97' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/setup.py b/setup.py index 1e51c284..e0e2bef9 100644 --- a/setup.py +++ b/setup.py @@ -1,15 +1,19 @@ from setuptools import setup, find_packages from os import path -import codecs CURRENT_DIR = path.abspath(path.dirname(__file__)) -with codecs.open(path.join(CURRENT_DIR, 'README.md'), encoding='utf-8') as f: - long_description = f.read() +try: + from pypandoc import convert + read_md = lambda f: convert(f, 'rst') +except ImportError: + read_md = lambda f: open(f, 'r').read() + +long_description = read_md(path.join(CURRENT_DIR, 'README.md')) setup( name='aws-xray-sdk', - version='0.96', + version='0.97', 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/aiobotocore/test_aiobotocore.py b/tests/ext/aiobotocore/test_aiobotocore.py index bd040aec..b999b30d 100644 --- a/tests/ext/aiobotocore/test_aiobotocore.py +++ b/tests/ext/aiobotocore/test_aiobotocore.py @@ -2,6 +2,7 @@ import aiobotocore from botocore.stub import Stubber, ANY +from botocore.exceptions import ClientError from aws_xray_sdk.core import patch from aws_xray_sdk.core.async_context import AsyncContext @@ -103,3 +104,30 @@ async def test_map_parameter_grouping(loop, recorder): aws_meta = subsegment.aws assert sorted(aws_meta['table_names']) == ['table1', 'table2'] + + +async def test_context_missing_not_swallow_return(loop, recorder): + xray_recorder.configure(service='test', sampling=False, + context=AsyncContext(loop=loop), context_missing='LOG_ERROR') + + response = {'ResponseMetadata': {'RequestId': '1234', 'HTTPStatusCode': 403}} + + session = aiobotocore.get_session(loop=loop) + async with session.create_client('dynamodb', region_name='eu-west-2') as client: + with Stubber(client) as stubber: + stubber.add_response('describe_table', response, {'TableName': 'mytable'}) + actual_resp = await client.describe_table(TableName='mytable') + + assert actual_resp == response + + +async def test_context_missing_not_suppress_exception(loop, recorder): + xray_recorder.configure(service='test', sampling=False, + context=AsyncContext(loop=loop), context_missing='LOG_ERROR') + + session = aiobotocore.get_session(loop=loop) + async with session.create_client('dynamodb', region_name='eu-west-2') as client: + with Stubber(client) as stubber: + stubber.add_client_error('describe_table', expected_params={'TableName': ANY}) + with pytest.raises(ClientError): + await client.describe_table(TableName='mytable') diff --git a/tests/ext/botocore/test_botocore.py b/tests/ext/botocore/test_botocore.py index 2e064c0b..e0cdbcff 100644 --- a/tests/ext/botocore/test_botocore.py +++ b/tests/ext/botocore/test_botocore.py @@ -123,6 +123,6 @@ def test_pass_through_on_context_missing(): with Stubber(ddb) as stubber: stubber.add_response('describe_table', response, {'TableName': 'mytable'}) result = ddb.describe_table(TableName='mytable') - assert result is not None + assert result is not None xray_recorder.configure(context_missing='RUNTIME_ERROR') diff --git a/tests/test_recorder.py b/tests/test_recorder.py index 16c58126..76e8a279 100644 --- a/tests/test_recorder.py +++ b/tests/test_recorder.py @@ -1,3 +1,4 @@ +import pytest from .util import get_new_stubbed_recorder @@ -40,3 +41,28 @@ def test_subsegments_streaming(): assert segment.get_total_subsegments_size() == 10 assert xray_recorder.current_subsegment().name == '9' + + +def test_capture_not_suppress_exception(): + xray_recorder = get_new_stubbed_recorder() + xray_recorder.configure(sampling=False, context_missing='LOG_ERROR') + + @xray_recorder.capture() + def buggy_func(): + return 1 / 0 + + with pytest.raises(ZeroDivisionError): + buggy_func() + + +def test_capture_not_swallow_return(): + xray_recorder = get_new_stubbed_recorder() + xray_recorder.configure(sampling=False, context_missing='LOG_ERROR') + value = 1 + + @xray_recorder.capture() + def my_func(): + return value + + actual = my_func() + assert actual == value diff --git a/tests/test_trace_entities.py b/tests/test_trace_entities.py index 7c6b326f..07aff9a7 100644 --- a/tests/test_trace_entities.py +++ b/tests/test_trace_entities.py @@ -1,3 +1,4 @@ +# -*- coding: iso-8859-15 -*- import pytest from aws_xray_sdk.core.models.segment import Segment @@ -10,6 +11,17 @@ from .util import entity_to_dict +def test_unicode_entity_name(): + + name1 = u'福' + name2 = u'セツナ' + segment = Segment(name1) + subsegment = Subsegment(name2, 'local', segment) + + assert segment.name == name1 + assert subsegment.name == name2 + + def test_put_http_meta(): segment = Segment('seg')