Skip to content

Commit b714572

Browse files
jonathangreenTyler Hargraves
authored and
Tyler Hargraves
committed
Add ability to ignore some requests from httplib (aws#263)
* Expand ability to ignore some httplib calls. * Add tests. * Add glob match to httplib ignore hostname. * Clean up httplib tests. * Use full module path for subclass. * Add documentation for ignoring httplib requests * Code review feedback
1 parent 2b95726 commit b714572

File tree

4 files changed

+142
-7
lines changed

4 files changed

+142
-7
lines changed

README.md

+30
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,36 @@ XRayMiddleware(app, xray_recorder)
473473
db = XRayFlaskSqlAlchemy(app)
474474

475475
```
476+
477+
### Ignoring httplib requests
478+
479+
If you want to ignore certain httplib requests you can do so based on the hostname or URL that is being requsted. The hostname is matched using the Python [fnmatch library](https://docs.python.org/3/library/fnmatch.html) which does Unix glob style matching.
480+
481+
```python
482+
from aws_xray_sdk.ext.httplib import add_ignored as xray_add_ignored
483+
484+
# ignore requests to test.myapp.com
485+
xray_add_ignored(hostname='test.myapp.com')
486+
487+
# ignore requests to a subdomain of myapp.com with a glob pattern
488+
xray_add_ignored(hostname='*.myapp.com')
489+
490+
# ignore requests to /test-url and /other-test-url
491+
xray_add_ignored(urls=['/test-path', '/other-test-path'])
492+
493+
# ignore requests to myapp.com for /test-url
494+
xray_add_ignored(hostname='myapp.com', urls=['/test-url'])
495+
```
496+
497+
If you use a subclass of httplib to make your requests, you can also filter on the class name that initiates the request. This must use the complete package name to do the match.
498+
499+
```python
500+
from aws_xray_sdk.ext.httplib import add_ignored as xray_add_ignored
501+
502+
# ignore all requests made by botocore
503+
xray_add_ignored(subclass='botocore.awsrequest.AWSHTTPConnection')
504+
```
505+
476506
## License
477507

478508
The AWS X-Ray SDK for Python is licensed under the Apache 2.0 License. See LICENSE and NOTICE.txt for more information.

aws_xray_sdk/ext/httplib/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
from .patch import patch, unpatch
1+
from .patch import patch, unpatch, add_ignored, reset_ignored
22

3-
__all__ = ['patch', 'unpatch']
3+
__all__ = ['patch', 'unpatch', 'add_ignored', 'reset_ignored']

aws_xray_sdk/ext/httplib/patch.py

+44-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from collections import namedtuple
22
import sys
33
import wrapt
4-
4+
import fnmatch
55
import urllib3.connection
66

77
from aws_xray_sdk.core import xray_recorder
@@ -22,8 +22,33 @@
2222

2323
_XRAY_PROP = '_xray_prop'
2424
_XRay_Data = namedtuple('xray_data', ['method', 'host', 'url'])
25+
_XRay_Ignore = namedtuple('xray_ignore', ['subclass', 'hostname', 'urls'])
2526
# A flag indicates whether this module is X-Ray patched or not
2627
PATCH_FLAG = '__xray_patched'
28+
# Calls that should be ignored
29+
_XRAY_IGNORE = set()
30+
31+
32+
def add_ignored(subclass=None, hostname=None, urls=None):
33+
global _XRAY_IGNORE
34+
if subclass is not None or hostname is not None or urls is not None:
35+
urls = urls if urls is None else tuple(urls)
36+
_XRAY_IGNORE.add(_XRay_Ignore(subclass=subclass, hostname=hostname, urls=urls))
37+
38+
39+
def reset_ignored():
40+
global _XRAY_IGNORE
41+
_XRAY_IGNORE.clear()
42+
_ignored_add_default()
43+
44+
45+
def _ignored_add_default():
46+
# skip httplib tracing for SDK built-in centralized sampling pollers
47+
add_ignored(subclass='botocore.awsrequest.AWSHTTPConnection', urls=['/GetSamplingRules', '/SamplingTargets'])
48+
49+
50+
# make sure we have the default rules
51+
_ignored_add_default()
2752

2853

2954
def http_response_processor(wrapped, instance, args, kwargs, return_value,
@@ -77,11 +102,26 @@ def http_send_request_processor(wrapped, instance, args, kwargs, return_value,
77102
subsegment.add_exception(exception, stack)
78103

79104

105+
def _ignore_request(instance, hostname, url):
106+
global _XRAY_IGNORE
107+
module = instance.__class__.__module__
108+
if module is None or module == str.__class__.__module__:
109+
subclass = instance.__class__.__name__
110+
else:
111+
subclass = module + '.' + instance.__class__.__name__
112+
for rule in _XRAY_IGNORE:
113+
subclass_match = subclass == rule.subclass if rule.subclass is not None else True
114+
host_match = fnmatch.fnmatch(hostname, rule.hostname) if rule.hostname is not None else True
115+
url_match = url in rule.urls if rule.urls is not None else True
116+
if url_match and host_match and subclass_match:
117+
return True
118+
return False
119+
120+
80121
def _send_request(wrapped, instance, args, kwargs):
81122
def decompose_args(method, url, body, headers, encode_chunked=False):
82-
# skip httplib tracing for SDK built-in centralized sampling pollers
83-
if (('/GetSamplingRules' in args or '/SamplingTargets' in args) and
84-
type(instance).__name__ == 'botocore.awsrequest.AWSHTTPConnection'):
123+
# skip any ignored requests
124+
if _ignore_request(instance, instance.host, url):
85125
return wrapped(*args, **kwargs)
86126

87127
# Only injects headers when the subsegment for the outgoing

tests/ext/httplib/test_httplib.py

+66-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def construct_ctx():
2525
so that later subsegment can be attached. After each test run
2626
it cleans up context storage again.
2727
"""
28-
from aws_xray_sdk.ext.httplib import unpatch
28+
from aws_xray_sdk.ext.httplib import unpatch, reset_ignored
2929

3030
patch(('httplib',))
3131
xray_recorder.configure(service='test', sampling=False, context=Context())
@@ -35,6 +35,7 @@ def construct_ctx():
3535
yield
3636
xray_recorder.clear_trace_entities()
3737
unpatch()
38+
reset_ignored()
3839

3940

4041
def _do_req(url, method='GET', use_https=True):
@@ -141,3 +142,67 @@ def test_correct_identify_https():
141142

142143
https_meta = subsegment.http
143144
assert https_meta['request']['url'].split(":")[0] == 'https'
145+
146+
147+
def test_ignore_url():
148+
from aws_xray_sdk.ext.httplib import add_ignored
149+
path = '/status/200'
150+
url = 'https://{}{}'.format(BASE_URL, path)
151+
add_ignored(urls=[path])
152+
_do_req(url, use_https=True)
153+
assert len(xray_recorder.current_segment().subsegments) == 0
154+
155+
156+
def test_ignore_hostname():
157+
from aws_xray_sdk.ext.httplib import add_ignored
158+
path = '/status/200'
159+
url = 'https://{}{}'.format(BASE_URL, path)
160+
add_ignored(hostname=BASE_URL)
161+
_do_req(url, use_https=True)
162+
assert len(xray_recorder.current_segment().subsegments) == 0
163+
164+
165+
def test_ignore_hostname_glob():
166+
from aws_xray_sdk.ext.httplib import add_ignored
167+
path = '/status/200'
168+
url = 'https://{}{}'.format(BASE_URL, path)
169+
add_ignored(hostname='http*.org')
170+
_do_req(url, use_https=True)
171+
assert len(xray_recorder.current_segment().subsegments) == 0
172+
173+
174+
class CustomHttpsConnection(httplib.HTTPSConnection):
175+
pass
176+
177+
178+
def test_ignore_subclass():
179+
from aws_xray_sdk.ext.httplib import add_ignored
180+
path = '/status/200'
181+
subclass = 'tests.ext.httplib.test_httplib.CustomHttpsConnection'
182+
add_ignored(subclass=subclass)
183+
conn = CustomHttpsConnection(BASE_URL)
184+
conn.request('GET', path)
185+
conn.getresponse()
186+
assert len(xray_recorder.current_segment().subsegments) == 0
187+
188+
189+
def test_ignore_multiple_match():
190+
from aws_xray_sdk.ext.httplib import add_ignored
191+
path = '/status/200'
192+
subclass = 'tests.ext.httplib.test_httplib.CustomHttpsConnection'
193+
add_ignored(subclass=subclass, hostname=BASE_URL)
194+
conn = CustomHttpsConnection(BASE_URL)
195+
conn.request('GET', path)
196+
conn.getresponse()
197+
assert len(xray_recorder.current_segment().subsegments) == 0
198+
199+
200+
def test_ignore_multiple_no_match():
201+
from aws_xray_sdk.ext.httplib import add_ignored
202+
path = '/status/200'
203+
subclass = 'tests.ext.httplib.test_httplib.CustomHttpsConnection'
204+
add_ignored(subclass=subclass, hostname='fake.host')
205+
conn = CustomHttpsConnection(BASE_URL)
206+
conn.request('GET', path)
207+
conn.getresponse()
208+
assert len(xray_recorder.current_segment().subsegments) > 0

0 commit comments

Comments
 (0)