-
Notifications
You must be signed in to change notification settings - Fork 143
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support fot Aiohttp Client tracing, aiohttp> 3.0.0 (#42)
* Support fot Aiohttp Client tracing, aiohttp> 3.0.0 New `aws_xray_trace_config()` function to retrieve a `aiohttp.TraceConfig` object ready to be used in any `ClientSession`, once the Aiohttp Client session is instantiated with this trace config all sampled HTTP calls will be traced as subsegements and the data properly uploaded. * Removed print * Aiohttp3 dependencie greter or equal than 3.0.0 * Force namespace to remote, use local for local exceptions * Added new feature to CHANGELOG * Added py35 as valid environment for aiohttp3 * Added Aiohttp client trace config usage in the README
- Loading branch information
1 parent
c354f6a
commit 9ff8539
Showing
10 changed files
with
268 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
""" | ||
AioHttp Client tracing, only compatible with Aiohttp 3.X versions | ||
""" | ||
import aiohttp | ||
import traceback | ||
|
||
from types import SimpleNamespace | ||
|
||
from aws_xray_sdk.core import xray_recorder | ||
from aws_xray_sdk.core.models import http | ||
from aws_xray_sdk.ext.util import inject_trace_header, strip_url | ||
|
||
# All aiohttp calls will entail outgoing HTTP requests, only in some ad-hoc | ||
# exceptions the namespace will be flip back to local. | ||
REMOTE_NAMESPACE = 'remote' | ||
LOCAL_NAMESPACE = 'local' | ||
LOCAL_EXCEPTIONS = ( | ||
aiohttp.client_exceptions.ClientConnectionError, | ||
# DNS issues | ||
OSError | ||
) | ||
|
||
|
||
async def begin_subsegment(session, trace_config_ctx, params): | ||
name = trace_config_ctx.name if trace_config_ctx.name else strip_url(str(params.url)) | ||
subsegment = xray_recorder.begin_subsegment(name, REMOTE_NAMESPACE) | ||
subsegment.put_http_meta(http.METHOD, params.method) | ||
subsegment.put_http_meta(http.URL, params.url.human_repr()) | ||
inject_trace_header(params.headers, subsegment) | ||
|
||
|
||
async def end_subsegment(session, trace_config_ctx, params): | ||
subsegment = xray_recorder.current_subsegment() | ||
subsegment.put_http_meta(http.STATUS, params.response.status) | ||
xray_recorder.end_subsegment() | ||
|
||
|
||
async def end_subsegment_with_exception(session, trace_config_ctx, params): | ||
subsegment = xray_recorder.current_subsegment() | ||
subsegment.add_exception( | ||
params.exception, | ||
traceback.extract_stack(limit=xray_recorder._max_trace_back) | ||
) | ||
|
||
if isinstance(params.exception, LOCAL_EXCEPTIONS): | ||
subsegment.namespace = LOCAL_NAMESPACE | ||
|
||
xray_recorder.end_subsegment() | ||
|
||
|
||
def aws_xray_trace_config(name=None): | ||
""" | ||
:param name: name used to identify the subsegment, with None internally the URL will | ||
be used as identifier. | ||
:returns: TraceConfig. | ||
""" | ||
trace_config = aiohttp.TraceConfig( | ||
trace_config_ctx_factory=lambda trace_request_ctx: SimpleNamespace(name=name, | ||
trace_request_ctx=trace_request_ctx) | ||
) | ||
trace_config.on_request_start.append(begin_subsegment) | ||
trace_config.on_request_end.append(end_subsegment) | ||
trace_config.on_request_exception.append(end_subsegment_with_exception) | ||
return trace_config |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
import pytest | ||
from aiohttp import ClientSession | ||
|
||
from aws_xray_sdk.core import xray_recorder | ||
from aws_xray_sdk.core.async_context import AsyncContext | ||
from aws_xray_sdk.ext.util import strip_url | ||
from aws_xray_sdk.ext.aiohttp.client import aws_xray_trace_config | ||
from aws_xray_sdk.ext.aiohttp.client import REMOTE_NAMESPACE, LOCAL_NAMESPACE | ||
|
||
|
||
# httpbin.org is created by the same author of requests to make testing http easy. | ||
BASE_URL = 'httpbin.org' | ||
|
||
|
||
@pytest.fixture(scope='function') | ||
def recorder(loop): | ||
""" | ||
Initiate a recorder and clear it up once has been used. | ||
""" | ||
xray_recorder.configure(service='test', sampling=False, context=AsyncContext(loop=loop)) | ||
xray_recorder.clear_trace_entities() | ||
yield recorder | ||
xray_recorder.clear_trace_entities() | ||
|
||
|
||
async def test_ok(loop, recorder): | ||
xray_recorder.begin_segment('name') | ||
trace_config = aws_xray_trace_config() | ||
status_code = 200 | ||
url = 'http://{}/status/{}?foo=bar'.format(BASE_URL, status_code) | ||
async with ClientSession(loop=loop, trace_configs=[trace_config]) as session: | ||
async with session.get(url): | ||
pass | ||
|
||
subsegment = xray_recorder.current_segment().subsegments[0] | ||
assert subsegment.name == strip_url(url) | ||
assert subsegment.namespace == REMOTE_NAMESPACE | ||
|
||
http_meta = subsegment.http | ||
assert http_meta['request']['url'] == url | ||
assert http_meta['request']['method'] == 'GET' | ||
assert http_meta['response']['status'] == status_code | ||
|
||
|
||
async def test_ok_name(loop, recorder): | ||
xray_recorder.begin_segment('name') | ||
trace_config = aws_xray_trace_config(name='test') | ||
status_code = 200 | ||
url = 'http://{}/status/{}?foo=bar'.format(BASE_URL, status_code) | ||
async with ClientSession(loop=loop, trace_configs=[trace_config]) as session: | ||
async with session.get(url): | ||
pass | ||
|
||
subsegment = xray_recorder.current_segment().subsegments[0] | ||
assert subsegment.name == 'test' | ||
|
||
|
||
async def test_error(loop, recorder): | ||
xray_recorder.begin_segment('name') | ||
trace_config = aws_xray_trace_config() | ||
status_code = 400 | ||
url = 'http://{}/status/{}'.format(BASE_URL, status_code) | ||
async with ClientSession(loop=loop, trace_configs=[trace_config]) as session: | ||
async with session.post(url): | ||
pass | ||
|
||
subsegment = xray_recorder.current_segment().subsegments[0] | ||
assert subsegment.name == url | ||
assert subsegment.error | ||
|
||
http_meta = subsegment.http | ||
assert http_meta['request']['url'] == url | ||
assert http_meta['request']['method'] == 'POST' | ||
assert http_meta['response']['status'] == status_code | ||
|
||
|
||
async def test_throttle(loop, recorder): | ||
xray_recorder.begin_segment('name') | ||
trace_config = aws_xray_trace_config() | ||
status_code = 429 | ||
url = 'http://{}/status/{}'.format(BASE_URL, status_code) | ||
async with ClientSession(loop=loop, trace_configs=[trace_config]) as session: | ||
async with session.head(url): | ||
pass | ||
|
||
subsegment = xray_recorder.current_segment().subsegments[0] | ||
assert subsegment.name == url | ||
assert subsegment.error | ||
assert subsegment.throttle | ||
|
||
http_meta = subsegment.http | ||
assert http_meta['request']['url'] == url | ||
assert http_meta['request']['method'] == 'HEAD' | ||
assert http_meta['response']['status'] == status_code | ||
|
||
|
||
async def test_fault(loop, recorder): | ||
xray_recorder.begin_segment('name') | ||
trace_config = aws_xray_trace_config() | ||
status_code = 500 | ||
url = 'http://{}/status/{}'.format(BASE_URL, status_code) | ||
async with ClientSession(loop=loop, trace_configs=[trace_config]) as session: | ||
async with session.put(url): | ||
pass | ||
|
||
subsegment = xray_recorder.current_segment().subsegments[0] | ||
assert subsegment.name == url | ||
assert subsegment.fault | ||
|
||
http_meta = subsegment.http | ||
assert http_meta['request']['url'] == url | ||
assert http_meta['request']['method'] == 'PUT' | ||
assert http_meta['response']['status'] == status_code | ||
|
||
|
||
async def test_invalid_url(loop, recorder): | ||
xray_recorder.begin_segment('name') | ||
trace_config = aws_xray_trace_config() | ||
async with ClientSession(loop=loop, trace_configs=[trace_config]) as session: | ||
try: | ||
async with session.get('http://doesnt.exist'): | ||
pass | ||
except Exception: | ||
# prevent uncatch exception from breaking test run | ||
pass | ||
|
||
subsegment = xray_recorder.current_segment().subsegments[0] | ||
assert subsegment.namespace == LOCAL_NAMESPACE | ||
assert subsegment.fault | ||
|
||
exception = subsegment.cause['exceptions'][0] | ||
assert exception.type == 'ClientConnectorError' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters