Skip to content

Commit 262a097

Browse files
authored
opentelemetry-instrumentation-urllib3: fix multiple arguments error (open-telemetry#4144)
* opentelemetry-instrumentation-urllib3: fix multiple arguments error * update CHANGELOG.md
1 parent 626f44b commit 262a097

3 files changed

Lines changed: 81 additions & 36 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
102102
([#3729](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3729))
103103
- `opentelemetry-instrumentation-confluent-kafka`: Fix incorrect number of argument to `_inner_wrap_close`
104104
([#3922](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3922))
105+
- `opentelemetry-instrumentation-urllib3`: fix multiple arguments error
106+
([#4144](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4144))
105107

106108
### Breaking changes
107109

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

Lines changed: 27 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -184,9 +184,11 @@ def response_hook(
184184
"""
185185

186186
import collections.abc
187+
import inspect
187188
import io
188189
import typing
189190
from dataclasses import dataclass
191+
from inspect import BoundArguments
190192
from timeit import default_timer
191193
from typing import Collection
192194

@@ -287,12 +289,6 @@ class RequestInfo:
287289
]
288290
]
289291

290-
_URL_OPEN_ARG_TO_INDEX_MAPPING = {
291-
"method": 0,
292-
"url": 1,
293-
"body": 2,
294-
}
295-
296292

297293
class URLLib3Instrumentor(BaseInstrumentor):
298294
def instrumentation_dependencies(self) -> Collection[str]:
@@ -428,6 +424,7 @@ def _get_span_name(method: str) -> str:
428424
return method
429425

430426

427+
# pylint: disable=too-many-locals
431428
def _instrument(
432429
tracer: Tracer,
433430
duration_histogram_old: Histogram,
@@ -445,18 +442,31 @@ def _instrument(
445442
captured_response_headers: typing.Optional[list[str]] = None,
446443
sensitive_headers: typing.Optional[list[str]] = None,
447444
):
448-
# pylint: disable=too-many-locals
445+
urlopen_signature = inspect.signature(
446+
urllib3.connectionpool.HTTPConnectionPool.urlopen
447+
)
448+
449449
def instrumented_urlopen(wrapped, instance, args, kwargs):
450450
if not is_http_instrumentation_enabled():
451451
return wrapped(*args, **kwargs)
452452

453-
url = _get_url(instance, args, kwargs, url_filter)
453+
try:
454+
bound_args = urlopen_signature.bind(instance, *args, **kwargs)
455+
except TypeError:
456+
return wrapped(*args, **kwargs)
457+
458+
bound_args.apply_defaults()
459+
460+
method = bound_args.arguments.get("method").upper()
461+
headers = bound_args.arguments.get("headers")
462+
body = bound_args.arguments.get("body")
463+
url = _get_url(instance, bound_args, url_filter)
464+
454465
if excluded_urls and excluded_urls.url_disabled(url):
455466
return wrapped(*args, **kwargs)
456467

457-
method = _get_url_open_arg("method", args, kwargs).upper()
458-
headers = _prepare_headers(kwargs)
459-
body = _get_url_open_arg("body", args, kwargs)
468+
# avoid modifying original headers on inject
469+
headers = headers.copy() if headers is not None else {}
460470

461471
span_name = _get_span_name(method)
462472
span_attributes = {}
@@ -496,10 +506,12 @@ def instrumented_urlopen(wrapped, instance, args, kwargs):
496506
),
497507
)
498508
inject(headers)
509+
bound_args.arguments["headers"] = headers
510+
499511
# TODO: add error handling to also set exception `error.type` in new semconv
500512
with suppress_http_instrumentation():
501513
start_time = default_timer()
502-
response = wrapped(*args, **kwargs)
514+
response = wrapped(*bound_args.args[1:], **bound_args.kwargs)
503515
duration_s = default_timer() - start_time
504516
# set http status code based on semconv
505517
metric_attributes = {}
@@ -554,23 +566,12 @@ def instrumented_urlopen(wrapped, instance, args, kwargs):
554566
)
555567

556568

557-
def _get_url_open_arg(name: str, args: typing.List, kwargs: typing.Mapping):
558-
arg_idx = _URL_OPEN_ARG_TO_INDEX_MAPPING.get(name)
559-
if arg_idx is not None:
560-
try:
561-
return args[arg_idx]
562-
except IndexError:
563-
pass
564-
return kwargs.get(name)
565-
566-
567569
def _get_url(
568570
instance: urllib3.connectionpool.HTTPConnectionPool,
569-
args: typing.List,
570-
kwargs: typing.Mapping,
571+
bound_args: BoundArguments,
571572
url_filter: _UrlFilterT,
572573
) -> str:
573-
url_or_path = _get_url_open_arg("url", args, kwargs)
574+
url_or_path = bound_args.arguments.get("url")
574575
if not url_or_path.startswith("/"):
575576
url = url_or_path
576577
else:
@@ -587,6 +588,7 @@ def _get_url(
587588
def _get_body_size(body: object) -> typing.Optional[int]:
588589
if body is None:
589590
return 0
591+
# pylint: disable-next=no-member
590592
if isinstance(body, collections.abc.Sized):
591593
return len(body)
592594
if isinstance(body, io.BytesIO):
@@ -604,16 +606,6 @@ def _should_append_port(scheme: str, port: typing.Optional[int]) -> bool:
604606
return True
605607

606608

607-
def _prepare_headers(urlopen_kwargs: typing.Dict) -> typing.Dict:
608-
headers = urlopen_kwargs.get("headers")
609-
610-
# avoid modifying original headers on inject
611-
headers = headers.copy() if headers is not None else {}
612-
urlopen_kwargs["headers"] = headers
613-
614-
return headers
615-
616-
617609
def _set_status_code_attribute(
618610
span: Span,
619611
status_code: int,

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

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
from opentelemetry.trace import Span
4343
from opentelemetry.util.http import get_excluded_urls
4444

45-
# pylint: disable=too-many-public-methods
45+
# pylint: disable=too-many-public-methods,too-many-lines
4646

4747

4848
class TestURLLib3Instrumentor(TestBase):
@@ -951,3 +951,54 @@ def test_capture_and_sanitize_environment_variables(self):
951951
"http.response.header.x_response_two",
952952
span.attributes,
953953
)
954+
955+
def test_urlopen_positional_headers(self):
956+
URLLib3Instrumentor().uninstrument()
957+
URLLib3Instrumentor().instrument(captured_request_headers=["X-Test"])
958+
url = "http://mock/status/200"
959+
httpretty.register_uri(httpretty.GET, url, body="Hello!")
960+
pool = urllib3.HTTPConnectionPool("mock")
961+
headers = {"X-Test": "Value"}
962+
response = pool.urlopen("GET", "/status/200", None, headers)
963+
self.assertEqual(b"Hello!", response.data)
964+
span = self.assert_span()
965+
self.assertEqual(
966+
span.attributes["http.request.header.x_test"], ("Value",)
967+
)
968+
969+
def test_urlopen_all_positional(self):
970+
URLLib3Instrumentor().uninstrument()
971+
URLLib3Instrumentor().instrument(captured_request_headers=["X-Test"])
972+
url = "http://mock/status/200"
973+
httpretty.register_uri(httpretty.GET, url, body="Hello!")
974+
pool = urllib3.HTTPConnectionPool("mock")
975+
response = pool.urlopen(
976+
"GET",
977+
"/status/200",
978+
None, # body
979+
{"X-Test": "Value"}, # headers
980+
None, # retries
981+
True, # redirect
982+
True, # assert_same_host
983+
urllib3.util.Timeout(5), # timeout
984+
)
985+
self.assertEqual(b"Hello!", response.data)
986+
span = self.assert_span()
987+
self.assertEqual(
988+
span.attributes["http.request.header.x_test"], ("Value",)
989+
)
990+
991+
def test_urlopen_mixed_args(self):
992+
URLLib3Instrumentor().uninstrument()
993+
URLLib3Instrumentor().instrument(captured_request_headers=["X-Test"])
994+
url = "http://mock/status/200"
995+
httpretty.register_uri(httpretty.GET, url, body="Hello!")
996+
pool = urllib3.HTTPConnectionPool("mock")
997+
response = pool.urlopen(
998+
"GET", "/status/200", headers={"X-Test": "Value"}, body=None
999+
)
1000+
self.assertEqual(b"Hello!", response.data)
1001+
span = self.assert_span()
1002+
self.assertEqual(
1003+
span.attributes["http.request.header.x_test"], ("Value",)
1004+
)

0 commit comments

Comments
 (0)