Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 92f7a15

Browse files
isra17pridhi-arora
authored andcommittedFeb 25, 2023
Fix exception in Urllib3 when dealing with filelike body. (open-telemetry#1399)
1 parent 20ea491 commit 92f7a15

File tree

3 files changed

+222
-146
lines changed

3 files changed

+222
-146
lines changed
 

‎CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99

10+
- Fix exception in Urllib3 when dealing with filelike body.
11+
([#1399](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1399))
12+
1013
### Added
1114

1215
- Add connection attributes to sqlalchemy connect span

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

+18-4
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,9 @@ def response_hook(span, request, response):
6464
---
6565
"""
6666

67+
import collections.abc
6768
import contextlib
69+
import io
6870
import typing
6971
from timeit import default_timer
7072
from typing import Collection
@@ -213,18 +215,20 @@ def instrumented_urlopen(wrapped, instance, args, kwargs):
213215
if callable(response_hook):
214216
response_hook(span, instance, response)
215217

216-
request_size = 0 if body is None else len(body)
218+
request_size = _get_body_size(body)
217219
response_size = int(response.headers.get("Content-Length", 0))
220+
218221
metric_attributes = _create_metric_attributes(
219222
instance, response, method
220223
)
221224

222225
duration_histogram.record(
223226
elapsed_time, attributes=metric_attributes
224227
)
225-
request_size_histogram.record(
226-
request_size, attributes=metric_attributes
227-
)
228+
if request_size is not None:
229+
request_size_histogram.record(
230+
request_size, attributes=metric_attributes
231+
)
228232
response_size_histogram.record(
229233
response_size, attributes=metric_attributes
230234
)
@@ -268,6 +272,16 @@ def _get_url(
268272
return url
269273

270274

275+
def _get_body_size(body: object) -> typing.Optional[int]:
276+
if body is None:
277+
return 0
278+
if isinstance(body, collections.abc.Sized):
279+
return len(body)
280+
if isinstance(body, io.BytesIO):
281+
return body.getbuffer().nbytes
282+
return None
283+
284+
271285
def _should_append_port(scheme: str, port: typing.Optional[int]) -> bool:
272286
if not port:
273287
return False

‎instrumentation/opentelemetry-instrumentation-urllib3/tests/test_urllib3_metrics.py

+201-142
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import io
1516
from timeit import default_timer
1617

18+
import httpretty
1719
import urllib3
1820
import urllib3.exceptions
1921
from urllib3.request import encode_multipart_formdata
@@ -23,181 +25,238 @@
2325
from opentelemetry.test.test_base import TestBase
2426

2527

26-
class TestUrllib3MetricsInstrumentation(HttpTestBase, TestBase):
28+
class TestURLLib3InstrumentorMetric(HttpTestBase, TestBase):
29+
HTTP_URL = "http://httpbin.org/status/200"
30+
2731
def setUp(self):
2832
super().setUp()
29-
self.assert_ip = self.server.server_address[0]
30-
self.assert_port = self.server.server_address[1]
31-
self.http_host = ":".join(map(str, self.server.server_address[:2]))
32-
self.http_url_base = "http://" + self.http_host
33-
self.http_url = self.http_url_base + "/status/200"
34-
URLLib3Instrumentor().instrument(meter_provider=self.meter_provider)
33+
URLLib3Instrumentor().instrument()
34+
httpretty.enable(allow_net_connect=False)
35+
httpretty.register_uri(httpretty.GET, self.HTTP_URL, body="Hello!")
36+
httpretty.register_uri(httpretty.POST, self.HTTP_URL, body="Hello!")
37+
self.pool = urllib3.PoolManager()
3538

3639
def tearDown(self):
3740
super().tearDown()
41+
self.pool.clear()
3842
URLLib3Instrumentor().uninstrument()
3943

40-
# Return Sequence with one histogram
41-
def create_histogram_data_points(self, sum_data_point, attributes):
42-
return [
43-
self.create_histogram_data_point(
44-
sum_data_point, 1, sum_data_point, sum_data_point, attributes
45-
)
46-
]
47-
48-
def test_basic_metric_check_client_size_get(self):
49-
with urllib3.PoolManager() as pool:
50-
start_time = default_timer()
51-
response = pool.request("GET", self.http_url)
52-
client_duration_estimated = (default_timer() - start_time) * 1000
53-
54-
metrics = self.get_sorted_metrics()
55-
56-
(
57-
client_duration,
58-
client_request_size,
59-
client_response_size,
60-
) = metrics
61-
62-
self.assertEqual(client_duration.name, "http.client.duration")
63-
self.assert_metric_expected(
64-
client_duration,
65-
self.create_histogram_data_points(
66-
client_duration_estimated,
44+
httpretty.disable()
45+
httpretty.reset()
46+
47+
def test_basic_metrics(self):
48+
start_time = default_timer()
49+
response = self.pool.request("GET", self.HTTP_URL)
50+
client_duration_estimated = (default_timer() - start_time) * 1000
51+
52+
metrics = self.get_sorted_metrics()
53+
54+
(
55+
client_duration,
56+
client_request_size,
57+
client_response_size,
58+
) = metrics
59+
60+
self.assertEqual(client_duration.name, "http.client.duration")
61+
self.assert_metric_expected(
62+
client_duration,
63+
[
64+
self.create_histogram_data_point(
65+
count=1,
66+
sum_data_point=client_duration_estimated,
67+
max_data_point=client_duration_estimated,
68+
min_data_point=client_duration_estimated,
6769
attributes={
68-
"http.status_code": 200,
69-
"http.host": self.assert_ip,
70-
"http.method": "GET",
7170
"http.flavor": "1.1",
71+
"http.host": "httpbin.org",
72+
"http.method": "GET",
7273
"http.scheme": "http",
73-
"net.peer.name": self.assert_ip,
74-
"net.peer.port": self.assert_port,
74+
"http.status_code": 200,
75+
"net.peer.name": "httpbin.org",
76+
"net.peer.port": 80,
7577
},
76-
),
77-
est_value_delta=200,
78-
)
79-
80-
self.assertEqual(
81-
client_request_size.name, "http.client.request.size"
82-
)
83-
self.assert_metric_expected(
84-
client_request_size,
85-
self.create_histogram_data_points(
86-
0,
78+
)
79+
],
80+
est_value_delta=200,
81+
)
82+
83+
self.assertEqual(client_request_size.name, "http.client.request.size")
84+
self.assert_metric_expected(
85+
client_request_size,
86+
[
87+
self.create_histogram_data_point(
88+
count=1,
89+
sum_data_point=0,
90+
max_data_point=0,
91+
min_data_point=0,
8792
attributes={
88-
"http.status_code": 200,
89-
"http.host": self.assert_ip,
90-
"http.method": "GET",
9193
"http.flavor": "1.1",
94+
"http.host": "httpbin.org",
95+
"http.method": "GET",
9296
"http.scheme": "http",
93-
"net.peer.name": self.assert_ip,
94-
"net.peer.port": self.assert_port,
97+
"http.status_code": 200,
98+
"net.peer.name": "httpbin.org",
99+
"net.peer.port": 80,
95100
},
96-
),
97-
)
98-
99-
self.assertEqual(
100-
client_response_size.name, "http.client.response.size"
101-
)
102-
self.assert_metric_expected(
103-
client_response_size,
104-
self.create_histogram_data_points(
105-
len(response.data),
101+
)
102+
],
103+
)
104+
105+
expected_size = len(response.data)
106+
self.assertEqual(
107+
client_response_size.name, "http.client.response.size"
108+
)
109+
self.assert_metric_expected(
110+
client_response_size,
111+
[
112+
self.create_histogram_data_point(
113+
count=1,
114+
sum_data_point=expected_size,
115+
max_data_point=expected_size,
116+
min_data_point=expected_size,
106117
attributes={
107-
"http.status_code": 200,
108-
"http.host": self.assert_ip,
109-
"http.method": "GET",
110118
"http.flavor": "1.1",
119+
"http.host": "httpbin.org",
120+
"http.method": "GET",
111121
"http.scheme": "http",
112-
"net.peer.name": self.assert_ip,
113-
"net.peer.port": self.assert_port,
122+
"http.status_code": 200,
123+
"net.peer.name": "httpbin.org",
124+
"net.peer.port": 80,
114125
},
115-
),
116-
)
117-
118-
def test_basic_metric_check_client_size_post(self):
119-
with urllib3.PoolManager() as pool:
120-
start_time = default_timer()
121-
data_fields = {"data": "test"}
122-
response = pool.request("POST", self.http_url, fields=data_fields)
123-
client_duration_estimated = (default_timer() - start_time) * 1000
124-
body = encode_multipart_formdata(data_fields)[0]
125-
126-
metrics = self.get_sorted_metrics()
127-
128-
(
129-
client_duration,
130-
client_request_size,
131-
client_response_size,
132-
) = metrics
133-
134-
self.assertEqual(client_duration.name, "http.client.duration")
135-
self.assert_metric_expected(
136-
client_duration,
137-
self.create_histogram_data_points(
138-
client_duration_estimated,
126+
)
127+
],
128+
)
129+
130+
def test_str_request_body_size_metrics(self):
131+
self.pool.request("POST", self.HTTP_URL, body="foobar")
132+
133+
metrics = self.get_sorted_metrics()
134+
(_, client_request_size, _) = metrics
135+
136+
self.assertEqual(client_request_size.name, "http.client.request.size")
137+
self.assert_metric_expected(
138+
client_request_size,
139+
[
140+
self.create_histogram_data_point(
141+
count=1,
142+
sum_data_point=6,
143+
max_data_point=6,
144+
min_data_point=6,
139145
attributes={
140-
"http.status_code": 501,
141-
"http.host": self.assert_ip,
142-
"http.method": "POST",
143146
"http.flavor": "1.1",
147+
"http.host": "httpbin.org",
148+
"http.method": "POST",
144149
"http.scheme": "http",
145-
"net.peer.name": self.assert_ip,
146-
"net.peer.port": self.assert_port,
150+
"http.status_code": 200,
151+
"net.peer.name": "httpbin.org",
152+
"net.peer.port": 80,
147153
},
148-
),
149-
est_value_delta=200,
150-
)
151-
152-
self.assertEqual(
153-
client_request_size.name, "http.client.request.size"
154-
)
155-
client_request_size_expected = len(body)
156-
self.assert_metric_expected(
157-
client_request_size,
158-
self.create_histogram_data_points(
159-
client_request_size_expected,
154+
)
155+
],
156+
)
157+
158+
def test_bytes_request_body_size_metrics(self):
159+
self.pool.request("POST", self.HTTP_URL, body=b"foobar")
160+
161+
metrics = self.get_sorted_metrics()
162+
(_, client_request_size, _) = metrics
163+
164+
self.assertEqual(client_request_size.name, "http.client.request.size")
165+
self.assert_metric_expected(
166+
client_request_size,
167+
[
168+
self.create_histogram_data_point(
169+
count=1,
170+
sum_data_point=6,
171+
max_data_point=6,
172+
min_data_point=6,
160173
attributes={
161-
"http.status_code": 501,
162-
"http.host": self.assert_ip,
163-
"http.method": "POST",
164174
"http.flavor": "1.1",
175+
"http.host": "httpbin.org",
176+
"http.method": "POST",
165177
"http.scheme": "http",
166-
"net.peer.name": self.assert_ip,
167-
"net.peer.port": self.assert_port,
178+
"http.status_code": 200,
179+
"net.peer.name": "httpbin.org",
180+
"net.peer.port": 80,
168181
},
169-
),
170-
)
171-
172-
self.assertEqual(
173-
client_response_size.name, "http.client.response.size"
174-
)
175-
client_response_size_expected = len(response.data)
176-
self.assert_metric_expected(
177-
client_response_size,
178-
self.create_histogram_data_points(
179-
client_response_size_expected,
182+
)
183+
],
184+
)
185+
186+
def test_fields_request_body_size_metrics(self):
187+
self.pool.request("POST", self.HTTP_URL, fields={"foo": "bar"})
188+
189+
metrics = self.get_sorted_metrics()
190+
(_, client_request_size, _) = metrics
191+
192+
self.assertEqual(client_request_size.name, "http.client.request.size")
193+
expected_value = len(encode_multipart_formdata({"foo": "bar"})[0])
194+
self.assert_metric_expected(
195+
client_request_size,
196+
[
197+
self.create_histogram_data_point(
198+
count=1,
199+
sum_data_point=expected_value,
200+
max_data_point=expected_value,
201+
min_data_point=expected_value,
180202
attributes={
181-
"http.status_code": 501,
182-
"http.host": self.assert_ip,
203+
"http.flavor": "1.1",
204+
"http.host": "httpbin.org",
183205
"http.method": "POST",
206+
"http.scheme": "http",
207+
"http.status_code": 200,
208+
"net.peer.name": "httpbin.org",
209+
"net.peer.port": 80,
210+
},
211+
)
212+
],
213+
)
214+
215+
def test_bytesio_request_body_size_metrics(self):
216+
self.pool.request("POST", self.HTTP_URL, body=io.BytesIO(b"foobar"))
217+
218+
metrics = self.get_sorted_metrics()
219+
(_, client_request_size, _) = metrics
220+
221+
self.assertEqual(client_request_size.name, "http.client.request.size")
222+
self.assert_metric_expected(
223+
client_request_size,
224+
[
225+
self.create_histogram_data_point(
226+
count=1,
227+
sum_data_point=6,
228+
max_data_point=6,
229+
min_data_point=6,
230+
attributes={
184231
"http.flavor": "1.1",
232+
"http.host": "httpbin.org",
233+
"http.method": "POST",
185234
"http.scheme": "http",
186-
"net.peer.name": self.assert_ip,
187-
"net.peer.port": self.assert_port,
235+
"http.status_code": 200,
236+
"net.peer.name": "httpbin.org",
237+
"net.peer.port": 80,
188238
},
189-
),
190-
)
239+
)
240+
],
241+
)
242+
243+
def test_generator_request_body_size_metrics(self):
244+
self.pool.request(
245+
"POST", self.HTTP_URL, body=(b for b in (b"foo", b"bar"))
246+
)
247+
248+
metrics = self.get_sorted_metrics()
249+
self.assertEqual(len(metrics), 2)
250+
self.assertNotIn("http.client.request.size", [m.name for m in metrics])
191251

192252
def test_metric_uninstrument(self):
193-
with urllib3.PoolManager() as pool:
194-
pool.request("GET", self.http_url)
195-
URLLib3Instrumentor().uninstrument()
196-
pool.request("GET", self.http_url)
253+
self.pool.request("GET", self.HTTP_URL)
254+
URLLib3Instrumentor().uninstrument()
255+
self.pool.request("GET", self.HTTP_URL)
197256

198-
metrics = self.get_sorted_metrics()
199-
self.assertEqual(len(metrics), 3)
257+
metrics = self.get_sorted_metrics()
258+
self.assertEqual(len(metrics), 3)
200259

201-
for metric in metrics:
202-
for point in list(metric.data.data_points):
203-
self.assertEqual(point.count, 1)
260+
for metric in metrics:
261+
for point in list(metric.data.data_points):
262+
self.assertEqual(point.count, 1)

0 commit comments

Comments
 (0)
Please sign in to comment.