Support PEP 561 to opentelemetry-util-http (#3127)

This commit is contained in:
Marcelo Trylesinski
2025-01-09 22:21:19 +01:00
committed by GitHub
parent cf6d45e96c
commit 9af3136e7f
4 changed files with 37 additions and 21 deletions

View File

@ -27,6 +27,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
([#3148](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3148)) ([#3148](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3148))
- add support to Python 3.13 - add support to Python 3.13
([#3134](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3134)) ([#3134](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3134))
- `opentelemetry-util-http` Add `py.typed` file to enable PEP 561
([#3127](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3127))
### Fixed ### Fixed

View File

@ -19,7 +19,7 @@ from os import environ
from re import IGNORECASE as RE_IGNORECASE from re import IGNORECASE as RE_IGNORECASE
from re import compile as re_compile from re import compile as re_compile
from re import search from re import search
from typing import Callable, Iterable, Optional from typing import Callable, Iterable
from urllib.parse import urlparse, urlunparse from urllib.parse import urlparse, urlunparse
from opentelemetry.semconv.trace import SpanAttributes from opentelemetry.semconv.trace import SpanAttributes
@ -121,18 +121,16 @@ class SanitizeValue:
_root = r"OTEL_PYTHON_{}" _root = r"OTEL_PYTHON_{}"
def get_traced_request_attrs(instrumentation): def get_traced_request_attrs(instrumentation: str) -> list[str]:
traced_request_attrs = environ.get( traced_request_attrs = environ.get(
_root.format(f"{instrumentation}_TRACED_REQUEST_ATTRS"), [] _root.format(f"{instrumentation}_TRACED_REQUEST_ATTRS")
) )
if traced_request_attrs: if traced_request_attrs:
traced_request_attrs = [ return [
traced_request_attr.strip() traced_request_attr.strip()
for traced_request_attr in traced_request_attrs.split(",") for traced_request_attr in traced_request_attrs.split(",")
] ]
return []
return traced_request_attrs
def get_excluded_urls(instrumentation: str) -> ExcludeList: def get_excluded_urls(instrumentation: str) -> ExcludeList:
@ -193,7 +191,7 @@ def normalise_response_header_name(header: str) -> str:
return f"http.response.header.{key}" return f"http.response.header.{key}"
def sanitize_method(method: Optional[str]) -> Optional[str]: def sanitize_method(method: str | None) -> str | None:
if method is None: if method is None:
return None return None
method = method.upper() method = method.upper()

View File

@ -17,12 +17,14 @@ This library provides functionality to enrich HTTP client spans with IPs. It doe
not create spans on its own. not create spans on its own.
""" """
from __future__ import annotations
import contextlib import contextlib
import http.client import http.client
import logging import logging
import socket # pylint:disable=unused-import # Used for typing import socket # pylint:disable=unused-import # Used for typing
import typing import typing
from typing import Collection from typing import Any, Callable, Collection, TypedDict, cast
import wrapt import wrapt
@ -36,20 +38,22 @@ _STATE_KEY = "httpbase_instrumentation_state"
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
R = typing.TypeVar("R")
class HttpClientInstrumentor(BaseInstrumentor): class HttpClientInstrumentor(BaseInstrumentor):
def instrumentation_dependencies(self) -> Collection[str]: def instrumentation_dependencies(self) -> Collection[str]:
return () # This instruments http.client from stdlib; no extra deps. return () # This instruments http.client from stdlib; no extra deps.
def _instrument(self, **kwargs): def _instrument(self, **kwargs: Any):
"""Instruments the http.client module (not creating spans on its own)""" """Instruments the http.client module (not creating spans on its own)"""
_instrument() _instrument()
def _uninstrument(self, **kwargs): def _uninstrument(self, **kwargs: Any):
_uninstrument() _uninstrument()
def _remove_nonrecording(spanlist: typing.List[Span]): def _remove_nonrecording(spanlist: list[Span]) -> bool:
idx = len(spanlist) - 1 idx = len(spanlist) - 1
while idx >= 0: while idx >= 0:
if not spanlist[idx].is_recording(): if not spanlist[idx].is_recording():
@ -67,7 +71,9 @@ def _remove_nonrecording(spanlist: typing.List[Span]):
return True return True
def trysetip(conn: http.client.HTTPConnection, loglevel=logging.DEBUG) -> bool: def trysetip(
conn: http.client.HTTPConnection, loglevel: int = logging.DEBUG
) -> bool:
"""Tries to set the net.peer.ip semantic attribute on the current span from the given """Tries to set the net.peer.ip semantic attribute on the current span from the given
HttpConnection. HttpConnection.
@ -110,14 +116,17 @@ def trysetip(conn: http.client.HTTPConnection, loglevel=logging.DEBUG) -> bool:
def _instrumented_connect( def _instrumented_connect(
wrapped, instance: http.client.HTTPConnection, args, kwargs wrapped: Callable[..., R],
): instance: http.client.HTTPConnection,
args: tuple[Any, ...],
kwargs: dict[str, Any],
) -> R:
result = wrapped(*args, **kwargs) result = wrapped(*args, **kwargs)
trysetip(instance, loglevel=logging.WARNING) trysetip(instance, loglevel=logging.WARNING)
return result return result
def instrument_connect(module, name="connect"): def instrument_connect(module: type[Any], name: str = "connect"):
"""Instrument additional connect() methods, e.g. for derived classes.""" """Instrument additional connect() methods, e.g. for derived classes."""
wrapt.wrap_function_wrapper( wrapt.wrap_function_wrapper(
@ -129,8 +138,11 @@ def instrument_connect(module, name="connect"):
def _instrument(): def _instrument():
def instrumented_send( def instrumented_send(
wrapped, instance: http.client.HTTPConnection, args, kwargs wrapped: Callable[..., R],
): instance: http.client.HTTPConnection,
args: tuple[Any, ...],
kwargs: dict[str, Any],
) -> R:
done = trysetip(instance) done = trysetip(instance)
result = wrapped(*args, **kwargs) result = wrapped(*args, **kwargs)
if not done: if not done:
@ -147,8 +159,12 @@ def _instrument():
# No need to instrument HTTPSConnection, as it calls super().connect() # No need to instrument HTTPSConnection, as it calls super().connect()
def _getstate() -> typing.Optional[dict]: class _ConnectionState(TypedDict):
return context.get_value(_STATE_KEY) need_ip: list[Span]
def _getstate() -> _ConnectionState | None:
return cast(_ConnectionState, context.get_value(_STATE_KEY))
@contextlib.contextmanager @contextlib.contextmanager
@ -163,7 +179,7 @@ def set_ip_on_next_http_connection(span: Span):
finally: finally:
context.detach(token) context.detach(token)
else: else:
spans: typing.List[Span] = state["need_ip"] spans = state["need_ip"]
spans.append(span) spans.append(span)
try: try:
yield yield