about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/opentelemetry/instrumentation/requests
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/opentelemetry/instrumentation/requests')
-rw-r--r--.venv/lib/python3.12/site-packages/opentelemetry/instrumentation/requests/__init__.py469
-rw-r--r--.venv/lib/python3.12/site-packages/opentelemetry/instrumentation/requests/package.py20
-rw-r--r--.venv/lib/python3.12/site-packages/opentelemetry/instrumentation/requests/py.typed0
-rw-r--r--.venv/lib/python3.12/site-packages/opentelemetry/instrumentation/requests/version.py15
4 files changed, 504 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/opentelemetry/instrumentation/requests/__init__.py b/.venv/lib/python3.12/site-packages/opentelemetry/instrumentation/requests/__init__.py
new file mode 100644
index 00000000..1940e2f6
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/opentelemetry/instrumentation/requests/__init__.py
@@ -0,0 +1,469 @@
+# 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 allows tracing HTTP requests made by the
+`requests <https://requests.readthedocs.io/en/master/>`_ library.
+
+Usage
+-----
+
+.. code-block:: python
+
+    import requests
+    from opentelemetry.instrumentation.requests import RequestsInstrumentor
+
+    # You can optionally pass a custom TracerProvider to instrument().
+    RequestsInstrumentor().instrument()
+    response = requests.get(url="https://www.example.org/")
+
+Configuration
+-------------
+
+Request/Response hooks
+**********************
+
+The requests instrumentation supports extending tracing behavior with the help of
+request and response hooks. These are functions that are called back by the instrumentation
+right after a Span is created for a request and right before the span is finished processing a response respectively.
+The hooks can be configured as follows:
+
+.. code:: python
+
+    import requests
+    from opentelemetry.instrumentation.requests import RequestsInstrumentor
+
+    # `request_obj` is an instance of requests.PreparedRequest
+    def request_hook(span, request_obj):
+        pass
+
+    # `request_obj` is an instance of requests.PreparedRequest
+    # `response` is an instance of requests.Response
+    def response_hook(span, request_obj, response):
+        pass
+
+    RequestsInstrumentor().instrument(
+        request_hook=request_hook, response_hook=response_hook
+    )
+
+Exclude lists
+*************
+To exclude certain URLs from being tracked, set the environment variable ``OTEL_PYTHON_REQUESTS_EXCLUDED_URLS``
+(or ``OTEL_PYTHON_EXCLUDED_URLS`` as fallback) with comma delimited regexes representing which URLs to exclude.
+
+For example,
+
+::
+
+    export OTEL_PYTHON_REQUESTS_EXCLUDED_URLS="client/.*/info,healthcheck"
+
+will exclude requests such as ``https://site/client/123/info`` and ``https://site/xyz/healthcheck``.
+
+API
+---
+"""
+
+from __future__ import annotations
+
+import functools
+import types
+from timeit import default_timer
+from typing import Any, Callable, Collection, Optional
+from urllib.parse import urlparse
+
+from requests.models import PreparedRequest, Response
+from requests.sessions import Session
+from requests.structures import CaseInsensitiveDict
+
+from opentelemetry.instrumentation._semconv import (
+    _client_duration_attrs_new,
+    _client_duration_attrs_old,
+    _filter_semconv_duration_attrs,
+    _get_schema_url,
+    _OpenTelemetrySemanticConventionStability,
+    _OpenTelemetryStabilitySignalType,
+    _report_new,
+    _report_old,
+    _set_http_host_client,
+    _set_http_method,
+    _set_http_net_peer_name_client,
+    _set_http_network_protocol_version,
+    _set_http_peer_port_client,
+    _set_http_scheme,
+    _set_http_url,
+    _set_status,
+    _StabilityMode,
+)
+from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
+from opentelemetry.instrumentation.requests.package import _instruments
+from opentelemetry.instrumentation.requests.version import __version__
+from opentelemetry.instrumentation.utils import (
+    is_http_instrumentation_enabled,
+    suppress_http_instrumentation,
+)
+from opentelemetry.metrics import Histogram, get_meter
+from opentelemetry.propagate import inject
+from opentelemetry.semconv.attributes.error_attributes import ERROR_TYPE
+from opentelemetry.semconv.attributes.network_attributes import (
+    NETWORK_PEER_ADDRESS,
+    NETWORK_PEER_PORT,
+)
+from opentelemetry.semconv.metrics import MetricInstruments
+from opentelemetry.semconv.metrics.http_metrics import (
+    HTTP_CLIENT_REQUEST_DURATION,
+)
+from opentelemetry.trace import SpanKind, Tracer, get_tracer
+from opentelemetry.trace.span import Span
+from opentelemetry.util.http import (
+    ExcludeList,
+    get_excluded_urls,
+    parse_excluded_urls,
+    remove_url_credentials,
+    sanitize_method,
+)
+from opentelemetry.util.http.httplib import set_ip_on_next_http_connection
+
+_excluded_urls_from_env = get_excluded_urls("REQUESTS")
+
+_RequestHookT = Optional[Callable[[Span, PreparedRequest], None]]
+_ResponseHookT = Optional[Callable[[Span, PreparedRequest, Response], None]]
+
+
+def _set_http_status_code_attribute(
+    span,
+    status_code,
+    metric_attributes=None,
+    sem_conv_opt_in_mode=_StabilityMode.DEFAULT,
+):
+    status_code_str = str(status_code)
+    try:
+        status_code = int(status_code)
+    except ValueError:
+        status_code = -1
+    if metric_attributes is None:
+        metric_attributes = {}
+    # When we have durations we should set metrics only once
+    # Also the decision to include status code on a histogram should
+    # not be dependent on tracing decisions.
+    _set_status(
+        span,
+        metric_attributes,
+        status_code,
+        status_code_str,
+        server_span=False,
+        sem_conv_opt_in_mode=sem_conv_opt_in_mode,
+    )
+
+
+# pylint: disable=unused-argument
+# pylint: disable=R0915
+def _instrument(
+    tracer: Tracer,
+    duration_histogram_old: Histogram,
+    duration_histogram_new: Histogram,
+    request_hook: _RequestHookT = None,
+    response_hook: _ResponseHookT = None,
+    excluded_urls: ExcludeList | None = None,
+    sem_conv_opt_in_mode: _StabilityMode = _StabilityMode.DEFAULT,
+):
+    """Enables tracing of all requests calls that go through
+    :code:`requests.session.Session.request` (this includes
+    :code:`requests.get`, etc.)."""
+
+    # Since
+    # https://github.com/psf/requests/commit/d72d1162142d1bf8b1b5711c664fbbd674f349d1
+    # (v0.7.0, Oct 23, 2011), get, post, etc are implemented via request which
+    # again, is implemented via Session.request (`Session` was named `session`
+    # before v1.0.0, Dec 17, 2012, see
+    # https://github.com/psf/requests/commit/4e5c4a6ab7bb0195dececdd19bb8505b872fe120)
+
+    wrapped_send = Session.send
+
+    # pylint: disable-msg=too-many-locals,too-many-branches
+    @functools.wraps(wrapped_send)
+    def instrumented_send(
+        self: Session, request: PreparedRequest, **kwargs: Any
+    ):
+        if excluded_urls and excluded_urls.url_disabled(request.url):
+            return wrapped_send(self, request, **kwargs)
+
+        def get_or_create_headers():
+            request.headers = (
+                request.headers
+                if request.headers is not None
+                else CaseInsensitiveDict()
+            )
+            return request.headers
+
+        if not is_http_instrumentation_enabled():
+            return wrapped_send(self, request, **kwargs)
+
+        # See
+        # https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-spans.md#http-client
+        method = request.method
+        span_name = get_default_span_name(method)
+
+        url = remove_url_credentials(request.url)
+
+        span_attributes = {}
+        _set_http_method(
+            span_attributes,
+            method,
+            sanitize_method(method),
+            sem_conv_opt_in_mode,
+        )
+        _set_http_url(span_attributes, url, sem_conv_opt_in_mode)
+
+        metric_labels = {}
+        _set_http_method(
+            metric_labels,
+            method,
+            sanitize_method(method),
+            sem_conv_opt_in_mode,
+        )
+
+        try:
+            parsed_url = urlparse(url)
+            if parsed_url.scheme:
+                if _report_old(sem_conv_opt_in_mode):
+                    # TODO: Support opt-in for url.scheme in new semconv
+                    _set_http_scheme(
+                        metric_labels, parsed_url.scheme, sem_conv_opt_in_mode
+                    )
+            if parsed_url.hostname:
+                _set_http_host_client(
+                    metric_labels, parsed_url.hostname, sem_conv_opt_in_mode
+                )
+                _set_http_net_peer_name_client(
+                    metric_labels, parsed_url.hostname, sem_conv_opt_in_mode
+                )
+                if _report_new(sem_conv_opt_in_mode):
+                    _set_http_host_client(
+                        span_attributes,
+                        parsed_url.hostname,
+                        sem_conv_opt_in_mode,
+                    )
+                    # Use semconv library when available
+                    span_attributes[NETWORK_PEER_ADDRESS] = parsed_url.hostname
+            if parsed_url.port:
+                _set_http_peer_port_client(
+                    metric_labels, parsed_url.port, sem_conv_opt_in_mode
+                )
+                if _report_new(sem_conv_opt_in_mode):
+                    _set_http_peer_port_client(
+                        span_attributes, parsed_url.port, sem_conv_opt_in_mode
+                    )
+                    # Use semconv library when available
+                    span_attributes[NETWORK_PEER_PORT] = parsed_url.port
+        except ValueError:
+            pass
+
+        with tracer.start_as_current_span(
+            span_name, kind=SpanKind.CLIENT, attributes=span_attributes
+        ) as span, set_ip_on_next_http_connection(span):
+            exception = None
+            if callable(request_hook):
+                request_hook(span, request)
+
+            headers = get_or_create_headers()
+            inject(headers)
+
+            with suppress_http_instrumentation():
+                start_time = default_timer()
+                try:
+                    result = wrapped_send(
+                        self, request, **kwargs
+                    )  # *** PROCEED
+                except Exception as exc:  # pylint: disable=W0703
+                    exception = exc
+                    result = getattr(exc, "response", None)
+                finally:
+                    elapsed_time = max(default_timer() - start_time, 0)
+
+            if isinstance(result, Response):
+                span_attributes = {}
+                _set_http_status_code_attribute(
+                    span,
+                    result.status_code,
+                    metric_labels,
+                    sem_conv_opt_in_mode,
+                )
+
+                if result.raw is not None:
+                    version = getattr(result.raw, "version", None)
+                    if version:
+                        # Only HTTP/1 is supported by requests
+                        version_text = "1.1" if version == 11 else "1.0"
+                        _set_http_network_protocol_version(
+                            metric_labels, version_text, sem_conv_opt_in_mode
+                        )
+                        if _report_new(sem_conv_opt_in_mode):
+                            _set_http_network_protocol_version(
+                                span_attributes,
+                                version_text,
+                                sem_conv_opt_in_mode,
+                            )
+                for key, val in span_attributes.items():
+                    span.set_attribute(key, val)
+
+                if callable(response_hook):
+                    response_hook(span, request, result)
+
+            if exception is not None and _report_new(sem_conv_opt_in_mode):
+                span.set_attribute(ERROR_TYPE, type(exception).__qualname__)
+                metric_labels[ERROR_TYPE] = type(exception).__qualname__
+
+            if duration_histogram_old is not None:
+                duration_attrs_old = _filter_semconv_duration_attrs(
+                    metric_labels,
+                    _client_duration_attrs_old,
+                    _client_duration_attrs_new,
+                    _StabilityMode.DEFAULT,
+                )
+                duration_histogram_old.record(
+                    max(round(elapsed_time * 1000), 0),
+                    attributes=duration_attrs_old,
+                )
+            if duration_histogram_new is not None:
+                duration_attrs_new = _filter_semconv_duration_attrs(
+                    metric_labels,
+                    _client_duration_attrs_old,
+                    _client_duration_attrs_new,
+                    _StabilityMode.HTTP,
+                )
+                duration_histogram_new.record(
+                    elapsed_time, attributes=duration_attrs_new
+                )
+
+            if exception is not None:
+                raise exception.with_traceback(exception.__traceback__)
+
+        return result
+
+    instrumented_send.opentelemetry_instrumentation_requests_applied = True
+    Session.send = instrumented_send
+
+
+def _uninstrument():
+    """Disables instrumentation of :code:`requests` through this module.
+
+    Note that this only works if no other module also patches requests."""
+    _uninstrument_from(Session)
+
+
+def _uninstrument_from(instr_root, restore_as_bound_func: bool = False):
+    for instr_func_name in ("request", "send"):
+        instr_func = getattr(instr_root, instr_func_name)
+        if not getattr(
+            instr_func,
+            "opentelemetry_instrumentation_requests_applied",
+            False,
+        ):
+            continue
+
+        original = instr_func.__wrapped__  # pylint:disable=no-member
+        if restore_as_bound_func:
+            original = types.MethodType(original, instr_root)
+        setattr(instr_root, instr_func_name, original)
+
+
+def get_default_span_name(method: str) -> str:
+    """
+    Default implementation for name_callback, returns HTTP {method_name}.
+    https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/http/#name
+
+    Args:
+        method: string representing HTTP method
+    Returns:
+        span name
+    """
+    method = sanitize_method(method.strip())
+    if method == "_OTHER":
+        return "HTTP"
+    return method
+
+
+class RequestsInstrumentor(BaseInstrumentor):
+    """An instrumentor for requests
+    See `BaseInstrumentor`
+    """
+
+    def instrumentation_dependencies(self) -> Collection[str]:
+        return _instruments
+
+    def _instrument(self, **kwargs: Any):
+        """Instruments requests module
+
+        Args:
+            **kwargs: Optional arguments
+                ``tracer_provider``: a TracerProvider, defaults to global
+                ``request_hook``: An optional callback that is invoked right after a span is created.
+                ``response_hook``: An optional callback which is invoked right before the span is finished processing a response.
+                ``excluded_urls``: A string containing a comma-delimited
+                    list of regexes used to exclude URLs from tracking
+        """
+        semconv_opt_in_mode = _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode(
+            _OpenTelemetryStabilitySignalType.HTTP,
+        )
+        schema_url = _get_schema_url(semconv_opt_in_mode)
+        tracer_provider = kwargs.get("tracer_provider")
+        tracer = get_tracer(
+            __name__,
+            __version__,
+            tracer_provider,
+            schema_url=schema_url,
+        )
+        excluded_urls = kwargs.get("excluded_urls")
+        meter_provider = kwargs.get("meter_provider")
+        meter = get_meter(
+            __name__,
+            __version__,
+            meter_provider,
+            schema_url=schema_url,
+        )
+        duration_histogram_old = None
+        if _report_old(semconv_opt_in_mode):
+            duration_histogram_old = meter.create_histogram(
+                name=MetricInstruments.HTTP_CLIENT_DURATION,
+                unit="ms",
+                description="measures the duration of the outbound HTTP request",
+            )
+        duration_histogram_new = None
+        if _report_new(semconv_opt_in_mode):
+            duration_histogram_new = meter.create_histogram(
+                name=HTTP_CLIENT_REQUEST_DURATION,
+                unit="s",
+                description="Duration of HTTP client requests.",
+            )
+        _instrument(
+            tracer,
+            duration_histogram_old,
+            duration_histogram_new,
+            request_hook=kwargs.get("request_hook"),
+            response_hook=kwargs.get("response_hook"),
+            excluded_urls=(
+                _excluded_urls_from_env
+                if excluded_urls is None
+                else parse_excluded_urls(excluded_urls)
+            ),
+            sem_conv_opt_in_mode=semconv_opt_in_mode,
+        )
+
+    def _uninstrument(self, **kwargs: Any):
+        _uninstrument()
+
+    @staticmethod
+    def uninstrument_session(session: Session):
+        """Disables instrumentation on the session object."""
+        _uninstrument_from(session, restore_as_bound_func=True)
diff --git a/.venv/lib/python3.12/site-packages/opentelemetry/instrumentation/requests/package.py b/.venv/lib/python3.12/site-packages/opentelemetry/instrumentation/requests/package.py
new file mode 100644
index 00000000..9cd93a91
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/opentelemetry/instrumentation/requests/package.py
@@ -0,0 +1,20 @@
+# 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.
+
+
+_instruments = ("requests ~= 2.0",)
+
+_supports_metrics = True
+
+_semconv_status = "migration"
diff --git a/.venv/lib/python3.12/site-packages/opentelemetry/instrumentation/requests/py.typed b/.venv/lib/python3.12/site-packages/opentelemetry/instrumentation/requests/py.typed
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/opentelemetry/instrumentation/requests/py.typed
diff --git a/.venv/lib/python3.12/site-packages/opentelemetry/instrumentation/requests/version.py b/.venv/lib/python3.12/site-packages/opentelemetry/instrumentation/requests/version.py
new file mode 100644
index 00000000..7fb5b98b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/opentelemetry/instrumentation/requests/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"