Skip to content

Commit 4e059b1

Browse files
authored
Add exclude_list for urllib/urllib3 instrumentations (#1733)
* urllib * urllib3 * Update __init__.py * readme * lint
1 parent 5052190 commit 4e059b1

File tree

8 files changed

+217
-6
lines changed

8 files changed

+217
-6
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1717
([#1690](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1690))
1818
- Add metrics instrumentation for sqlalchemy
1919
([#1645](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1645))
20+
- Add `excluded_urls` functionality to `urllib` and `urllib3` instrumentations
21+
([#1733](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1733))
2022

2123
### Fixed
2224

instrumentation/opentelemetry-instrumentation-requests/src/opentelemetry/instrumentation/requests/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
import functools
5252
import types
5353
from timeit import default_timer
54-
from typing import Callable, Collection, Iterable, Optional
54+
from typing import Callable, Collection, Optional
5555
from urllib.parse import urlparse
5656

5757
from requests.models import PreparedRequest, Response
@@ -77,6 +77,7 @@
7777
from opentelemetry.trace.span import Span
7878
from opentelemetry.trace.status import Status
7979
from opentelemetry.util.http import (
80+
ExcludeList,
8081
get_excluded_urls,
8182
parse_excluded_urls,
8283
remove_url_credentials,
@@ -96,7 +97,7 @@ def _instrument(
9697
duration_histogram: Histogram,
9798
request_hook: _RequestHookT = None,
9899
response_hook: _ResponseHookT = None,
99-
excluded_urls: Iterable[str] = None,
100+
excluded_urls: ExcludeList = None,
100101
):
101102
"""Enables tracing of all requests calls that go through
102103
:code:`requests.session.Session.request` (this includes

instrumentation/opentelemetry-instrumentation-urllib/README.rst

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,46 @@ Installation
1616

1717
pip install opentelemetry-instrumentation-urllib
1818

19+
Configuration
20+
-------------
21+
22+
Request/Response hooks
23+
**********************
24+
25+
The urllib instrumentation supports extending tracing behavior with the help of
26+
request and response hooks. These are functions that are called back by the instrumentation
27+
right after a Span is created for a request and right before the span is finished processing a response respectively.
28+
The hooks can be configured as follows:
29+
30+
.. code:: python
31+
32+
# `request_obj` is an instance of urllib.request.Request
33+
def request_hook(span, request_obj):
34+
pass
35+
36+
# `request_obj` is an instance of urllib.request.Request
37+
# `response` is an instance of http.client.HTTPResponse
38+
def response_hook(span, request_obj, response)
39+
pass
40+
41+
URLLibInstrumentor.instrument(
42+
request_hook=request_hook, response_hook=response_hook)
43+
)
44+
45+
Exclude lists
46+
*************
47+
48+
To exclude certain URLs from being tracked, set the environment variable ``OTEL_PYTHON_URLLIB_EXCLUDED_URLS``
49+
(or ``OTEL_PYTHON_EXCLUDED_URLS`` as fallback) with comma delimited regexes representing which URLs to exclude.
50+
51+
For example,
52+
53+
::
54+
55+
export OTEL_PYTHON_URLLIB_EXCLUDED_URLS="client/.*/info,healthcheck"
56+
57+
will exclude requests such as ``https://site/client/123/info`` and ``https://site/xyz/healthcheck``.
58+
1959
References
2060
----------
2161

instrumentation/opentelemetry-instrumentation-urllib/src/opentelemetry/instrumentation/urllib/__init__.py

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,20 @@ def response_hook(span, request_obj, response)
5656
request_hook=request_hook, response_hook=response_hook)
5757
)
5858
59+
Exclude lists
60+
*************
61+
62+
To exclude certain URLs from being tracked, set the environment variable ``OTEL_PYTHON_URLLIB_EXCLUDED_URLS``
63+
(or ``OTEL_PYTHON_EXCLUDED_URLS`` as fallback) with comma delimited regexes representing which URLs to exclude.
64+
65+
For example,
66+
67+
::
68+
69+
export OTEL_PYTHON_URLLIB_EXCLUDED_URLS="client/.*/info,healthcheck"
70+
71+
will exclude requests such as ``https://site/client/123/info`` and ``https://site/xyz/healthcheck``.
72+
5973
API
6074
---
6175
"""
@@ -88,7 +102,14 @@ def response_hook(span, request_obj, response)
88102
from opentelemetry.semconv.trace import SpanAttributes
89103
from opentelemetry.trace import Span, SpanKind, get_tracer
90104
from opentelemetry.trace.status import Status
91-
from opentelemetry.util.http import remove_url_credentials
105+
from opentelemetry.util.http import (
106+
ExcludeList,
107+
get_excluded_urls,
108+
parse_excluded_urls,
109+
remove_url_credentials,
110+
)
111+
112+
_excluded_urls_from_env = get_excluded_urls("URLLIB")
92113

93114
_RequestHookT = typing.Optional[typing.Callable[[Span, Request], None]]
94115
_ResponseHookT = typing.Optional[
@@ -112,10 +133,12 @@ def _instrument(self, **kwargs):
112133
``tracer_provider``: a TracerProvider, defaults to global
113134
``request_hook``: An optional callback invoked that is invoked right after a span is created.
114135
``response_hook``: An optional callback which is invoked right before the span is finished processing a response
136+
``excluded_urls``: A string containing a comma-delimited
137+
list of regexes used to exclude URLs from tracking
115138
"""
116139
tracer_provider = kwargs.get("tracer_provider")
117140
tracer = get_tracer(__name__, __version__, tracer_provider)
118-
141+
excluded_urls = kwargs.get("excluded_urls")
119142
meter_provider = kwargs.get("meter_provider")
120143
meter = get_meter(__name__, __version__, meter_provider)
121144

@@ -126,6 +149,9 @@ def _instrument(self, **kwargs):
126149
histograms,
127150
request_hook=kwargs.get("request_hook"),
128151
response_hook=kwargs.get("response_hook"),
152+
excluded_urls=_excluded_urls_from_env
153+
if excluded_urls is None
154+
else parse_excluded_urls(excluded_urls),
129155
)
130156

131157
def _uninstrument(self, **kwargs):
@@ -143,6 +169,7 @@ def _instrument(
143169
histograms: Dict[str, Histogram],
144170
request_hook: _RequestHookT = None,
145171
response_hook: _ResponseHookT = None,
172+
excluded_urls: ExcludeList = None,
146173
):
147174
"""Enables tracing of all requests calls that go through
148175
:code:`urllib.Client._make_request`"""
@@ -174,8 +201,11 @@ def _instrumented_open_call(
174201
) or context.get_value(_SUPPRESS_HTTP_INSTRUMENTATION_KEY):
175202
return call_wrapped()
176203

177-
method = request.get_method().upper()
178204
url = request.full_url
205+
if excluded_urls and excluded_urls.url_disabled(url):
206+
return call_wrapped()
207+
208+
method = request.get_method().upper()
179209

180210
span_name = f"HTTP {method}".strip()
181211

instrumentation/opentelemetry-instrumentation-urllib/tests/test_urllib_integration.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
from opentelemetry.test.mock_textmap import MockTextMapPropagator
3939
from opentelemetry.test.test_base import TestBase
4040
from opentelemetry.trace import StatusCode
41+
from opentelemetry.util.http import get_excluded_urls
4142

4243
# pylint: disable=too-many-public-methods
4344

@@ -52,6 +53,21 @@ class RequestsIntegrationTestBase(abc.ABC):
5253
# pylint: disable=invalid-name
5354
def setUp(self):
5455
super().setUp()
56+
57+
self.env_patch = mock.patch.dict(
58+
"os.environ",
59+
{
60+
"OTEL_PYTHON_URLLIB_EXCLUDED_URLS": "http://localhost/env_excluded_arg/123,env_excluded_noarg"
61+
},
62+
)
63+
self.env_patch.start()
64+
65+
self.exclude_patch = mock.patch(
66+
"opentelemetry.instrumentation.urllib._excluded_urls_from_env",
67+
get_excluded_urls("URLLIB"),
68+
)
69+
self.exclude_patch.start()
70+
5571
URLLibInstrumentor().instrument()
5672
httpretty.enable()
5773
httpretty.register_uri(httpretty.GET, self.URL, body=b"Hello!")
@@ -125,6 +141,36 @@ def test_basic(self):
125141
span, opentelemetry.instrumentation.urllib
126142
)
127143

144+
def test_excluded_urls_explicit(self):
145+
url_201 = "http://httpbin.org/status/201"
146+
httpretty.register_uri(
147+
httpretty.GET,
148+
url_201,
149+
status=201,
150+
)
151+
152+
URLLibInstrumentor().uninstrument()
153+
URLLibInstrumentor().instrument(excluded_urls=".*/201")
154+
self.perform_request(self.URL)
155+
self.perform_request(url_201)
156+
157+
self.assert_span(num_spans=1)
158+
159+
def test_excluded_urls_from_env(self):
160+
url = "http://localhost/env_excluded_arg/123"
161+
httpretty.register_uri(
162+
httpretty.GET,
163+
url,
164+
status=200,
165+
)
166+
167+
URLLibInstrumentor().uninstrument()
168+
URLLibInstrumentor().instrument()
169+
self.perform_request(self.URL)
170+
self.perform_request(url)
171+
172+
self.assert_span(num_spans=1)
173+
128174
def test_not_foundbasic(self):
129175
url_404 = "http://httpbin.org/status/404/"
130176
httpretty.register_uri(

instrumentation/opentelemetry-instrumentation-urllib3/README.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,20 @@ The hooks can be configured as follows:
4242
request_hook=request_hook, response_hook=response_hook)
4343
)
4444
45+
Exclude lists
46+
*************
47+
48+
To exclude certain URLs from being tracked, set the environment variable ``OTEL_PYTHON_URLLIB3_EXCLUDED_URLS``
49+
(or ``OTEL_PYTHON_EXCLUDED_URLS`` as fallback) with comma delimited regexes representing which URLs to exclude.
50+
51+
For example,
52+
53+
::
54+
55+
export OTEL_PYTHON_URLLIB3_EXCLUDED_URLS="client/.*/info,healthcheck"
56+
57+
will exclude requests such as ``https://site/client/123/info`` and ``https://site/xyz/healthcheck``.
58+
4559
References
4660
----------
4761

instrumentation/opentelemetry-instrumentation-urllib3/src/opentelemetry/instrumentation/urllib3/__init__.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,20 @@ def response_hook(span, request, response):
6060
request_hook=request_hook, response_hook=response_hook)
6161
)
6262
63+
Exclude lists
64+
*************
65+
66+
To exclude certain URLs from being tracked, set the environment variable ``OTEL_PYTHON_URLLIB3_EXCLUDED_URLS``
67+
(or ``OTEL_PYTHON_EXCLUDED_URLS`` as fallback) with comma delimited regexes representing which URLs to exclude.
68+
69+
For example,
70+
71+
::
72+
73+
export OTEL_PYTHON_URLLIB3_EXCLUDED_URLS="client/.*/info,healthcheck"
74+
75+
will exclude requests such as ``https://site/client/123/info`` and ``https://site/xyz/healthcheck``.
76+
6377
API
6478
---
6579
"""
@@ -92,8 +106,15 @@ def response_hook(span, request, response):
92106
from opentelemetry.semconv.trace import SpanAttributes
93107
from opentelemetry.trace import Span, SpanKind, Tracer, get_tracer
94108
from opentelemetry.trace.status import Status
109+
from opentelemetry.util.http import (
110+
ExcludeList,
111+
get_excluded_urls,
112+
parse_excluded_urls,
113+
)
95114
from opentelemetry.util.http.httplib import set_ip_on_next_http_connection
96115

116+
_excluded_urls_from_env = get_excluded_urls("URLLIB3")
117+
97118
_UrlFilterT = typing.Optional[typing.Callable[[str], str]]
98119
_RequestHookT = typing.Optional[
99120
typing.Callable[
@@ -138,10 +159,14 @@ def _instrument(self, **kwargs):
138159
``response_hook``: An optional callback which is invoked right before the span is finished processing a response.
139160
``url_filter``: A callback to process the requested URL prior
140161
to adding it as a span attribute.
162+
``excluded_urls``: A string containing a comma-delimited
163+
list of regexes used to exclude URLs from tracking
141164
"""
142165
tracer_provider = kwargs.get("tracer_provider")
143166
tracer = get_tracer(__name__, __version__, tracer_provider)
144167

168+
excluded_urls = kwargs.get("excluded_urls")
169+
145170
meter_provider = kwargs.get("meter_provider")
146171
meter = get_meter(__name__, __version__, meter_provider)
147172

@@ -169,6 +194,9 @@ def _instrument(self, **kwargs):
169194
request_hook=kwargs.get("request_hook"),
170195
response_hook=kwargs.get("response_hook"),
171196
url_filter=kwargs.get("url_filter"),
197+
excluded_urls=_excluded_urls_from_env
198+
if excluded_urls is None
199+
else parse_excluded_urls(excluded_urls),
172200
)
173201

174202
def _uninstrument(self, **kwargs):
@@ -183,13 +211,17 @@ def _instrument(
183211
request_hook: _RequestHookT = None,
184212
response_hook: _ResponseHookT = None,
185213
url_filter: _UrlFilterT = None,
214+
excluded_urls: ExcludeList = None,
186215
):
187216
def instrumented_urlopen(wrapped, instance, args, kwargs):
188217
if _is_instrumentation_suppressed():
189218
return wrapped(*args, **kwargs)
190219

191-
method = _get_url_open_arg("method", args, kwargs).upper()
192220
url = _get_url(instance, args, kwargs, url_filter)
221+
if excluded_urls and excluded_urls.url_disabled(url):
222+
return wrapped(*args, **kwargs)
223+
224+
method = _get_url_open_arg("method", args, kwargs).upper()
193225
headers = _prepare_headers(kwargs)
194226
body = _get_url_open_arg("body", args, kwargs)
195227

instrumentation/opentelemetry-instrumentation-urllib3/tests/test_urllib3_integration.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from opentelemetry.semconv.trace import SpanAttributes
3030
from opentelemetry.test.mock_textmap import MockTextMapPropagator
3131
from opentelemetry.test.test_base import TestBase
32+
from opentelemetry.util.http import get_excluded_urls
3233

3334
# pylint: disable=too-many-public-methods
3435

@@ -39,6 +40,21 @@ class TestURLLib3Instrumentor(TestBase):
3940

4041
def setUp(self):
4142
super().setUp()
43+
44+
self.env_patch = mock.patch.dict(
45+
"os.environ",
46+
{
47+
"OTEL_PYTHON_URLLIB3_EXCLUDED_URLS": "http://localhost/env_excluded_arg/123,env_excluded_noarg"
48+
},
49+
)
50+
self.env_patch.start()
51+
52+
self.exclude_patch = mock.patch(
53+
"opentelemetry.instrumentation.urllib3._excluded_urls_from_env",
54+
get_excluded_urls("URLLIB3"),
55+
)
56+
self.exclude_patch.start()
57+
4258
URLLib3Instrumentor().instrument()
4359

4460
httpretty.enable(allow_net_connect=False)
@@ -158,6 +174,36 @@ def test_url_open_explicit_arg_parameters(self):
158174

159175
self.assert_success_span(response, url)
160176

177+
def test_excluded_urls_explicit(self):
178+
url_201 = "http://httpbin.org/status/201"
179+
httpretty.register_uri(
180+
httpretty.GET,
181+
url_201,
182+
status=201,
183+
)
184+
185+
URLLib3Instrumentor().uninstrument()
186+
URLLib3Instrumentor().instrument(excluded_urls=".*/201")
187+
self.perform_request(self.HTTP_URL)
188+
self.perform_request(url_201)
189+
190+
self.assert_span(num_spans=1)
191+
192+
def test_excluded_urls_from_env(self):
193+
url = "http://localhost/env_excluded_arg/123"
194+
httpretty.register_uri(
195+
httpretty.GET,
196+
url,
197+
status=200,
198+
)
199+
200+
URLLib3Instrumentor().uninstrument()
201+
URLLib3Instrumentor().instrument()
202+
self.perform_request(self.HTTP_URL)
203+
self.perform_request(url)
204+
205+
self.assert_span(num_spans=1)
206+
161207
def test_uninstrument(self):
162208
URLLib3Instrumentor().uninstrument()
163209

0 commit comments

Comments
 (0)