about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/opentelemetry/util
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/opentelemetry/util')
-rw-r--r--.venv/lib/python3.12/site-packages/opentelemetry/util/_decorator.py85
-rw-r--r--.venv/lib/python3.12/site-packages/opentelemetry/util/_importlib_metadata.py37
-rw-r--r--.venv/lib/python3.12/site-packages/opentelemetry/util/_once.py47
-rw-r--r--.venv/lib/python3.12/site-packages/opentelemetry/util/_providers.py52
-rw-r--r--.venv/lib/python3.12/site-packages/opentelemetry/util/http/__init__.py257
-rw-r--r--.venv/lib/python3.12/site-packages/opentelemetry/util/http/httplib.py195
-rw-r--r--.venv/lib/python3.12/site-packages/opentelemetry/util/http/py.typed0
-rw-r--r--.venv/lib/python3.12/site-packages/opentelemetry/util/http/version.py15
-rw-r--r--.venv/lib/python3.12/site-packages/opentelemetry/util/py.typed0
-rw-r--r--.venv/lib/python3.12/site-packages/opentelemetry/util/re.py114
-rw-r--r--.venv/lib/python3.12/site-packages/opentelemetry/util/types.py57
11 files changed, 859 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/opentelemetry/util/_decorator.py b/.venv/lib/python3.12/site-packages/opentelemetry/util/_decorator.py
new file mode 100644
index 00000000..f574438f
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/opentelemetry/util/_decorator.py
@@ -0,0 +1,85 @@
+# Copyright The OpenTelemetry Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import asyncio
+import contextlib
+import functools
+from typing import TYPE_CHECKING, Callable, Generic, Iterator, TypeVar
+
+V = TypeVar("V")
+R = TypeVar("R")  # Return type
+Pargs = TypeVar("Pargs")  # Generic type for arguments
+Pkwargs = TypeVar("Pkwargs")  # Generic type for arguments
+
+# We don't actually depend on typing_extensions but we can use it in CI with this conditional
+# import. ParamSpec can be imported directly from typing after python 3.9 is dropped
+# https://peps.python.org/pep-0612/.
+if TYPE_CHECKING:
+    from typing_extensions import ParamSpec
+
+    P = ParamSpec("P")  # Generic type for all arguments
+
+
+class _AgnosticContextManager(
+    contextlib._GeneratorContextManager,  # type: ignore  # FIXME use contextlib._GeneratorContextManager[R] when we drop the python 3.8 support
+    Generic[R],
+):  # pylint: disable=protected-access
+    """Context manager that can decorate both async and sync functions.
+
+    This is an overridden version of the contextlib._GeneratorContextManager
+    class that will decorate async functions with an async context manager
+    to end the span AFTER the entire async function coroutine finishes.
+
+    Else it will report near zero spans durations for async functions.
+
+    We are overriding the contextlib._GeneratorContextManager class as
+    reimplementing it is a lot of code to maintain and this class (even if it's
+    marked as protected) doesn't seems like to be evolving a lot.
+
+    For more information, see:
+    https://github.com/open-telemetry/opentelemetry-python/pull/3633
+    """
+
+    def __enter__(self) -> R:
+        """Reimplementing __enter__ to avoid the type error.
+
+        The original __enter__ method returns Any type, but we want to return R.
+        """
+        del self.args, self.kwds, self.func  # type: ignore
+        try:
+            return next(self.gen)  # type: ignore
+        except StopIteration:
+            raise RuntimeError("generator didn't yield") from None
+
+    def __call__(self, func: V) -> V:  # pyright: ignore [reportIncompatibleMethodOverride]
+        if asyncio.iscoroutinefunction(func):
+
+            @functools.wraps(func)  # type: ignore
+            async def async_wrapper(*args: Pargs, **kwargs: Pkwargs) -> R:  # pyright: ignore [reportInvalidTypeVarUse]
+                with self._recreate_cm():  # type: ignore
+                    return await func(*args, **kwargs)  # type: ignore
+
+            return async_wrapper  # type: ignore
+        return super().__call__(func)  # type: ignore
+
+
+def _agnosticcontextmanager(
+    func: "Callable[P, Iterator[R]]",
+) -> "Callable[P, _AgnosticContextManager[R]]":
+    @functools.wraps(func)
+    def helper(*args: Pargs, **kwargs: Pkwargs) -> _AgnosticContextManager[R]:  # pyright: ignore [reportInvalidTypeVarUse]
+        return _AgnosticContextManager(func, args, kwargs)  # pyright: ignore [reportArgumentType]
+
+    # Ignoring the type to keep the original signature of the function
+    return helper  # type: ignore[return-value]
diff --git a/.venv/lib/python3.12/site-packages/opentelemetry/util/_importlib_metadata.py b/.venv/lib/python3.12/site-packages/opentelemetry/util/_importlib_metadata.py
new file mode 100644
index 00000000..2457630b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/opentelemetry/util/_importlib_metadata.py
@@ -0,0 +1,37 @@
+# Copyright The OpenTelemetry Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# FIXME: Use importlib.metadata when support for 3.11 is dropped if the rest of
+# the supported versions at that time have the same API.
+from importlib_metadata import (  # type: ignore
+    Distribution,
+    EntryPoint,
+    EntryPoints,
+    PackageNotFoundError,
+    distributions,
+    entry_points,
+    requires,
+    version,
+)
+
+__all__ = [
+    "entry_points",
+    "version",
+    "EntryPoint",
+    "EntryPoints",
+    "requires",
+    "Distribution",
+    "distributions",
+    "PackageNotFoundError",
+]
diff --git a/.venv/lib/python3.12/site-packages/opentelemetry/util/_once.py b/.venv/lib/python3.12/site-packages/opentelemetry/util/_once.py
new file mode 100644
index 00000000..c0cee43a
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/opentelemetry/util/_once.py
@@ -0,0 +1,47 @@
+# Copyright The OpenTelemetry Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from threading import Lock
+from typing import Callable
+
+
+class Once:
+    """Execute a function exactly once and block all callers until the function returns
+
+    Same as golang's `sync.Once <https://pkg.go.dev/sync#Once>`_
+    """
+
+    def __init__(self) -> None:
+        self._lock = Lock()
+        self._done = False
+
+    def do_once(self, func: Callable[[], None]) -> bool:
+        """Execute ``func`` if it hasn't been executed or return.
+
+        Will block until ``func`` has been called by one thread.
+
+        Returns:
+            Whether or not ``func`` was executed in this call
+        """
+
+        # fast path, try to avoid locking
+        if self._done:
+            return False
+
+        with self._lock:
+            if not self._done:
+                func()
+                self._done = True
+                return True
+        return False
diff --git a/.venv/lib/python3.12/site-packages/opentelemetry/util/_providers.py b/.venv/lib/python3.12/site-packages/opentelemetry/util/_providers.py
new file mode 100644
index 00000000..b748eadf
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/opentelemetry/util/_providers.py
@@ -0,0 +1,52 @@
+# Copyright The OpenTelemetry Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from logging import getLogger
+from os import environ
+from typing import TYPE_CHECKING, TypeVar, cast
+
+from opentelemetry.util._importlib_metadata import entry_points
+
+if TYPE_CHECKING:
+    from opentelemetry.metrics import MeterProvider
+    from opentelemetry.trace import TracerProvider
+
+Provider = TypeVar("Provider", "TracerProvider", "MeterProvider")
+
+logger = getLogger(__name__)
+
+
+def _load_provider(
+    provider_environment_variable: str, provider: str
+) -> Provider:  # type: ignore[type-var]
+    try:
+        provider_name = cast(
+            str,
+            environ.get(provider_environment_variable, f"default_{provider}"),
+        )
+
+        return cast(
+            Provider,
+            next(  # type: ignore
+                iter(  # type: ignore
+                    entry_points(  # type: ignore
+                        group=f"opentelemetry_{provider}",
+                        name=provider_name,
+                    )
+                )
+            ).load()(),
+        )
+    except Exception:  # pylint: disable=broad-exception-caught
+        logger.exception("Failed to load configured provider %s", provider)
+        raise
diff --git a/.venv/lib/python3.12/site-packages/opentelemetry/util/http/__init__.py b/.venv/lib/python3.12/site-packages/opentelemetry/util/http/__init__.py
new file mode 100644
index 00000000..71a6403a
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/opentelemetry/util/http/__init__.py
@@ -0,0 +1,257 @@
+# Copyright The OpenTelemetry Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import annotations
+
+from collections.abc import Mapping
+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, overload
+from urllib.parse import urlparse, urlunparse
+
+from opentelemetry.semconv.trace import SpanAttributes
+
+OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS = (
+    "OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS"
+)
+OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST = (
+    "OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST"
+)
+OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE = (
+    "OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE"
+)
+
+OTEL_PYTHON_INSTRUMENTATION_HTTP_CAPTURE_ALL_METHODS = (
+    "OTEL_PYTHON_INSTRUMENTATION_HTTP_CAPTURE_ALL_METHODS"
+)
+
+# List of recommended metrics attributes
+_duration_attrs = {
+    SpanAttributes.HTTP_METHOD,
+    SpanAttributes.HTTP_HOST,
+    SpanAttributes.HTTP_SCHEME,
+    SpanAttributes.HTTP_STATUS_CODE,
+    SpanAttributes.HTTP_FLAVOR,
+    SpanAttributes.HTTP_SERVER_NAME,
+    SpanAttributes.NET_HOST_NAME,
+    SpanAttributes.NET_HOST_PORT,
+}
+
+_active_requests_count_attrs = {
+    SpanAttributes.HTTP_METHOD,
+    SpanAttributes.HTTP_HOST,
+    SpanAttributes.HTTP_SCHEME,
+    SpanAttributes.HTTP_FLAVOR,
+    SpanAttributes.HTTP_SERVER_NAME,
+}
+
+
+class ExcludeList:
+    """Class to exclude certain paths (given as a list of regexes) from tracing requests"""
+
+    def __init__(self, excluded_urls: Iterable[str]):
+        self._excluded_urls = excluded_urls
+        if self._excluded_urls:
+            self._regex = re_compile("|".join(excluded_urls))
+
+    def url_disabled(self, url: str) -> bool:
+        return bool(self._excluded_urls and search(self._regex, url))
+
+
+class SanitizeValue:
+    """Class to sanitize (remove sensitive data from) certain headers (given as a list of regexes)"""
+
+    def __init__(self, sanitized_fields: Iterable[str]):
+        self._sanitized_fields = sanitized_fields
+        if self._sanitized_fields:
+            self._regex = re_compile("|".join(sanitized_fields), RE_IGNORECASE)
+
+    def sanitize_header_value(self, header: str, value: str) -> str:
+        return (
+            "[REDACTED]"
+            if (self._sanitized_fields and search(self._regex, header))
+            else value
+        )
+
+    def sanitize_header_values(
+        self,
+        headers: Mapping[str, str | list[str]],
+        header_regexes: list[str],
+        normalize_function: Callable[[str], str],
+    ) -> dict[str, list[str]]:
+        values: dict[str, list[str]] = {}
+
+        if header_regexes:
+            header_regexes_compiled = re_compile(
+                "|".join(header_regexes),
+                RE_IGNORECASE,
+            )
+
+            for header_name, header_value in headers.items():
+                if header_regexes_compiled.fullmatch(header_name):
+                    key = normalize_function(header_name.lower())
+                    if isinstance(header_value, str):
+                        values[key] = [
+                            self.sanitize_header_value(
+                                header_name, header_value
+                            )
+                        ]
+                    else:
+                        values[key] = [
+                            self.sanitize_header_value(header_name, value)
+                            for value in header_value
+                        ]
+
+        return values
+
+
+_root = r"OTEL_PYTHON_{}"
+
+
+def get_traced_request_attrs(instrumentation: str) -> list[str]:
+    traced_request_attrs = environ.get(
+        _root.format(f"{instrumentation}_TRACED_REQUEST_ATTRS")
+    )
+    if traced_request_attrs:
+        return [
+            traced_request_attr.strip()
+            for traced_request_attr in traced_request_attrs.split(",")
+        ]
+    return []
+
+
+def get_excluded_urls(instrumentation: str) -> ExcludeList:
+    # Get instrumentation-specific excluded URLs. If not set, retrieve them
+    # from generic variable.
+    excluded_urls = environ.get(
+        _root.format(f"{instrumentation}_EXCLUDED_URLS"),
+        environ.get(_root.format("EXCLUDED_URLS"), ""),
+    )
+
+    return parse_excluded_urls(excluded_urls)
+
+
+def parse_excluded_urls(excluded_urls: str) -> ExcludeList:
+    """
+    Small helper to put an arbitrary url list inside an ExcludeList
+    """
+    if excluded_urls:
+        excluded_url_list = [
+            excluded_url.strip() for excluded_url in excluded_urls.split(",")
+        ]
+    else:
+        excluded_url_list = []
+
+    return ExcludeList(excluded_url_list)
+
+
+def remove_url_credentials(url: str) -> str:
+    """Given a string url, remove the username and password only if it is a valid url"""
+
+    try:
+        parsed = urlparse(url)
+        if all([parsed.scheme, parsed.netloc]):  # checks for valid url
+            parsed_url = urlparse(url)
+            _, _, netloc = parsed.netloc.rpartition("@")
+            return urlunparse(
+                (
+                    parsed_url.scheme,
+                    netloc,
+                    parsed_url.path,
+                    parsed_url.params,
+                    parsed_url.query,
+                    parsed_url.fragment,
+                )
+            )
+    except ValueError:  # an unparsable url was passed
+        pass
+    return url
+
+
+def normalise_request_header_name(header: str) -> str:
+    key = header.lower().replace("-", "_")
+    return f"http.request.header.{key}"
+
+
+def normalise_response_header_name(header: str) -> str:
+    key = header.lower().replace("-", "_")
+    return f"http.response.header.{key}"
+
+
+@overload
+def sanitize_method(method: str) -> str: ...
+
+
+@overload
+def sanitize_method(method: None) -> None: ...
+
+
+def sanitize_method(method: str | None) -> str | None:
+    if method is None:
+        return None
+    method = method.upper()
+    if (
+        environ.get(OTEL_PYTHON_INSTRUMENTATION_HTTP_CAPTURE_ALL_METHODS)
+        or
+        # Based on https://www.rfc-editor.org/rfc/rfc7231#section-4.1 and https://www.rfc-editor.org/rfc/rfc5789#section-2.
+        method
+        in [
+            "GET",
+            "HEAD",
+            "POST",
+            "PUT",
+            "DELETE",
+            "CONNECT",
+            "OPTIONS",
+            "TRACE",
+            "PATCH",
+        ]
+    ):
+        return method
+    return "_OTHER"
+
+
+def get_custom_headers(env_var: str) -> list[str]:
+    custom_headers = environ.get(env_var, None)
+    if custom_headers:
+        return [
+            custom_headers.strip()
+            for custom_headers in custom_headers.split(",")
+        ]
+    return []
+
+
+def _parse_active_request_count_attrs(req_attrs):
+    active_requests_count_attrs = {
+        key: req_attrs[key]
+        for key in _active_requests_count_attrs.intersection(req_attrs.keys())
+    }
+    return active_requests_count_attrs
+
+
+def _parse_duration_attrs(req_attrs):
+    duration_attrs = {
+        key: req_attrs[key]
+        for key in _duration_attrs.intersection(req_attrs.keys())
+    }
+    return duration_attrs
+
+
+def _parse_url_query(url: str):
+    parsed_url = urlparse(url)
+    path = parsed_url.path
+    query_params = parsed_url.query
+    return path, query_params
diff --git a/.venv/lib/python3.12/site-packages/opentelemetry/util/http/httplib.py b/.venv/lib/python3.12/site-packages/opentelemetry/util/http/httplib.py
new file mode 100644
index 00000000..f375e2f7
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/opentelemetry/util/http/httplib.py
@@ -0,0 +1,195 @@
+# Copyright The OpenTelemetry Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+This library provides functionality to enrich HTTP client spans with IPs. It does
+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 Any, Callable, Collection, TypedDict, cast
+
+import wrapt
+
+from opentelemetry import context
+from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
+from opentelemetry.instrumentation.utils import unwrap
+from opentelemetry.semconv.trace import SpanAttributes
+from opentelemetry.trace.span import Span
+
+_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: Any):
+        """Instruments the http.client module (not creating spans on its own)"""
+        _instrument()
+
+    def _uninstrument(self, **kwargs: Any):
+        _uninstrument()
+
+
+def _remove_nonrecording(spanlist: list[Span]) -> bool:
+    idx = len(spanlist) - 1
+    while idx >= 0:
+        if not spanlist[idx].is_recording():
+            logger.debug("Span is not recording: %s", spanlist[idx])
+            islast = idx + 1 == len(spanlist)
+            if not islast:
+                spanlist[idx] = spanlist[len(spanlist) - 1]
+            spanlist.pop()
+            if islast:
+                if idx == 0:
+                    return False  # We removed everything
+                idx -= 1
+        else:
+            idx -= 1
+    return True
+
+
+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.
+
+    Returns False if the connection is not yet established, False if the IP was captured
+    or there is no need to capture it.
+    """
+
+    state = _getstate()
+    if not state:
+        return True
+    spanlist: typing.List[Span] = state.get("need_ip")
+    if not spanlist:
+        return True
+
+    # Remove all non-recording spans from the list.
+    if not _remove_nonrecording(spanlist):
+        return True
+
+    sock = "<property not accessed>"
+    try:
+        sock: typing.Optional[socket.socket] = conn.sock
+        logger.debug("Got socket: %s", sock)
+        if sock is None:
+            return False
+        addr = sock.getpeername()
+        if addr and addr[0]:
+            ip = addr[0]
+    except Exception:  # pylint:disable=broad-except
+        logger.log(
+            loglevel,
+            "Failed to get peer address from %s",
+            sock,
+            exc_info=True,
+            stack_info=True,
+        )
+    else:
+        for span in spanlist:
+            span.set_attribute(SpanAttributes.NET_PEER_IP, ip)
+    return True
+
+
+def _instrumented_connect(
+    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: type[Any], name: str = "connect"):
+    """Instrument additional connect() methods, e.g. for derived classes."""
+
+    wrapt.wrap_function_wrapper(
+        module,
+        name,
+        _instrumented_connect,
+    )
+
+
+def _instrument():
+    def instrumented_send(
+        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:
+            trysetip(instance, loglevel=logging.WARNING)
+        return result
+
+    wrapt.wrap_function_wrapper(
+        http.client.HTTPConnection,
+        "send",
+        instrumented_send,
+    )
+
+    instrument_connect(http.client.HTTPConnection)
+    # No need to instrument HTTPSConnection, as it calls super().connect()
+
+
+class _ConnectionState(TypedDict):
+    need_ip: list[Span]
+
+
+def _getstate() -> _ConnectionState | None:
+    return cast(_ConnectionState, context.get_value(_STATE_KEY))
+
+
+@contextlib.contextmanager
+def set_ip_on_next_http_connection(span: Span):
+    state = _getstate()
+    if not state:
+        token = context.attach(
+            context.set_value(_STATE_KEY, {"need_ip": [span]})
+        )
+        try:
+            yield
+        finally:
+            context.detach(token)
+    else:
+        spans = state["need_ip"]
+        spans.append(span)
+        try:
+            yield
+        finally:
+            try:
+                spans.remove(span)
+            except ValueError:  # Span might have become non-recording
+                pass
+
+
+def _uninstrument():
+    unwrap(http.client.HTTPConnection, "send")
+    unwrap(http.client.HTTPConnection, "connect")
diff --git a/.venv/lib/python3.12/site-packages/opentelemetry/util/http/py.typed b/.venv/lib/python3.12/site-packages/opentelemetry/util/http/py.typed
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/opentelemetry/util/http/py.typed
diff --git a/.venv/lib/python3.12/site-packages/opentelemetry/util/http/version.py b/.venv/lib/python3.12/site-packages/opentelemetry/util/http/version.py
new file mode 100644
index 00000000..7fb5b98b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/opentelemetry/util/http/version.py
@@ -0,0 +1,15 @@
+# Copyright The OpenTelemetry Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+__version__ = "0.52b1"
diff --git a/.venv/lib/python3.12/site-packages/opentelemetry/util/py.typed b/.venv/lib/python3.12/site-packages/opentelemetry/util/py.typed
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/opentelemetry/util/py.typed
diff --git a/.venv/lib/python3.12/site-packages/opentelemetry/util/re.py b/.venv/lib/python3.12/site-packages/opentelemetry/util/re.py
new file mode 100644
index 00000000..2436cb61
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/opentelemetry/util/re.py
@@ -0,0 +1,114 @@
+# Copyright The OpenTelemetry Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from logging import getLogger
+from re import compile, split
+from typing import Dict, List, Mapping
+from urllib.parse import unquote
+
+from deprecated import deprecated
+
+_logger = getLogger(__name__)
+
+# The following regexes reference this spec: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#specifying-headers-via-environment-variables
+
+# Optional whitespace
+_OWS = r"[ \t]*"
+# A key contains printable US-ASCII characters except: SP and "(),/:;<=>?@[\]{}
+_KEY_FORMAT = (
+    r"[\x21\x23-\x27\x2a\x2b\x2d\x2e\x30-\x39\x41-\x5a\x5e-\x7a\x7c\x7e]+"
+)
+# A value contains a URL-encoded UTF-8 string. The encoded form can contain any
+# printable US-ASCII characters (0x20-0x7f) other than SP, DEL, and ",;/
+_VALUE_FORMAT = r"[\x21\x23-\x2b\x2d-\x3a\x3c-\x5b\x5d-\x7e]*"
+# Like above with SP included
+_LIBERAL_VALUE_FORMAT = r"[\x20\x21\x23-\x2b\x2d-\x3a\x3c-\x5b\x5d-\x7e]*"
+# A key-value is key=value, with optional whitespace surrounding key and value
+_KEY_VALUE_FORMAT = rf"{_OWS}{_KEY_FORMAT}{_OWS}={_OWS}{_VALUE_FORMAT}{_OWS}"
+
+_HEADER_PATTERN = compile(_KEY_VALUE_FORMAT)
+_LIBERAL_HEADER_PATTERN = compile(
+    rf"{_OWS}{_KEY_FORMAT}{_OWS}={_OWS}{_LIBERAL_VALUE_FORMAT}{_OWS}"
+)
+_DELIMITER_PATTERN = compile(r"[ \t]*,[ \t]*")
+
+_BAGGAGE_PROPERTY_FORMAT = rf"{_KEY_VALUE_FORMAT}|{_OWS}{_KEY_FORMAT}{_OWS}"
+
+_INVALID_HEADER_ERROR_MESSAGE_STRICT_TEMPLATE = (
+    "Header format invalid! Header values in environment variables must be "
+    "URL encoded per the OpenTelemetry Protocol Exporter specification: %s"
+)
+
+_INVALID_HEADER_ERROR_MESSAGE_LIBERAL_TEMPLATE = (
+    "Header format invalid! Header values in environment variables must be "
+    "URL encoded per the OpenTelemetry Protocol Exporter specification or "
+    "a comma separated list of name=value occurrences: %s"
+)
+
+# pylint: disable=invalid-name
+
+
+@deprecated(version="1.15.0", reason="You should use parse_env_headers")  # type: ignore
+def parse_headers(s: str) -> Mapping[str, str]:
+    return parse_env_headers(s)
+
+
+def parse_env_headers(s: str, liberal: bool = False) -> Mapping[str, str]:
+    """
+    Parse ``s``, which is a ``str`` instance containing HTTP headers encoded
+    for use in ENV variables per the W3C Baggage HTTP header format at
+    https://www.w3.org/TR/baggage/#baggage-http-header-format, except that
+    additional semi-colon delimited metadata is not supported.
+    If ``liberal`` is True we try to parse ``s`` anyway to be more compatible
+    with other languages SDKs that accept non URL-encoded headers by default.
+    """
+    headers: Dict[str, str] = {}
+    headers_list: List[str] = split(_DELIMITER_PATTERN, s)
+    for header in headers_list:
+        if not header:  # empty string
+            continue
+        header_match = _HEADER_PATTERN.fullmatch(header.strip())
+        if not header_match and not liberal:
+            _logger.warning(
+                _INVALID_HEADER_ERROR_MESSAGE_STRICT_TEMPLATE, header
+            )
+            continue
+
+        if header_match:
+            match_string: str = header_match.string
+            # value may contain any number of `=`
+            name, value = match_string.split("=", 1)
+            name = unquote(name).strip().lower()
+            value = unquote(value).strip()
+            headers[name] = value
+        else:
+            # this is not url-encoded and does not match the spec but we decided to be
+            # liberal in what we accept to match other languages SDKs behaviour
+            liberal_header_match = _LIBERAL_HEADER_PATTERN.fullmatch(
+                header.strip()
+            )
+            if not liberal_header_match:
+                _logger.warning(
+                    _INVALID_HEADER_ERROR_MESSAGE_LIBERAL_TEMPLATE, header
+                )
+                continue
+
+            liberal_match_string: str = liberal_header_match.string
+            # value may contain any number of `=`
+            name, value = liberal_match_string.split("=", 1)
+            name = name.strip().lower()
+            value = value.strip()
+            headers[name] = value
+
+    return headers
diff --git a/.venv/lib/python3.12/site-packages/opentelemetry/util/types.py b/.venv/lib/python3.12/site-packages/opentelemetry/util/types.py
new file mode 100644
index 00000000..be311faf
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/opentelemetry/util/types.py
@@ -0,0 +1,57 @@
+# Copyright The OpenTelemetry Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from typing import Mapping, Optional, Sequence, Tuple, Union
+
+# This is the implementation of the "Any" type as specified by the specifications of OpenTelemetry data model for logs.
+# For more details, refer to the OTel specification:
+# https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/data-model.md#type-any
+AnyValue = Union[
+    str,
+    bool,
+    int,
+    float,
+    bytes,
+    Sequence["AnyValue"],
+    Mapping[str, "AnyValue"],
+    None,
+]
+
+AttributeValue = Union[
+    str,
+    bool,
+    int,
+    float,
+    Sequence[str],
+    Sequence[bool],
+    Sequence[int],
+    Sequence[float],
+]
+Attributes = Optional[Mapping[str, AttributeValue]]
+AttributesAsKey = Tuple[
+    Tuple[
+        str,
+        Union[
+            str,
+            bool,
+            int,
+            float,
+            Tuple[Optional[str], ...],
+            Tuple[Optional[bool], ...],
+            Tuple[Optional[int], ...],
+            Tuple[Optional[float], ...],
+        ],
+    ],
+    ...,
+]