Skip to content

Commit 0f7ae6b

Browse files
Urllib3: extend request hook with request body and headers
1 parent c4639ee commit 0f7ae6b

File tree

2 files changed

+54
-3
lines changed

2 files changed

+54
-3
lines changed

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

+28-2
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,18 @@ def response_hook(span, request, response):
9292
_RequestHookT = typing.Optional[
9393
typing.Callable[[Span, urllib3.connectionpool.HTTPConnectionPool], None]
9494
]
95+
_ExtendedRequestHookT = typing.Optional[
96+
typing.Callable[
97+
[
98+
Span,
99+
urllib3.connectionpool.HTTPConnectionPool,
100+
# Request headers dict
101+
typing.Dict,
102+
# Request Body
103+
str,
104+
],
105+
None]
106+
]
95107
_ResponseHookT = typing.Optional[
96108
typing.Callable[
97109
[
@@ -139,7 +151,7 @@ def _uninstrument(self, **kwargs):
139151

140152
def _instrument(
141153
tracer,
142-
request_hook: _RequestHookT = None,
154+
request_hook: typing.Union[_RequestHookT, _ExtendedRequestHookT] = None,
143155
response_hook: _ResponseHookT = None,
144156
url_filter: _UrlFilterT = None,
145157
):
@@ -150,6 +162,7 @@ def instrumented_urlopen(wrapped, instance, args, kwargs):
150162
method = _get_url_open_arg("method", args, kwargs).upper()
151163
url = _get_url(instance, args, kwargs, url_filter)
152164
headers = _prepare_headers(kwargs)
165+
body = _get_url_open_arg("body", args, kwargs)
153166

154167
span_name = "HTTP {}".format(method.strip())
155168
span_attributes = {
@@ -161,7 +174,7 @@ def instrumented_urlopen(wrapped, instance, args, kwargs):
161174
span_name, kind=SpanKind.CLIENT, attributes=span_attributes
162175
) as span:
163176
if callable(request_hook):
164-
request_hook(span, instance)
177+
_call_request_hook(request_hook, span, instance, headers, body)
165178
inject(headers)
166179

167180
with _suppress_further_instrumentation():
@@ -179,6 +192,19 @@ def instrumented_urlopen(wrapped, instance, args, kwargs):
179192
)
180193

181194

195+
def _call_request_hook(request_hook: typing.Union[_RequestHookT, _ExtendedRequestHookT],
196+
span: Span,
197+
connection_pool: urllib3.connectionpool.HTTPConnectionPool,
198+
headers: typing.Dict,
199+
body: str):
200+
try:
201+
# First assume request_hook is a function of type _ExtendedRequestHookT
202+
request_hook(span, connection_pool, headers, body)
203+
except TypeError:
204+
# Fallback to call request_hook as a function of type _RequestHookT
205+
request_hook(span, connection_pool)
206+
207+
182208
def _get_url_open_arg(name: str, args: typing.List, kwargs: typing.Mapping):
183209
arg_idx = _URL_OPEN_ARG_TO_INDEX_MAPPING.get(name)
184210
if arg_idx is not None:

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

+26-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14-
14+
import json
1515
import typing
1616
from unittest import mock
1717

@@ -279,3 +279,28 @@ def response_hook(span, request, response):
279279
self.assertEqual(span.name, "name set from hook")
280280
self.assertIn("response_hook_attr", span.attributes)
281281
self.assertEqual(span.attributes["response_hook_attr"], "value")
282+
283+
def test_extended_request_hook(self):
284+
def extended_request_hook(span, request, headers, body):
285+
span.set_attribute("request_hook_headers", json.dumps(headers))
286+
span.set_attribute("request_hook_body", body)
287+
288+
URLLib3Instrumentor().uninstrument()
289+
URLLib3Instrumentor().instrument(
290+
request_hook=extended_request_hook,
291+
)
292+
293+
headers = {"header1": "value1", "header2": "value2"}
294+
body = "param1=1&param2=2"
295+
296+
pool = urllib3.HTTPConnectionPool("httpbin.org")
297+
response = pool.request("GET", "/status/200", body=body, headers=headers)
298+
299+
self.assertEqual(b"Hello!", response.data)
300+
301+
span = self.assert_span()
302+
303+
self.assertIn("request_hook_headers", span.attributes)
304+
self.assertEqual(span.attributes["request_hook_headers"], json.dumps(headers))
305+
self.assertIn("request_hook_body", span.attributes)
306+
self.assertEqual(span.attributes["request_hook_body"], body)

0 commit comments

Comments
 (0)