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))
- add support to Python 3.13
([#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

View File

@ -19,7 +19,7 @@ from os import environ
from re import IGNORECASE as RE_IGNORECASE
from re import compile as re_compile
from re import search
from typing import Callable, Iterable, Optional
from typing import Callable, Iterable
from urllib.parse import urlparse, urlunparse
from opentelemetry.semconv.trace import SpanAttributes
@ -121,18 +121,16 @@ class SanitizeValue:
_root = r"OTEL_PYTHON_{}"
def get_traced_request_attrs(instrumentation):
def get_traced_request_attrs(instrumentation: str) -> list[str]:
traced_request_attrs = environ.get(
_root.format(f"{instrumentation}_TRACED_REQUEST_ATTRS"), []
_root.format(f"{instrumentation}_TRACED_REQUEST_ATTRS")
)
if traced_request_attrs:
traced_request_attrs = [
return [
traced_request_attr.strip()
for traced_request_attr in traced_request_attrs.split(",")
]
return traced_request_attrs
return []
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}"
def sanitize_method(method: Optional[str]) -> Optional[str]:
def sanitize_method(method: str | None) -> str | None:
if method is None:
return None
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.
"""
from __future__ import annotations
import contextlib
import http.client
import logging
import socket # pylint:disable=unused-import # Used for typing
import typing
from typing import Collection
from typing import Any, Callable, Collection, TypedDict, cast
import wrapt
@ -36,20 +38,22 @@ _STATE_KEY = "httpbase_instrumentation_state"
logger = logging.getLogger(__name__)
R = typing.TypeVar("R")
class HttpClientInstrumentor(BaseInstrumentor):
def instrumentation_dependencies(self) -> Collection[str]:
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)"""
_instrument()
def _uninstrument(self, **kwargs):
def _uninstrument(self, **kwargs: Any):
_uninstrument()
def _remove_nonrecording(spanlist: typing.List[Span]):
def _remove_nonrecording(spanlist: list[Span]) -> bool:
idx = len(spanlist) - 1
while idx >= 0:
if not spanlist[idx].is_recording():
@ -67,7 +71,9 @@ def _remove_nonrecording(spanlist: typing.List[Span]):
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
HttpConnection.
@ -110,14 +116,17 @@ def trysetip(conn: http.client.HTTPConnection, loglevel=logging.DEBUG) -> bool:
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)
trysetip(instance, loglevel=logging.WARNING)
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."""
wrapt.wrap_function_wrapper(
@ -129,8 +138,11 @@ def instrument_connect(module, name="connect"):
def _instrument():
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)
result = wrapped(*args, **kwargs)
if not done:
@ -147,8 +159,12 @@ def _instrument():
# No need to instrument HTTPSConnection, as it calls super().connect()
def _getstate() -> typing.Optional[dict]:
return context.get_value(_STATE_KEY)
class _ConnectionState(TypedDict):
need_ip: list[Span]
def _getstate() -> _ConnectionState | None:
return cast(_ConnectionState, context.get_value(_STATE_KEY))
@contextlib.contextmanager
@ -163,7 +179,7 @@ def set_ip_on_next_http_connection(span: Span):
finally:
context.detach(token)
else:
spans: typing.List[Span] = state["need_ip"]
spans = state["need_ip"]
spans.append(span)
try:
yield