feat(urllib3)!: refactor request hook parameters (#2711)

This commit is contained in:
Pavel Perestoronin 2024-07-26 00:08:26 +02:00 committed by GitHub
parent 6690ecc441
commit 4f985196c6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 67 additions and 18 deletions

View File

@ -67,6 +67,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
([#2726](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2726)) ([#2726](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2726))
- `opentelemetry-instrumentation-fastapi` Add dependency support for fastapi-slim - `opentelemetry-instrumentation-fastapi` Add dependency support for fastapi-slim
([#2702](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2702)) ([#2702](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2702))
- `opentelemetry-instrumentation-urllib3` improve request_hook, replacing `headers` and `body` parameters with a single `request_info: RequestInfo` parameter that now contains the `method` and `url` ([#2711](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2711))
### Fixed ### Fixed

View File

@ -47,14 +47,19 @@ The hooks can be configured as follows:
.. code:: python .. code:: python
# `request` is an instance of urllib3.connectionpool.HTTPConnectionPool def request_hook(
def request_hook(span, request): span: Span,
pass pool: urllib3.connectionpool.HTTPConnectionPool,
request_info: RequestInfo,
) -> Any:
...
# `request` is an instance of urllib3.connectionpool.HTTPConnectionPool def response_hook(
# `response` is an instance of urllib3.response.HTTPResponse span: Span,
def response_hook(span, request, response): pool: urllib3.connectionpool.HTTPConnectionPool,
pass response: urllib3.response.HTTPResponse,
) -> Any:
...
URLLib3Instrumentor().instrument( URLLib3Instrumentor().instrument(
request_hook=request_hook, response_hook=response_hook request_hook=request_hook, response_hook=response_hook
@ -81,6 +86,7 @@ API
import collections.abc import collections.abc
import io import io
import typing import typing
from dataclasses import dataclass
from timeit import default_timer from timeit import default_timer
from typing import Collection from typing import Collection
@ -135,14 +141,29 @@ from opentelemetry.util.http.httplib import set_ip_on_next_http_connection
_excluded_urls_from_env = get_excluded_urls("URLLIB3") _excluded_urls_from_env = get_excluded_urls("URLLIB3")
@dataclass
class RequestInfo:
"""Arguments that were passed to the ``urlopen()`` call."""
__slots__ = ("method", "url", "headers", "body")
# The type annotations here come from ``HTTPConnectionPool.urlopen()``.
method: str
url: str
headers: typing.Optional[typing.Mapping[str, str]]
body: typing.Union[
bytes, typing.IO[typing.Any], typing.Iterable[bytes], str, None
]
_UrlFilterT = typing.Optional[typing.Callable[[str], str]] _UrlFilterT = typing.Optional[typing.Callable[[str], str]]
_RequestHookT = typing.Optional[ _RequestHookT = typing.Optional[
typing.Callable[ typing.Callable[
[ [
Span, Span,
urllib3.connectionpool.HTTPConnectionPool, urllib3.connectionpool.HTTPConnectionPool,
typing.Dict, RequestInfo,
typing.Optional[str],
], ],
None, None,
] ]
@ -317,7 +338,16 @@ def _instrument(
span_name, kind=SpanKind.CLIENT, attributes=span_attributes span_name, kind=SpanKind.CLIENT, attributes=span_attributes
) as span, set_ip_on_next_http_connection(span): ) as span, set_ip_on_next_http_connection(span):
if callable(request_hook): if callable(request_hook):
request_hook(span, instance, headers, body) request_hook(
span,
instance,
RequestInfo(
method=method,
url=url,
headers=headers,
body=body,
),
)
inject(headers) inject(headers)
# TODO: add error handling to also set exception `error.type` in new semconv # TODO: add error handling to also set exception `error.type` in new semconv
with suppress_http_instrumentation(): with suppress_http_instrumentation():

View File

@ -27,7 +27,10 @@ from opentelemetry.instrumentation._semconv import (
_HTTPStabilityMode, _HTTPStabilityMode,
_OpenTelemetrySemanticConventionStability, _OpenTelemetrySemanticConventionStability,
) )
from opentelemetry.instrumentation.urllib3 import URLLib3Instrumentor from opentelemetry.instrumentation.urllib3 import (
RequestInfo,
URLLib3Instrumentor,
)
from opentelemetry.instrumentation.utils import ( from opentelemetry.instrumentation.utils import (
suppress_http_instrumentation, suppress_http_instrumentation,
suppress_instrumentation, suppress_instrumentation,
@ -42,6 +45,7 @@ from opentelemetry.semconv.attributes.url_attributes import URL_FULL
from opentelemetry.semconv.trace import SpanAttributes from opentelemetry.semconv.trace import SpanAttributes
from opentelemetry.test.mock_textmap import MockTextMapPropagator from opentelemetry.test.mock_textmap import MockTextMapPropagator
from opentelemetry.test.test_base import TestBase from opentelemetry.test.test_base import TestBase
from opentelemetry.trace import Span
from opentelemetry.util.http import get_excluded_urls from opentelemetry.util.http import get_excluded_urls
# pylint: disable=too-many-public-methods # pylint: disable=too-many-public-methods
@ -521,10 +525,10 @@ class TestURLLib3Instrumentor(TestBase):
self.assert_success_span(response, self.HTTP_URL) self.assert_success_span(response, self.HTTP_URL)
def test_hooks(self): def test_hooks(self):
def request_hook(span, request, body, headers): def request_hook(span, pool, request_info):
span.update_name("name set from hook") span.update_name("name set from hook")
def response_hook(span, request, response): def response_hook(span, pool, response):
span.set_attribute("response_hook_attr", "value") span.set_attribute("response_hook_attr", "value")
URLLib3Instrumentor().uninstrument() URLLib3Instrumentor().uninstrument()
@ -541,11 +545,17 @@ class TestURLLib3Instrumentor(TestBase):
self.assertEqual(span.attributes["response_hook_attr"], "value") self.assertEqual(span.attributes["response_hook_attr"], "value")
def test_request_hook_params(self): def test_request_hook_params(self):
def request_hook(span, request, headers, body): def request_hook(
span: Span,
_pool: urllib3.connectionpool.ConnectionPool,
request_info: RequestInfo,
) -> None:
span.set_attribute("request_hook_method", request_info.method)
span.set_attribute("request_hook_url", request_info.url)
span.set_attribute( span.set_attribute(
"request_hook_headers", json.dumps(dict(headers)) "request_hook_headers", json.dumps(dict(request_info.headers))
) )
span.set_attribute("request_hook_body", body) span.set_attribute("request_hook_body", request_info.body)
URLLib3Instrumentor().uninstrument() URLLib3Instrumentor().uninstrument()
URLLib3Instrumentor().instrument( URLLib3Instrumentor().instrument(
@ -564,6 +574,10 @@ class TestURLLib3Instrumentor(TestBase):
span = self.assert_span() span = self.assert_span()
self.assertEqual(span.attributes["request_hook_method"], "POST")
self.assertEqual(
span.attributes["request_hook_url"], "http://mock/status/200"
)
self.assertIn("request_hook_headers", span.attributes) self.assertIn("request_hook_headers", span.attributes)
self.assertEqual( self.assertEqual(
span.attributes["request_hook_headers"], json.dumps(headers) span.attributes["request_hook_headers"], json.dumps(headers)
@ -572,8 +586,12 @@ class TestURLLib3Instrumentor(TestBase):
self.assertEqual(span.attributes["request_hook_body"], body) self.assertEqual(span.attributes["request_hook_body"], body)
def test_request_positional_body(self): def test_request_positional_body(self):
def request_hook(span, request, headers, body): def request_hook(
span.set_attribute("request_hook_body", body) span: Span,
_pool: urllib3.connectionpool.ConnectionPool,
request_info: RequestInfo,
) -> None:
span.set_attribute("request_hook_body", request_info.body)
URLLib3Instrumentor().uninstrument() URLLib3Instrumentor().uninstrument()
URLLib3Instrumentor().instrument( URLLib3Instrumentor().instrument(