From 705ba2a44e70a4ebfda2abc65e7db38b9c4c61f4 Mon Sep 17 00:00:00 2001 From: jerevoss Date: Mon, 22 May 2023 15:14:51 -0700 Subject: [PATCH 01/10] Added vendored pacakges --- .../monitor/opentelemetry/vendor/__init__.py | 5 + .../vendor/opentelemetry/__init__.py | 5 + .../opentelemetry/instrumentation/__init__.py | 5 + .../instrumentation/asgi/__init__.py | 661 ++++++++++++++++++ .../instrumentation/asgi/package.py | 16 + .../instrumentation/asgi/version.py | 15 + .../instrumentation/dbapi/__init__.py | 495 +++++++++++++ .../instrumentation/dbapi/package.py | 16 + .../instrumentation/dbapi/version.py | 17 + .../instrumentation/django/__init__.py | 387 ++++++++++ .../django/environment_variables.py | 15 + .../django/middleware/__init__.py | 0 .../django/middleware/otel_middleware.py | 400 +++++++++++ .../middleware/sqlcommenter_middleware.py | 119 ++++ .../instrumentation/django/package.py | 17 + .../instrumentation/django/version.py | 15 + .../instrumentation/fastapi/__init__.py | 350 ++++++++++ .../instrumentation/fastapi/package.py | 18 + .../instrumentation/fastapi/version.py | 15 + .../instrumentation/flask/__init__.py | 649 +++++++++++++++++ .../instrumentation/flask/package.py | 18 + .../instrumentation/flask/version.py | 15 + .../instrumentation/psycopg2/__init__.py | 269 +++++++ .../instrumentation/psycopg2/package.py | 16 + .../instrumentation/psycopg2/version.py | 15 + .../instrumentation/requests/__init__.py | 306 ++++++++ .../instrumentation/requests/package.py | 18 + .../instrumentation/requests/version.py | 15 + .../instrumentation/urllib/__init__.py | 310 ++++++++ .../instrumentation/urllib/package.py | 18 + .../instrumentation/urllib/version.py | 17 + .../instrumentation/urllib3/__init__.py | 361 ++++++++++ .../instrumentation/urllib3/package.py | 18 + .../instrumentation/urllib3/version.py | 15 + .../instrumentation/util/http/__init__.py | 213 ++++++ .../instrumentation/util/http/httplib.py | 179 +++++ .../instrumentation/util/http/version.py | 15 + .../instrumentation/wsgi/__init__.py | 594 ++++++++++++++++ .../instrumentation/wsgi/package.py | 18 + .../instrumentation/wsgi/version.py | 15 + .../vendor/opentelemetry/util/__init__.py | 5 + .../opentelemetry/util/http/__init__.py | 213 ++++++ .../vendor/opentelemetry/util/http/httplib.py | 179 +++++ .../vendor/opentelemetry/util/http/version.py | 15 + tox.ini | 6 +- 45 files changed, 6080 insertions(+), 3 deletions(-) create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/__init__.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/__init__.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/__init__.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/asgi/__init__.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/asgi/package.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/asgi/version.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/dbapi/__init__.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/dbapi/package.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/dbapi/version.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/__init__.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/environment_variables.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/middleware/__init__.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/middleware/otel_middleware.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/middleware/sqlcommenter_middleware.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/package.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/version.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/fastapi/__init__.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/fastapi/package.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/fastapi/version.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/flask/__init__.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/flask/package.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/flask/version.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/psycopg2/__init__.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/psycopg2/package.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/psycopg2/version.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/requests/__init__.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/requests/package.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/requests/version.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib/__init__.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib/package.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib/version.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib3/__init__.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib3/package.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib3/version.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/util/http/__init__.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/util/http/httplib.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/util/http/version.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/wsgi/__init__.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/wsgi/package.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/wsgi/version.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/util/__init__.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/util/http/__init__.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/util/http/httplib.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/util/http/version.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/__init__.py new file mode 100644 index 00000000..0bdee620 --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/__init__.py @@ -0,0 +1,5 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License in the project root for +# license information. +# -------------------------------------------------------------------------- diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/__init__.py new file mode 100644 index 00000000..0bdee620 --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/__init__.py @@ -0,0 +1,5 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License in the project root for +# license information. +# -------------------------------------------------------------------------- diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/__init__.py new file mode 100644 index 00000000..0bdee620 --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/__init__.py @@ -0,0 +1,5 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License in the project root for +# license information. +# -------------------------------------------------------------------------- diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/asgi/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/asgi/__init__.py new file mode 100644 index 00000000..da78196d --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/asgi/__init__.py @@ -0,0 +1,661 @@ +# 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. +# pylint: disable=too-many-locals + +""" +The opentelemetry-instrumentation-asgi package provides an ASGI middleware that can be used +on any ASGI framework (such as Django-channels / Quart) to track request timing through OpenTelemetry. + +Usage (Quart) +------------- + +.. code-block:: python + + from quart import Quart + from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware + + app = Quart(__name__) + app.asgi_app = OpenTelemetryMiddleware(app.asgi_app) + + @app.route("/") + async def hello(): + return "Hello!" + + if __name__ == "__main__": + app.run(debug=True) + + +Usage (Django 3.0) +------------------ + +Modify the application's ``asgi.py`` file as shown below. + +.. code-block:: python + + import os + from django.core.asgi import get_asgi_application + from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware + + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'asgi_example.settings') + + application = get_asgi_application() + application = OpenTelemetryMiddleware(application) + + +Usage (Raw ASGI) +---------------- + +.. code-block:: python + + from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware + + app = ... # An ASGI application. + app = OpenTelemetryMiddleware(app) + + +Configuration +------------- + +Request/Response hooks +********************** + +This instrumentation supports request and response hooks. These are functions that get called +right after a span is created for a request and right before the span is finished for the response. + +- The server request hook is passed a server span and ASGI scope object for every incoming request. +- The client request hook is called with the internal span and an ASGI scope when the method ``receive`` is called. +- The client response hook is called with the internal span and an ASGI event when the method ``send`` is called. + +For example, + +.. code-block:: python + + def server_request_hook(span: Span, scope: dict): + if span and span.is_recording(): + span.set_attribute("custom_user_attribute_from_request_hook", "some-value") + + def client_request_hook(span: Span, scope: dict): + if span and span.is_recording(): + span.set_attribute("custom_user_attribute_from_client_request_hook", "some-value") + + def client_response_hook(span: Span, message: dict): + if span and span.is_recording(): + span.set_attribute("custom_user_attribute_from_response_hook", "some-value") + + OpenTelemetryMiddleware().(application, server_request_hook=server_request_hook, client_request_hook=client_request_hook, client_response_hook=client_response_hook) + +Capture HTTP request and response headers +***************************************** +You can configure the agent to capture specified HTTP headers as span attributes, according to the +`semantic convention `_. + +Request headers +*************** +To capture HTTP request headers as span attributes, set the environment variable +``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST`` to a comma delimited list of HTTP header names. + +For example, +:: + + export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST="content-type,custom_request_header" + +will extract ``content-type`` and ``custom_request_header`` from the request headers and add them as span attributes. + +Request header names in ASGI are case-insensitive. So, giving the header name as ``CUStom-Header`` in the environment +variable will capture the header named ``custom-header``. + +Regular expressions may also be used to match multiple headers that correspond to the given pattern. For example: +:: + + export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST="Accept.*,X-.*" + +Would match all request headers that start with ``Accept`` and ``X-``. + +To capture all request headers, set ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST`` to ``".*"``. +:: + + export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST=".*" + +The name of the added span attribute will follow the format ``http.request.header.`` where ```` +is the normalized HTTP header name (lowercase, with ``-`` replaced by ``_``). The value of the attribute will be a +single item list containing all the header values. + +For example: +``http.request.header.custom_request_header = [","]`` + +Response headers +**************** +To capture HTTP response headers as span attributes, set the environment variable +``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE`` to a comma delimited list of HTTP header names. + +For example, +:: + + export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE="content-type,custom_response_header" + +will extract ``content-type`` and ``custom_response_header`` from the response headers and add them as span attributes. + +Response header names in ASGI are case-insensitive. So, giving the header name as ``CUStom-Header`` in the environment +variable will capture the header named ``custom-header``. + +Regular expressions may also be used to match multiple headers that correspond to the given pattern. For example: +:: + + export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE="Content.*,X-.*" + +Would match all response headers that start with ``Content`` and ``X-``. + +To capture all response headers, set ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE`` to ``".*"``. +:: + + export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE=".*" + +The name of the added span attribute will follow the format ``http.response.header.`` where ```` +is the normalized HTTP header name (lowercase, with ``-`` replaced by ``_``). The value of the attribute will be a +single item list containing all the header values. + +For example: +``http.response.header.custom_response_header = [","]`` + +Sanitizing headers +****************** +In order to prevent storing sensitive data such as personally identifiable information (PII), session keys, passwords, +etc, set the environment variable ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS`` +to a comma delimited list of HTTP header names to be sanitized. Regexes may be used, and all header names will be +matched in a case-insensitive manner. + +For example, +:: + + export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS=".*session.*,set-cookie" + +will replace the value of headers such as ``session-id`` and ``set-cookie`` with ``[REDACTED]`` in the span. + +Note: + The environment variable names used to capture HTTP headers are still experimental, and thus are subject to change. + +API +--- +""" + +import typing +import urllib +from functools import wraps +from timeit import default_timer +from typing import Tuple + +from asgiref.compatibility import guarantee_single_callable + +from opentelemetry import context, trace +from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.asgi.version import ( + __version__, +) # noqa +from opentelemetry.instrumentation.propagators import ( + get_global_response_propagator, +) +from opentelemetry.instrumentation.utils import ( + _start_internal_or_server_span, + http_status_to_status_code, +) +from opentelemetry.metrics import get_meter +from opentelemetry.propagators.textmap import Getter, Setter +from opentelemetry.semconv.metrics import MetricInstruments +from opentelemetry.semconv.trace import SpanAttributes +from opentelemetry.trace import Span, set_span_in_context +from opentelemetry.trace.status import Status, StatusCode +from azure.monitor.opentelemetry.vendor.opentelemetry.util.http import ( + OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS, + OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST, + OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE, + SanitizeValue, + _parse_active_request_count_attrs, + _parse_duration_attrs, + get_custom_headers, + normalise_request_header_name, + normalise_response_header_name, + remove_url_credentials, +) + +_ServerRequestHookT = typing.Optional[typing.Callable[[Span, dict], None]] +_ClientRequestHookT = typing.Optional[typing.Callable[[Span, dict], None]] +_ClientResponseHookT = typing.Optional[typing.Callable[[Span, dict], None]] + + +class ASGIGetter(Getter[dict]): + def get( + self, carrier: dict, key: str + ) -> typing.Optional[typing.List[str]]: + """Getter implementation to retrieve a HTTP header value from the ASGI + scope. + + Args: + carrier: ASGI scope object + key: header name in scope + Returns: + A list with a single string with the header value if it exists, + else None. + """ + headers = carrier.get("headers") + if not headers: + return None + + # ASGI header keys are in lower case + key = key.lower() + decoded = [ + _value.decode("utf8") + for (_key, _value) in headers + if _key.decode("utf8").lower() == key + ] + if not decoded: + return None + return decoded + + def keys(self, carrier: dict) -> typing.List[str]: + headers = carrier.get("headers") or [] + return [_key.decode("utf8") for (_key, _value) in headers] + + +asgi_getter = ASGIGetter() + + +class ASGISetter(Setter[dict]): + def set( + self, carrier: dict, key: str, value: str + ) -> None: # pylint: disable=no-self-use + """Sets response header values on an ASGI scope according to `the spec `_. + + Args: + carrier: ASGI scope object + key: response header name to set + value: response header value + Returns: + None + """ + headers = carrier.get("headers") + if not headers: + headers = [] + carrier["headers"] = headers + + headers.append([key.lower().encode(), value.encode()]) + + +asgi_setter = ASGISetter() + + +def collect_request_attributes(scope): + """Collects HTTP request attributes from the ASGI scope and returns a + dictionary to be used as span creation attributes.""" + server_host, port, http_url = get_host_port_url_tuple(scope) + query_string = scope.get("query_string") + if query_string and http_url: + if isinstance(query_string, bytes): + query_string = query_string.decode("utf8") + http_url += "?" + urllib.parse.unquote(query_string) + + result = { + SpanAttributes.HTTP_SCHEME: scope.get("scheme"), + SpanAttributes.HTTP_HOST: server_host, + SpanAttributes.NET_HOST_PORT: port, + SpanAttributes.HTTP_FLAVOR: scope.get("http_version"), + SpanAttributes.HTTP_TARGET: scope.get("path"), + SpanAttributes.HTTP_URL: remove_url_credentials(http_url), + } + http_method = scope.get("method") + if http_method: + result[SpanAttributes.HTTP_METHOD] = http_method + + http_host_value_list = asgi_getter.get(scope, "host") + if http_host_value_list: + result[SpanAttributes.HTTP_SERVER_NAME] = ",".join( + http_host_value_list + ) + http_user_agent = asgi_getter.get(scope, "user-agent") + if http_user_agent: + result[SpanAttributes.HTTP_USER_AGENT] = http_user_agent[0] + + if "client" in scope and scope["client"] is not None: + result[SpanAttributes.NET_PEER_IP] = scope.get("client")[0] + result[SpanAttributes.NET_PEER_PORT] = scope.get("client")[1] + + # remove None values + result = {k: v for k, v in result.items() if v is not None} + + return result + + +def collect_custom_request_headers_attributes(scope): + """returns custom HTTP request headers to be added into SERVER span as span attributes + Refer specification https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#http-request-and-response-headers + """ + + sanitize = SanitizeValue( + get_custom_headers( + OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS + ) + ) + + # Decode headers before processing. + headers = { + _key.decode("utf8"): _value.decode("utf8") + for (_key, _value) in scope.get("headers") + } + + return sanitize.sanitize_header_values( + headers, + get_custom_headers( + OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST + ), + normalise_request_header_name, + ) + + +def collect_custom_response_headers_attributes(message): + """returns custom HTTP response headers to be added into SERVER span as span attributes + Refer specification https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#http-request-and-response-headers + """ + + sanitize = SanitizeValue( + get_custom_headers( + OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS + ) + ) + + # Decode headers before processing. + headers = { + _key.decode("utf8"): _value.decode("utf8") + for (_key, _value) in message.get("headers") + } + + return sanitize.sanitize_header_values( + headers, + get_custom_headers( + OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE + ), + normalise_response_header_name, + ) + + +def get_host_port_url_tuple(scope): + """Returns (host, port, full_url) tuple.""" + server = scope.get("server") or ["0.0.0.0", 80] + port = server[1] + server_host = server[0] + (":" + str(port) if str(port) != "80" else "") + full_path = scope.get("root_path", "") + scope.get("path", "") + http_url = scope.get("scheme", "http") + "://" + server_host + full_path + return server_host, port, http_url + + +def set_status_code(span, status_code): + """Adds HTTP response attributes to span using the status_code argument.""" + if not span.is_recording(): + return + try: + status_code = int(status_code) + except ValueError: + span.set_status( + Status( + StatusCode.ERROR, + "Non-integer HTTP status: " + repr(status_code), + ) + ) + else: + span.set_attribute(SpanAttributes.HTTP_STATUS_CODE, status_code) + span.set_status( + Status(http_status_to_status_code(status_code, server_span=True)) + ) + + +def get_default_span_details(scope: dict) -> Tuple[str, dict]: + """Default implementation for get_default_span_details + Args: + scope: the ASGI scope dictionary + Returns: + a tuple of the span name, and any attributes to attach to the span. + """ + span_name = ( + scope.get("path", "").strip() + or f"HTTP {scope.get('method', '').strip()}" + ) + + return span_name, {} + + +def _collect_target_attribute( + scope: typing.Dict[str, typing.Any] +) -> typing.Optional[str]: + """ + Returns the target path as defined by the Semantic Conventions. + + This value is suitable to use in metrics as it should replace concrete + values with a parameterized name. Example: /api/users/{user_id} + + Refer to the specification + https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/http-metrics.md#parameterized-attributes + + Note: this function requires specific code for each framework, as there's no + standard attribute to use. + """ + # FastAPI + root_path = scope.get("root_path", "") + + route = scope.get("route") + path_format = getattr(route, "path_format", None) + if path_format: + return f"{root_path}{path_format}" + + return None + + +class OpenTelemetryMiddleware: + """The ASGI application middleware. + + This class is an ASGI middleware that starts and annotates spans for any + requests it is invoked with. + + Args: + app: The ASGI application callable to forward requests to. + default_span_details: Callback which should return a string and a tuple, representing the desired default span name and a + dictionary with any additional span attributes to set. + Optional: Defaults to get_default_span_details. + server_request_hook: Optional callback which is called with the server span and ASGI + scope object for every incoming request. + client_request_hook: Optional callback which is called with the internal span and an ASGI + scope which is sent as a dictionary for when the method receive is called. + client_response_hook: Optional callback which is called with the internal span and an ASGI + event which is sent as a dictionary for when the method send is called. + tracer_provider: The optional tracer provider to use. If omitted + the current globally configured one is used. + """ + + # pylint: disable=too-many-branches + def __init__( + self, + app, + excluded_urls=None, + default_span_details=None, + server_request_hook: _ServerRequestHookT = None, + client_request_hook: _ClientRequestHookT = None, + client_response_hook: _ClientResponseHookT = None, + tracer_provider=None, + meter_provider=None, + meter=None, + ): + self.app = guarantee_single_callable(app) + self.tracer = trace.get_tracer(__name__, __version__, tracer_provider) + self.meter = ( + get_meter(__name__, __version__, meter_provider) + if meter is None + else meter + ) + self.duration_histogram = self.meter.create_histogram( + name=MetricInstruments.HTTP_SERVER_DURATION, + unit="ms", + description="measures the duration of the inbound HTTP request", + ) + self.active_requests_counter = self.meter.create_up_down_counter( + name=MetricInstruments.HTTP_SERVER_ACTIVE_REQUESTS, + unit="requests", + description="measures the number of concurrent HTTP requests that are currently in-flight", + ) + self.excluded_urls = excluded_urls + self.default_span_details = ( + default_span_details or get_default_span_details + ) + self.server_request_hook = server_request_hook + self.client_request_hook = client_request_hook + self.client_response_hook = client_response_hook + + async def __call__(self, scope, receive, send): + """The ASGI application + + Args: + scope: An ASGI environment. + receive: An awaitable callable yielding dictionaries + send: An awaitable callable taking a single dictionary as argument. + """ + if scope["type"] not in ("http", "websocket"): + return await self.app(scope, receive, send) + + _, _, url = get_host_port_url_tuple(scope) + if self.excluded_urls and self.excluded_urls.url_disabled(url): + return await self.app(scope, receive, send) + + span_name, additional_attributes = self.default_span_details(scope) + + span, token = _start_internal_or_server_span( + tracer=self.tracer, + span_name=span_name, + start_time=None, + context_carrier=scope, + context_getter=asgi_getter, + ) + attributes = collect_request_attributes(scope) + attributes.update(additional_attributes) + active_requests_count_attrs = _parse_active_request_count_attrs( + attributes + ) + duration_attrs = _parse_duration_attrs(attributes) + + if scope["type"] == "http": + self.active_requests_counter.add(1, active_requests_count_attrs) + try: + with trace.use_span(span, end_on_exit=True) as current_span: + if current_span.is_recording(): + for key, value in attributes.items(): + current_span.set_attribute(key, value) + + if current_span.kind == trace.SpanKind.SERVER: + custom_attributes = ( + collect_custom_request_headers_attributes(scope) + ) + if len(custom_attributes) > 0: + current_span.set_attributes(custom_attributes) + + if callable(self.server_request_hook): + self.server_request_hook(current_span, scope) + + otel_receive = self._get_otel_receive( + span_name, scope, receive + ) + + otel_send = self._get_otel_send( + current_span, + span_name, + scope, + send, + duration_attrs, + ) + start = default_timer() + + await self.app(scope, otel_receive, otel_send) + finally: + if scope["type"] == "http": + target = _collect_target_attribute(scope) + if target: + duration_attrs[SpanAttributes.HTTP_TARGET] = target + duration = max(round((default_timer() - start) * 1000), 0) + self.duration_histogram.record(duration, duration_attrs) + self.active_requests_counter.add( + -1, active_requests_count_attrs + ) + if token: + context.detach(token) + + # pylint: enable=too-many-branches + + def _get_otel_receive(self, server_span_name, scope, receive): + @wraps(receive) + async def otel_receive(): + with self.tracer.start_as_current_span( + " ".join((server_span_name, scope["type"], "receive")) + ) as receive_span: + if callable(self.client_request_hook): + self.client_request_hook(receive_span, scope) + message = await receive() + if receive_span.is_recording(): + if message["type"] == "websocket.receive": + set_status_code(receive_span, 200) + receive_span.set_attribute("type", message["type"]) + return message + + return otel_receive + + def _get_otel_send( + self, server_span, server_span_name, scope, send, duration_attrs + ): + @wraps(send) + async def otel_send(message): + with self.tracer.start_as_current_span( + " ".join((server_span_name, scope["type"], "send")) + ) as send_span: + if callable(self.client_response_hook): + self.client_response_hook(send_span, message) + if send_span.is_recording(): + if message["type"] == "http.response.start": + status_code = message["status"] + duration_attrs[ + SpanAttributes.HTTP_STATUS_CODE + ] = status_code + set_status_code(server_span, status_code) + set_status_code(send_span, status_code) + elif message["type"] == "websocket.send": + set_status_code(server_span, 200) + set_status_code(send_span, 200) + send_span.set_attribute("type", message["type"]) + if ( + server_span.is_recording() + and server_span.kind == trace.SpanKind.SERVER + and "headers" in message + ): + custom_response_attributes = ( + collect_custom_response_headers_attributes(message) + ) + if len(custom_response_attributes) > 0: + server_span.set_attributes( + custom_response_attributes + ) + + propagator = get_global_response_propagator() + if propagator: + propagator.inject( + message, + context=set_span_in_context( + server_span, trace.context_api.Context() + ), + setter=asgi_setter, + ) + + await send(message) + + return otel_send diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/asgi/package.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/asgi/package.py new file mode 100644 index 00000000..c219ec54 --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/asgi/package.py @@ -0,0 +1,16 @@ +# 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 = ("asgiref ~= 3.0",) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/asgi/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/asgi/version.py new file mode 100644 index 00000000..88e9292a --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/asgi/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.38b0" diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/dbapi/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/dbapi/__init__.py new file mode 100644 index 00000000..e6382aad --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/dbapi/__init__.py @@ -0,0 +1,495 @@ +# 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. + +""" +The trace integration with Database API supports libraries that follow the +Python Database API Specification v2.0. +``_ + +Usage +----- + +.. code-block:: python + + import mysql.connector + import pyodbc + + from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.dbapi import trace_integration + + + # Ex: mysql.connector + trace_integration(mysql.connector, "connect", "mysql") + # Ex: pyodbc + trace_integration(pyodbc, "Connection", "odbc") + +API +--- +""" + +import functools +import logging +import re +import typing + +import wrapt + +from opentelemetry import trace as trace_api +from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.dbapi.version import ( + __version__, +) +from opentelemetry.instrumentation.sqlcommenter_utils import _add_sql_comment +from opentelemetry.instrumentation.utils import ( + _get_opentelemetry_values, + unwrap, +) +from opentelemetry.semconv.trace import SpanAttributes +from opentelemetry.trace import SpanKind, TracerProvider, get_tracer + +_logger = logging.getLogger(__name__) + + +def trace_integration( + connect_module: typing.Callable[..., typing.Any], + connect_method_name: str, + database_system: str, + connection_attributes: typing.Dict = None, + tracer_provider: typing.Optional[TracerProvider] = None, + capture_parameters: bool = False, + enable_commenter: bool = False, + db_api_integration_factory=None, +): + """Integrate with DB API library. + https://www.python.org/dev/peps/pep-0249/ + + Args: + connect_module: Module name where connect method is available. + connect_method_name: The connect method name. + database_system: An identifier for the database management system (DBMS) + product being used. + connection_attributes: Attribute names for database, port, host and + user in Connection object. + tracer_provider: The :class:`opentelemetry.trace.TracerProvider` to + use. If omitted the current configured one is used. + capture_parameters: Configure if db.statement.parameters should be captured. + enable_commenter: Flag to enable/disable sqlcommenter. + db_api_integration_factory: The `DatabaseApiIntegration` to use. If none is passed the + default one is used. + """ + wrap_connect( + __name__, + connect_module, + connect_method_name, + database_system, + connection_attributes, + version=__version__, + tracer_provider=tracer_provider, + capture_parameters=capture_parameters, + enable_commenter=enable_commenter, + db_api_integration_factory=db_api_integration_factory, + ) + + +def wrap_connect( + name: str, + connect_module: typing.Callable[..., typing.Any], + connect_method_name: str, + database_system: str, + connection_attributes: typing.Dict = None, + version: str = "", + tracer_provider: typing.Optional[TracerProvider] = None, + capture_parameters: bool = False, + enable_commenter: bool = False, + db_api_integration_factory=None, + commenter_options: dict = None, +): + """Integrate with DB API library. + https://www.python.org/dev/peps/pep-0249/ + + Args: + connect_module: Module name where connect method is available. + connect_method_name: The connect method name. + database_system: An identifier for the database management system (DBMS) + product being used. + connection_attributes: Attribute names for database, port, host and + user in Connection object. + tracer_provider: The :class:`opentelemetry.trace.TracerProvider` to + use. If omitted the current configured one is used. + capture_parameters: Configure if db.statement.parameters should be captured. + enable_commenter: Flag to enable/disable sqlcommenter. + db_api_integration_factory: The `DatabaseApiIntegration` to use. If none is passed the + default one is used. + commenter_options: Configurations for tags to be appended at the sql query. + + """ + db_api_integration_factory = ( + db_api_integration_factory or DatabaseApiIntegration + ) + + # pylint: disable=unused-argument + def wrap_connect_( + wrapped: typing.Callable[..., typing.Any], + instance: typing.Any, + args: typing.Tuple[typing.Any, typing.Any], + kwargs: typing.Dict[typing.Any, typing.Any], + ): + db_integration = db_api_integration_factory( + name, + database_system, + connection_attributes=connection_attributes, + version=version, + tracer_provider=tracer_provider, + capture_parameters=capture_parameters, + enable_commenter=enable_commenter, + commenter_options=commenter_options, + connect_module=connect_module, + ) + return db_integration.wrapped_connection(wrapped, args, kwargs) + + try: + wrapt.wrap_function_wrapper( + connect_module, connect_method_name, wrap_connect_ + ) + except Exception as ex: # pylint: disable=broad-except + _logger.warning("Failed to integrate with DB API. %s", str(ex)) + + +def unwrap_connect( + connect_module: typing.Callable[..., typing.Any], connect_method_name: str +): + """Disable integration with DB API library. + https://www.python.org/dev/peps/pep-0249/ + + Args: + connect_module: Module name where the connect method is available. + connect_method_name: The connect method name. + """ + unwrap(connect_module, connect_method_name) + + +def instrument_connection( + name: str, + connection, + database_system: str, + connection_attributes: typing.Dict = None, + version: str = "", + tracer_provider: typing.Optional[TracerProvider] = None, + capture_parameters: bool = False, + enable_commenter: bool = False, + commenter_options: dict = None, +): + """Enable instrumentation in a database connection. + + Args: + connection: The connection to instrument. + database_system: An identifier for the database management system (DBMS) + product being used. + connection_attributes: Attribute names for database, port, host and + user in a connection object. + tracer_provider: The :class:`opentelemetry.trace.TracerProvider` to + use. If omitted the current configured one is used. + capture_parameters: Configure if db.statement.parameters should be captured. + enable_commenter: Flag to enable/disable sqlcommenter. + commenter_options: Configurations for tags to be appended at the sql query. + + Returns: + An instrumented connection. + """ + if isinstance(connection, wrapt.ObjectProxy): + _logger.warning("Connection already instrumented") + return connection + + db_integration = DatabaseApiIntegration( + name, + database_system, + connection_attributes=connection_attributes, + version=version, + tracer_provider=tracer_provider, + capture_parameters=capture_parameters, + enable_commenter=enable_commenter, + commenter_options=commenter_options, + ) + db_integration.get_connection_attributes(connection) + return get_traced_connection_proxy(connection, db_integration) + + +def uninstrument_connection(connection): + """Disable instrumentation in a database connection. + + Args: + connection: The connection to uninstrument. + + Returns: + An uninstrumented connection. + """ + if isinstance(connection, wrapt.ObjectProxy): + return connection.__wrapped__ + + _logger.warning("Connection is not instrumented") + return connection + + +class DatabaseApiIntegration: + def __init__( + self, + name: str, + database_system: str, + connection_attributes=None, + version: str = "", + tracer_provider: typing.Optional[TracerProvider] = None, + capture_parameters: bool = False, + enable_commenter: bool = False, + commenter_options: dict = None, + connect_module: typing.Callable[..., typing.Any] = None, + ): + self.connection_attributes = connection_attributes + if self.connection_attributes is None: + self.connection_attributes = { + "database": "database", + "port": "port", + "host": "host", + "user": "user", + } + self._name = name + self._version = version + self._tracer = get_tracer( + self._name, + instrumenting_library_version=self._version, + tracer_provider=tracer_provider, + ) + self.capture_parameters = capture_parameters + self.enable_commenter = enable_commenter + self.commenter_options = commenter_options + self.database_system = database_system + self.connection_props = {} + self.span_attributes = {} + self.name = "" + self.database = "" + self.connect_module = connect_module + + def wrapped_connection( + self, + connect_method: typing.Callable[..., typing.Any], + args: typing.Tuple[typing.Any, typing.Any], + kwargs: typing.Dict[typing.Any, typing.Any], + ): + """Add object proxy to connection object.""" + connection = connect_method(*args, **kwargs) + self.get_connection_attributes(connection) + return get_traced_connection_proxy(connection, self) + + def get_connection_attributes(self, connection): + # Populate span fields using connection + for key, value in self.connection_attributes.items(): + # Allow attributes nested in connection object + attribute = functools.reduce( + lambda attribute, attribute_value: getattr( + attribute, attribute_value, None + ), + value.split("."), + connection, + ) + if attribute: + self.connection_props[key] = attribute + self.name = self.database_system + self.database = self.connection_props.get("database", "") + if self.database: + # PyMySQL encodes names with utf-8 + if hasattr(self.database, "decode"): + self.database = self.database.decode(errors="ignore") + self.name += "." + self.database + user = self.connection_props.get("user") + # PyMySQL encodes this data + if user and isinstance(user, bytes): + user = user.decode() + if user is not None: + self.span_attributes[SpanAttributes.DB_USER] = str(user) + host = self.connection_props.get("host") + if host is not None: + self.span_attributes[SpanAttributes.NET_PEER_NAME] = host + port = self.connection_props.get("port") + if port is not None: + self.span_attributes[SpanAttributes.NET_PEER_PORT] = port + + +def get_traced_connection_proxy( + connection, db_api_integration, *args, **kwargs +): + # pylint: disable=abstract-method + class TracedConnectionProxy(wrapt.ObjectProxy): + # pylint: disable=unused-argument + def __init__(self, connection, *args, **kwargs): + wrapt.ObjectProxy.__init__(self, connection) + + def __getattribute__(self, name): + if object.__getattribute__(self, name): + return object.__getattribute__(self, name) + + return object.__getattribute__( + object.__getattribute__(self, "_connection"), name + ) + + def cursor(self, *args, **kwargs): + return get_traced_cursor_proxy( + self.__wrapped__.cursor(*args, **kwargs), db_api_integration + ) + + def __enter__(self): + self.__wrapped__.__enter__() + return self + + def __exit__(self, *args, **kwargs): + self.__wrapped__.__exit__(*args, **kwargs) + + return TracedConnectionProxy(connection, *args, **kwargs) + + +class CursorTracer: + def __init__(self, db_api_integration: DatabaseApiIntegration) -> None: + self._db_api_integration = db_api_integration + self._commenter_enabled = self._db_api_integration.enable_commenter + self._commenter_options = ( + self._db_api_integration.commenter_options + if self._db_api_integration.commenter_options + else {} + ) + self._connect_module = self._db_api_integration.connect_module + self._leading_comment_remover = re.compile(r"^/\*.*?\*/") + + def _populate_span( + self, + span: trace_api.Span, + cursor, + *args: typing.Tuple[typing.Any, typing.Any], + ): + if not span.is_recording(): + return + statement = self.get_statement(cursor, args) + span.set_attribute( + SpanAttributes.DB_SYSTEM, self._db_api_integration.database_system + ) + span.set_attribute( + SpanAttributes.DB_NAME, self._db_api_integration.database + ) + span.set_attribute(SpanAttributes.DB_STATEMENT, statement) + + for ( + attribute_key, + attribute_value, + ) in self._db_api_integration.span_attributes.items(): + span.set_attribute(attribute_key, attribute_value) + + if self._db_api_integration.capture_parameters and len(args) > 1: + span.set_attribute("db.statement.parameters", str(args[1])) + + def get_operation_name(self, cursor, args): # pylint: disable=no-self-use + if args and isinstance(args[0], str): + # Strip leading comments so we get the operation name. + return self._leading_comment_remover.sub("", args[0]).split()[0] + return "" + + def get_statement(self, cursor, args): # pylint: disable=no-self-use + if not args: + return "" + statement = args[0] + if isinstance(statement, bytes): + return statement.decode("utf8", "replace") + return statement + + def traced_execution( + self, + cursor, + query_method: typing.Callable[..., typing.Any], + *args: typing.Tuple[typing.Any, typing.Any], + **kwargs: typing.Dict[typing.Any, typing.Any], + ): + name = self.get_operation_name(cursor, args) + if not name: + name = ( + self._db_api_integration.database + if self._db_api_integration.database + else self._db_api_integration.name + ) + + with self._db_api_integration._tracer.start_as_current_span( + name, kind=SpanKind.CLIENT + ) as span: + self._populate_span(span, cursor, *args) + if args and self._commenter_enabled: + try: + args_list = list(args) + commenter_data = dict( + # Psycopg2/framework information + db_driver=f"psycopg2:{self._connect_module.__version__.split(' ')[0]}", + dbapi_threadsafety=self._connect_module.threadsafety, + dbapi_level=self._connect_module.apilevel, + libpq_version=self._connect_module.__libpq_version__, + driver_paramstyle=self._connect_module.paramstyle, + ) + if self._commenter_options.get( + "opentelemetry_values", True + ): + commenter_data.update(**_get_opentelemetry_values()) + + # Filter down to just the requested attributes. + commenter_data = { + k: v + for k, v in commenter_data.items() + if self._commenter_options.get(k, True) + } + statement = _add_sql_comment( + args_list[0], **commenter_data + ) + + args_list[0] = statement + args = tuple(args_list) + + except Exception as exc: # pylint: disable=broad-except + _logger.exception( + "Exception while generating sql comment: %s", exc + ) + return query_method(*args, **kwargs) + + +def get_traced_cursor_proxy(cursor, db_api_integration, *args, **kwargs): + _cursor_tracer = CursorTracer(db_api_integration) + + # pylint: disable=abstract-method + class TracedCursorProxy(wrapt.ObjectProxy): + # pylint: disable=unused-argument + def __init__(self, cursor, *args, **kwargs): + wrapt.ObjectProxy.__init__(self, cursor) + + def execute(self, *args, **kwargs): + return _cursor_tracer.traced_execution( + self.__wrapped__, self.__wrapped__.execute, *args, **kwargs + ) + + def executemany(self, *args, **kwargs): + return _cursor_tracer.traced_execution( + self.__wrapped__, self.__wrapped__.executemany, *args, **kwargs + ) + + def callproc(self, *args, **kwargs): + return _cursor_tracer.traced_execution( + self.__wrapped__, self.__wrapped__.callproc, *args, **kwargs + ) + + def __enter__(self): + self.__wrapped__.__enter__() + return self + + def __exit__(self, *args, **kwargs): + self.__wrapped__.__exit__(*args, **kwargs) + + return TracedCursorProxy(cursor, *args, **kwargs) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/dbapi/package.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/dbapi/package.py new file mode 100644 index 00000000..7a66a17a --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/dbapi/package.py @@ -0,0 +1,16 @@ +# 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 = tuple() diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/dbapi/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/dbapi/version.py new file mode 100644 index 00000000..2cfda14d --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/dbapi/version.py @@ -0,0 +1,17 @@ +# 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.38b0" + +_instruments = tuple() diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/__init__.py new file mode 100644 index 00000000..b5f052c7 --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/__init__.py @@ -0,0 +1,387 @@ +# 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. +""" + +Instrument `django`_ to trace Django applications. + +.. _django: https://pypi.org/project/django/ + +SQLCOMMENTER +***************************************** +You can optionally configure Django instrumentation to enable sqlcommenter which enriches +the query with contextual information. + +Usage +----- + +.. code:: python + + from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.django import DjangoInstrumentor + + DjangoInstrumentor().instrument(is_sql_commentor_enabled=True) + + +For example, +:: + + Invoking Users().objects.all() will lead to sql query "select * from auth_users" but when SQLCommenter is enabled + the query will get appended with some configurable tags like "select * from auth_users /*metrics=value*/;" + + +SQLCommenter Configurations +*************************** +We can configure the tags to be appended to the sqlquery log by adding below variables to the settings.py + +SQLCOMMENTER_WITH_FRAMEWORK = True(Default) or False + +For example, +:: +Enabling this flag will add django framework and it's version which is /*framework='django%3A2.2.3*/ + +SQLCOMMENTER_WITH_CONTROLLER = True(Default) or False + +For example, +:: +Enabling this flag will add controller name that handles the request /*controller='index'*/ + +SQLCOMMENTER_WITH_ROUTE = True(Default) or False + +For example, +:: +Enabling this flag will add url path that handles the request /*route='polls/'*/ + +SQLCOMMENTER_WITH_APP_NAME = True(Default) or False + +For example, +:: +Enabling this flag will add app name that handles the request /*app_name='polls'*/ + +SQLCOMMENTER_WITH_OPENTELEMETRY = True(Default) or False + +For example, +:: +Enabling this flag will add opentelemetry traceparent /*traceparent='00-fd720cffceba94bbf75940ff3caaf3cc-4fd1a2bdacf56388-01'*/ + +SQLCOMMENTER_WITH_DB_DRIVER = True(Default) or False + +For example, +:: +Enabling this flag will add name of the db driver /*db_driver='django.db.backends.postgresql'*/ + +Usage +----- + +.. code:: python + + from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.django import DjangoInstrumentor + + DjangoInstrumentor().instrument() + + +Configuration +------------- + +Exclude lists +************* +To exclude certain URLs from tracking, set the environment variable ``OTEL_PYTHON_DJANGO_EXCLUDED_URLS`` +(or ``OTEL_PYTHON_EXCLUDED_URLS`` to cover all instrumentations) to a string of comma delimited regexes that match the +URLs. + +For example, + +:: + + export OTEL_PYTHON_DJANGO_EXCLUDED_URLS="client/.*/info,healthcheck" + +will exclude requests such as ``https://site/client/123/info`` and ``https://site/xyz/healthcheck``. + +Request attributes +******************** +To extract attributes from Django's request object and use them as span attributes, set the environment variable +``OTEL_PYTHON_DJANGO_TRACED_REQUEST_ATTRS`` to a comma delimited list of request attribute names. + +For example, + +:: + + export OTEL_PYTHON_DJANGO_TRACED_REQUEST_ATTRS='path_info,content_type' + +will extract the ``path_info`` and ``content_type`` attributes from every traced request and add them as span attributes. + +Django Request object reference: https://docs.djangoproject.com/en/3.1/ref/request-response/#attributes + +Request and Response hooks +*************************** +This instrumentation supports request and response hooks. These are functions that get called +right after a span is created for a request and right before the span is finished for the response. +The hooks can be configured as follows: + +.. code:: python + + def request_hook(span, request): + pass + + def response_hook(span, request, response): + pass + + DjangoInstrumentor().instrument(request_hook=request_hook, response_hook=response_hook) + +Django Request object: https://docs.djangoproject.com/en/3.1/ref/request-response/#httprequest-objects +Django Response object: https://docs.djangoproject.com/en/3.1/ref/request-response/#httpresponse-objects + +Capture HTTP request and response headers +***************************************** +You can configure the agent to capture specified HTTP headers as span attributes, according to the +`semantic convention `_. + +Request headers +*************** +To capture HTTP request headers as span attributes, set the environment variable +``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST`` to a comma delimited list of HTTP header names. + +For example, +:: + + export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST="content-type,custom_request_header" + +will extract ``content-type`` and ``custom_request_header`` from the request headers and add them as span attributes. + +Request header names in Django are case-insensitive. So, giving the header name as ``CUStom-Header`` in the environment +variable will capture the header named ``custom-header``. + +Regular expressions may also be used to match multiple headers that correspond to the given pattern. For example: +:: + + export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST="Accept.*,X-.*" + +Would match all request headers that start with ``Accept`` and ``X-``. + +To capture all request headers, set ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST`` to ``".*"``. +:: + + export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST=".*" + +The name of the added span attribute will follow the format ``http.request.header.`` where ```` +is the normalized HTTP header name (lowercase, with ``-`` replaced by ``_``). The value of the attribute will be a +single item list containing all the header values. + +For example: +``http.request.header.custom_request_header = [","]`` + +Response headers +**************** +To capture HTTP response headers as span attributes, set the environment variable +``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE`` to a comma delimited list of HTTP header names. + +For example, +:: + + export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE="content-type,custom_response_header" + +will extract ``content-type`` and ``custom_response_header`` from the response headers and add them as span attributes. + +Response header names in Django are case-insensitive. So, giving the header name as ``CUStom-Header`` in the environment +variable will capture the header named ``custom-header``. + +Regular expressions may also be used to match multiple headers that correspond to the given pattern. For example: +:: + + export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE="Content.*,X-.*" + +Would match all response headers that start with ``Content`` and ``X-``. + +To capture all response headers, set ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE`` to ``".*"``. +:: + + export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE=".*" + +The name of the added span attribute will follow the format ``http.response.header.`` where ```` +is the normalized HTTP header name (lowercase, with ``-`` replaced by ``_``). The value of the attribute will be a +single item list containing all the header values. + +For example: +``http.response.header.custom_response_header = [","]`` + +Sanitizing headers +****************** +In order to prevent storing sensitive data such as personally identifiable information (PII), session keys, passwords, +etc, set the environment variable ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS`` +to a comma delimited list of HTTP header names to be sanitized. Regexes may be used, and all header names will be +matched in a case-insensitive manner. + +For example, +:: + + export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS=".*session.*,set-cookie" + +will replace the value of headers such as ``session-id`` and ``set-cookie`` with ``[REDACTED]`` in the span. + +Note: + The environment variable names used to capture HTTP headers are still experimental, and thus are subject to change. + +API +--- + +""" + +from logging import getLogger +from os import environ +from typing import Collection + +from django import VERSION as django_version +from django.conf import settings +from django.core.exceptions import ImproperlyConfigured + +from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.django.environment_variables import ( + OTEL_PYTHON_DJANGO_INSTRUMENT, +) +from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.django.middleware.otel_middleware import ( + _DjangoMiddleware, +) +from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.django.package import ( + _instruments, +) +from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.django.version import ( + __version__, +) +from opentelemetry.instrumentation.instrumentor import BaseInstrumentor +from opentelemetry.metrics import get_meter +from opentelemetry.semconv.metrics import MetricInstruments +from opentelemetry.trace import get_tracer +from azure.monitor.opentelemetry.vendor.opentelemetry.util.http import ( + get_excluded_urls, + parse_excluded_urls, +) + +DJANGO_2_0 = django_version >= (2, 0) + +_excluded_urls_from_env = get_excluded_urls("DJANGO") +_logger = getLogger(__name__) + + +def _get_django_middleware_setting() -> str: + # In Django versions 1.x, setting MIDDLEWARE_CLASSES can be used as a legacy + # alternative to MIDDLEWARE. This is the case when `settings.MIDDLEWARE` has + # its default value (`None`). + if not DJANGO_2_0 and getattr(settings, "MIDDLEWARE", None) is None: + return "MIDDLEWARE_CLASSES" + return "MIDDLEWARE" + + +class DjangoInstrumentor(BaseInstrumentor): + """An instrumentor for Django + + See `BaseInstrumentor` + """ + + _opentelemetry_middleware = ".".join( + [_DjangoMiddleware.__module__, _DjangoMiddleware.__qualname__] + ) + + _sql_commenter_middleware = "azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.django.middleware.sqlcommenter_middleware.SqlCommenter" + + def instrumentation_dependencies(self) -> Collection[str]: + return _instruments + + def _instrument(self, **kwargs): + # FIXME this is probably a pattern that will show up in the rest of the + # ext. Find a better way of implementing this. + if environ.get(OTEL_PYTHON_DJANGO_INSTRUMENT) == "False": + return + + tracer_provider = kwargs.get("tracer_provider") + meter_provider = kwargs.get("meter_provider") + _excluded_urls = kwargs.get("excluded_urls") + tracer = get_tracer( + __name__, + __version__, + tracer_provider=tracer_provider, + ) + meter = get_meter(__name__, __version__, meter_provider=meter_provider) + _DjangoMiddleware._tracer = tracer + _DjangoMiddleware._meter = meter + _DjangoMiddleware._excluded_urls = ( + _excluded_urls_from_env + if _excluded_urls is None + else parse_excluded_urls(_excluded_urls) + ) + _DjangoMiddleware._otel_request_hook = kwargs.pop("request_hook", None) + _DjangoMiddleware._otel_response_hook = kwargs.pop( + "response_hook", None + ) + _DjangoMiddleware._duration_histogram = meter.create_histogram( + name=MetricInstruments.HTTP_SERVER_DURATION, + unit="ms", + description="measures the duration of the inbound http request", + ) + _DjangoMiddleware._active_request_counter = meter.create_up_down_counter( + name=MetricInstruments.HTTP_SERVER_ACTIVE_REQUESTS, + unit="requests", + description="measures the number of concurrent HTTP requests those are currently in flight", + ) + # This can not be solved, but is an inherent problem of this approach: + # the order of middleware entries matters, and here you have no control + # on that: + # https://docs.djangoproject.com/en/3.0/topics/http/middleware/#activating-middleware + # https://docs.djangoproject.com/en/3.0/ref/middleware/#middleware-ordering + + _middleware_setting = _get_django_middleware_setting() + settings_middleware = [] + try: + settings_middleware = getattr(settings, _middleware_setting, []) + except ImproperlyConfigured as exception: + _logger.debug( + "DJANGO_SETTINGS_MODULE environment variable not configured. Defaulting to empty settings: %s", + exception, + ) + settings.configure() + settings_middleware = getattr(settings, _middleware_setting, []) + except ModuleNotFoundError as exception: + _logger.debug( + "DJANGO_SETTINGS_MODULE points to a non-existent module. Defaulting to empty settings: %s", + exception, + ) + settings.configure() + settings_middleware = getattr(settings, _middleware_setting, []) + + # Django allows to specify middlewares as a tuple, so we convert this tuple to a + # list, otherwise we wouldn't be able to call append/remove + if isinstance(settings_middleware, tuple): + settings_middleware = list(settings_middleware) + + is_sql_commentor_enabled = kwargs.pop("is_sql_commentor_enabled", None) + + if is_sql_commentor_enabled: + settings_middleware.insert(0, self._sql_commenter_middleware) + + settings_middleware.insert(0, self._opentelemetry_middleware) + + setattr(settings, _middleware_setting, settings_middleware) + + def _uninstrument(self, **kwargs): + _middleware_setting = _get_django_middleware_setting() + settings_middleware = getattr(settings, _middleware_setting, None) + + # FIXME This is starting to smell like trouble. We have 2 mechanisms + # that may make this condition be True, one implemented in + # BaseInstrumentor and another one implemented in _instrument. Both + # stop _instrument from running and thus, settings_middleware not being + # set. + if settings_middleware is None or ( + self._opentelemetry_middleware not in settings_middleware + ): + return + + settings_middleware.remove(self._opentelemetry_middleware) + setattr(settings, _middleware_setting, settings_middleware) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/environment_variables.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/environment_variables.py new file mode 100644 index 00000000..4972a62e --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/environment_variables.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. + +OTEL_PYTHON_DJANGO_INSTRUMENT = "OTEL_PYTHON_DJANGO_INSTRUMENT" diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/middleware/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/middleware/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/middleware/otel_middleware.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/middleware/otel_middleware.py new file mode 100644 index 00000000..5ac16097 --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/middleware/otel_middleware.py @@ -0,0 +1,400 @@ +# 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 types +from logging import getLogger +from time import time +from timeit import default_timer +from typing import Callable + +from django import VERSION as django_version +from django.http import HttpRequest, HttpResponse + +from opentelemetry.context import detach +from opentelemetry.instrumentation.propagators import ( + get_global_response_propagator, +) +from opentelemetry.instrumentation.utils import ( + _start_internal_or_server_span, + extract_attributes_from_object, +) +from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.wsgi import ( + add_response_attributes, +) +from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.wsgi import ( + collect_custom_request_headers_attributes as wsgi_collect_custom_request_headers_attributes, +) +from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.wsgi import ( + collect_custom_response_headers_attributes as wsgi_collect_custom_response_headers_attributes, +) +from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.wsgi import ( + collect_request_attributes as wsgi_collect_request_attributes, +) +from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.wsgi import ( + wsgi_getter, +) +from opentelemetry.semconv.trace import SpanAttributes +from opentelemetry.trace import Span, SpanKind, use_span +from azure.monitor.opentelemetry.vendor.opentelemetry.util.http import ( + _parse_active_request_count_attrs, + _parse_duration_attrs, + get_excluded_urls, + get_traced_request_attrs, +) + +try: + from django.core.urlresolvers import ( # pylint: disable=no-name-in-module + Resolver404, + resolve, + ) +except ImportError: + from django.urls import Resolver404, resolve + +DJANGO_2_0 = django_version >= (2, 0) +DJANGO_3_0 = django_version >= (3, 0) + +if DJANGO_2_0: + # Since Django 2.0, only `settings.MIDDLEWARE` is supported, so new-style + # middlewares can be used. + class MiddlewareMixin: + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + self.process_request(request) + response = self.get_response(request) + return self.process_response(request, response) + +else: + # Django versions 1.x can use `settings.MIDDLEWARE_CLASSES` and expect + # old-style middlewares, which are created by inheriting from + # `deprecation.MiddlewareMixin` since its creation in Django 1.10 and 1.11, + # or from `object` for older versions. + try: + from django.utils.deprecation import MiddlewareMixin + except ImportError: + MiddlewareMixin = object + +if DJANGO_3_0: + from django.core.handlers.asgi import ASGIRequest +else: + ASGIRequest = None + +# try/except block exclusive for optional ASGI imports. +try: + from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.asgi import ( + asgi_getter, + asgi_setter, + ) + from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.asgi import ( + collect_custom_request_headers_attributes as asgi_collect_custom_request_attributes, + ) + from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.asgi import ( + collect_custom_response_headers_attributes as asgi_collect_custom_response_attributes, + ) + from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.asgi import ( + collect_request_attributes as asgi_collect_request_attributes, + ) + from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.asgi import ( + set_status_code, + ) + + _is_asgi_supported = True +except ImportError: + asgi_getter = None + asgi_collect_request_attributes = None + set_status_code = None + _is_asgi_supported = False + + +_logger = getLogger(__name__) +_attributes_by_preference = [ + [ + SpanAttributes.HTTP_SCHEME, + SpanAttributes.HTTP_HOST, + SpanAttributes.HTTP_TARGET, + ], + [ + SpanAttributes.HTTP_SCHEME, + SpanAttributes.HTTP_SERVER_NAME, + SpanAttributes.NET_HOST_PORT, + SpanAttributes.HTTP_TARGET, + ], + [ + SpanAttributes.HTTP_SCHEME, + SpanAttributes.NET_HOST_NAME, + SpanAttributes.NET_HOST_PORT, + SpanAttributes.HTTP_TARGET, + ], + [SpanAttributes.HTTP_URL], +] + + +def _is_asgi_request(request: HttpRequest) -> bool: + return ASGIRequest is not None and isinstance(request, ASGIRequest) + + +class _DjangoMiddleware(MiddlewareMixin): + """Django Middleware for OpenTelemetry""" + + _environ_activation_key = ( + "opentelemetry-instrumentor-django.activation_key" + ) + _environ_token = "opentelemetry-instrumentor-django.token" + _environ_span_key = "opentelemetry-instrumentor-django.span_key" + _environ_exception_key = "opentelemetry-instrumentor-django.exception_key" + _environ_active_request_attr_key = ( + "opentelemetry-instrumentor-django.active_request_attr_key" + ) + _environ_duration_attr_key = ( + "opentelemetry-instrumentor-django.duration_attr_key" + ) + _environ_timer_key = "opentelemetry-instrumentor-django.timer_key" + _traced_request_attrs = get_traced_request_attrs("DJANGO") + _excluded_urls = get_excluded_urls("DJANGO") + _tracer = None + _meter = None + _duration_histogram = None + _active_request_counter = None + + _otel_request_hook: Callable[[Span, HttpRequest], None] = None + _otel_response_hook: Callable[ + [Span, HttpRequest, HttpResponse], None + ] = None + + @staticmethod + def _get_span_name(request): + try: + if getattr(request, "resolver_match"): + match = request.resolver_match + else: + match = resolve(request.path) + + if hasattr(match, "route"): + return match.route + + # Instead of using `view_name`, better to use `_func_name` as some applications can use similar + # view names in different modules + if hasattr(match, "_func_name"): + return match._func_name # pylint: disable=protected-access + + # Fallback for safety as `_func_name` private field + return match.view_name + + except Resolver404: + return f"HTTP {request.method}" + + # pylint: disable=too-many-locals + def process_request(self, request): + # request.META is a dictionary containing all available HTTP headers + # Read more about request.META here: + # https://docs.djangoproject.com/en/3.0/ref/request-response/#django.http.HttpRequest.META + + if self._excluded_urls.url_disabled(request.build_absolute_uri("?")): + return + + is_asgi_request = _is_asgi_request(request) + if not _is_asgi_supported and is_asgi_request: + return + + # pylint:disable=W0212 + request._otel_start_time = time() + request_meta = request.META + + if is_asgi_request: + carrier = request.scope + carrier_getter = asgi_getter + collect_request_attributes = asgi_collect_request_attributes + else: + carrier = request_meta + carrier_getter = wsgi_getter + collect_request_attributes = wsgi_collect_request_attributes + + span, token = _start_internal_or_server_span( + tracer=self._tracer, + span_name=self._get_span_name(request), + start_time=request_meta.get( + "opentelemetry-instrumentor-django.starttime_key" + ), + context_carrier=carrier, + context_getter=carrier_getter, + ) + + attributes = collect_request_attributes(carrier) + active_requests_count_attrs = _parse_active_request_count_attrs( + attributes + ) + duration_attrs = _parse_duration_attrs(attributes) + + request.META[ + self._environ_active_request_attr_key + ] = active_requests_count_attrs + request.META[self._environ_duration_attr_key] = duration_attrs + self._active_request_counter.add(1, active_requests_count_attrs) + if span.is_recording(): + attributes = extract_attributes_from_object( + request, self._traced_request_attrs, attributes + ) + if is_asgi_request: + # ASGI requests include extra attributes in request.scope.headers. + attributes = extract_attributes_from_object( + types.SimpleNamespace( + **{ + name.decode("latin1"): value.decode("latin1") + for name, value in request.scope.get("headers", []) + } + ), + self._traced_request_attrs, + attributes, + ) + if span.is_recording() and span.kind == SpanKind.SERVER: + attributes.update( + asgi_collect_custom_request_attributes(carrier) + ) + else: + if span.is_recording() and span.kind == SpanKind.SERVER: + custom_attributes = ( + wsgi_collect_custom_request_headers_attributes(carrier) + ) + if len(custom_attributes) > 0: + span.set_attributes(custom_attributes) + + for key, value in attributes.items(): + span.set_attribute(key, value) + + activation = use_span(span, end_on_exit=True) + activation.__enter__() # pylint: disable=E1101 + request_start_time = default_timer() + request.META[self._environ_timer_key] = request_start_time + request.META[self._environ_activation_key] = activation + request.META[self._environ_span_key] = span + if token: + request.META[self._environ_token] = token + + if _DjangoMiddleware._otel_request_hook: + _DjangoMiddleware._otel_request_hook( # pylint: disable=not-callable + span, request + ) + + # pylint: disable=unused-argument + def process_view(self, request, view_func, *args, **kwargs): + # Process view is executed before the view function, here we get the + # route template from request.resolver_match. It is not set yet in process_request + if self._excluded_urls.url_disabled(request.build_absolute_uri("?")): + return + + if ( + self._environ_activation_key in request.META.keys() + and self._environ_span_key in request.META.keys() + ): + span = request.META[self._environ_span_key] + + if span.is_recording(): + match = getattr(request, "resolver_match", None) + if match: + route = getattr(match, "route", None) + if route: + span.set_attribute(SpanAttributes.HTTP_ROUTE, route) + + def process_exception(self, request, exception): + if self._excluded_urls.url_disabled(request.build_absolute_uri("?")): + return + + if self._environ_activation_key in request.META.keys(): + request.META[self._environ_exception_key] = exception + + # pylint: disable=too-many-branches + # pylint: disable=too-many-locals + def process_response(self, request, response): + if self._excluded_urls.url_disabled(request.build_absolute_uri("?")): + return response + + is_asgi_request = _is_asgi_request(request) + if not _is_asgi_supported and is_asgi_request: + return response + + activation = request.META.pop(self._environ_activation_key, None) + span = request.META.pop(self._environ_span_key, None) + active_requests_count_attrs = request.META.pop( + self._environ_active_request_attr_key, None + ) + duration_attrs = request.META.pop( + self._environ_duration_attr_key, None + ) + if duration_attrs: + duration_attrs[ + SpanAttributes.HTTP_STATUS_CODE + ] = response.status_code + request_start_time = request.META.pop(self._environ_timer_key, None) + + if activation and span: + if is_asgi_request: + set_status_code(span, response.status_code) + + if span.is_recording() and span.kind == SpanKind.SERVER: + custom_headers = {} + for key, value in response.items(): + asgi_setter.set(custom_headers, key, value) + + custom_res_attributes = ( + asgi_collect_custom_response_attributes(custom_headers) + ) + for key, value in custom_res_attributes.items(): + span.set_attribute(key, value) + else: + add_response_attributes( + span, + f"{response.status_code} {response.reason_phrase}", + response.items(), + ) + if span.is_recording() and span.kind == SpanKind.SERVER: + custom_attributes = ( + wsgi_collect_custom_response_headers_attributes( + response.items() + ) + ) + if len(custom_attributes) > 0: + span.set_attributes(custom_attributes) + + propagator = get_global_response_propagator() + if propagator: + propagator.inject(response) + + # record any exceptions raised while processing the request + exception = request.META.pop(self._environ_exception_key, None) + if _DjangoMiddleware._otel_response_hook: + _DjangoMiddleware._otel_response_hook( # pylint: disable=not-callable + span, request, response + ) + + if exception: + activation.__exit__( + type(exception), + exception, + getattr(exception, "__traceback__", None), + ) + else: + activation.__exit__(None, None, None) + + if request_start_time is not None: + duration = max( + round((default_timer() - request_start_time) * 1000), 0 + ) + self._duration_histogram.record(duration, duration_attrs) + self._active_request_counter.add(-1, active_requests_count_attrs) + if request.META.get(self._environ_token, None) is not None: + detach(request.META.get(self._environ_token)) + request.META.pop(self._environ_token) + + return response diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/middleware/sqlcommenter_middleware.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/middleware/sqlcommenter_middleware.py new file mode 100644 index 00000000..89d8a9b7 --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/middleware/sqlcommenter_middleware.py @@ -0,0 +1,119 @@ +#!/usr/bin/python +# +# 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 contextlib import ExitStack +from logging import getLogger +from typing import Any, Type, TypeVar + +# pylint: disable=no-name-in-module +from django import conf, get_version +from django.db import connections +from django.db.backends.utils import CursorDebugWrapper + +from opentelemetry.instrumentation.sqlcommenter_utils import _add_sql_comment +from opentelemetry.instrumentation.utils import _get_opentelemetry_values +from opentelemetry.trace.propagation.tracecontext import ( + TraceContextTextMapPropagator, +) + +_propagator = TraceContextTextMapPropagator() + +_django_version = get_version() +_logger = getLogger(__name__) + +T = TypeVar("T") # pylint: disable-msg=invalid-name + + +class SqlCommenter: + """ + Middleware to append a comment to each database query with details about + the framework and the execution context. + """ + + def __init__(self, get_response) -> None: + self.get_response = get_response + + def __call__(self, request) -> Any: + with ExitStack() as stack: + for db_alias in connections: + stack.enter_context( + connections[db_alias].execute_wrapper( + _QueryWrapper(request) + ) + ) + return self.get_response(request) + + +class _QueryWrapper: + def __init__(self, request) -> None: + self.request = request + + def __call__(self, execute: Type[T], sql, params, many, context) -> T: + # pylint: disable-msg=too-many-locals + with_framework = getattr( + conf.settings, "SQLCOMMENTER_WITH_FRAMEWORK", True + ) + with_controller = getattr( + conf.settings, "SQLCOMMENTER_WITH_CONTROLLER", True + ) + with_route = getattr(conf.settings, "SQLCOMMENTER_WITH_ROUTE", True) + with_app_name = getattr( + conf.settings, "SQLCOMMENTER_WITH_APP_NAME", True + ) + with_opentelemetry = getattr( + conf.settings, "SQLCOMMENTER_WITH_OPENTELEMETRY", True + ) + with_db_driver = getattr( + conf.settings, "SQLCOMMENTER_WITH_DB_DRIVER", True + ) + + db_driver = context["connection"].settings_dict.get("ENGINE", "") + resolver_match = self.request.resolver_match + + sql = _add_sql_comment( + sql, + # Information about the controller. + controller=resolver_match.view_name + if resolver_match and with_controller + else None, + # route is the pattern that matched a request with a controller i.e. the regex + # See https://docs.djangoproject.com/en/stable/ref/urlresolvers/#django.urls.ResolverMatch.route + # getattr() because the attribute doesn't exist in Django < 2.2. + route=getattr(resolver_match, "route", None) + if resolver_match and with_route + else None, + # app_name is the application namespace for the URL pattern that matches the URL. + # See https://docs.djangoproject.com/en/stable/ref/urlresolvers/#django.urls.ResolverMatch.app_name + app_name=(resolver_match.app_name or None) + if resolver_match and with_app_name + else None, + # Framework centric information. + framework=f"django:{_django_version}" if with_framework else None, + # Information about the database and driver. + db_driver=db_driver if with_db_driver else None, + **_get_opentelemetry_values() if with_opentelemetry else {}, + ) + + # TODO: MySQL truncates logs > 1024B so prepend comments + # instead of statements, if the engine is MySQL. + # See: + # * https://github.com/basecamp/marginalia/issues/61 + # * https://github.com/basecamp/marginalia/pull/80 + + # Add the query to the query log if debugging. + if isinstance(context["cursor"], CursorDebugWrapper): + context["connection"].queries_log.append(sql) + + return execute(sql, params, many, context) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/package.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/package.py new file mode 100644 index 00000000..290061a3 --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/package.py @@ -0,0 +1,17 @@ +# 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 = ("django >= 1.10",) +_supports_metrics = True diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/version.py new file mode 100644 index 00000000..88e9292a --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/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.38b0" diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/fastapi/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/fastapi/__init__.py new file mode 100644 index 00000000..3406bab7 --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/fastapi/__init__.py @@ -0,0 +1,350 @@ +# 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. + +""" +Usage +----- + +.. code-block:: python + + import fastapi + from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.fastapi import FastAPIInstrumentor + + app = fastapi.FastAPI() + + @app.get("/foobar") + async def foobar(): + return {"message": "hello world"} + + FastAPIInstrumentor.instrument_app(app) + +Configuration +------------- + +Exclude lists +************* +To exclude certain URLs from tracking, set the environment variable ``OTEL_PYTHON_FASTAPI_EXCLUDED_URLS`` +(or ``OTEL_PYTHON_EXCLUDED_URLS`` to cover all instrumentations) to a string of comma delimited regexes that match the +URLs. + +For example, + +:: + + export OTEL_PYTHON_FASTAPI_EXCLUDED_URLS="client/.*/info,healthcheck" + +will exclude requests such as ``https://site/client/123/info`` and ``https://site/xyz/healthcheck``. + +You can also pass comma delimited regexes directly to the ``instrument_app`` method: + +.. code-block:: python + + FastAPIInstrumentor.instrument_app(app, excluded_urls="client/.*/info,healthcheck") + +Request/Response hooks +********************** + +This instrumentation supports request and response hooks. These are functions that get called +right after a span is created for a request and right before the span is finished for the response. + +- The server request hook is passed a server span and ASGI scope object for every incoming request. +- The client request hook is called with the internal span and an ASGI scope when the method ``receive`` is called. +- The client response hook is called with the internal span and an ASGI event when the method ``send`` is called. + +.. code-block:: python + + def server_request_hook(span: Span, scope: dict): + if span and span.is_recording(): + span.set_attribute("custom_user_attribute_from_request_hook", "some-value") + + def client_request_hook(span: Span, scope: dict): + if span and span.is_recording(): + span.set_attribute("custom_user_attribute_from_client_request_hook", "some-value") + + def client_response_hook(span: Span, message: dict): + if span and span.is_recording(): + span.set_attribute("custom_user_attribute_from_response_hook", "some-value") + + FastAPIInstrumentor().instrument(server_request_hook=server_request_hook, client_request_hook=client_request_hook, client_response_hook=client_response_hook) + +Capture HTTP request and response headers +***************************************** +You can configure the agent to capture specified HTTP headers as span attributes, according to the +`semantic convention `_. + +Request headers +*************** +To capture HTTP request headers as span attributes, set the environment variable +``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST`` to a comma delimited list of HTTP header names. + +For example, +:: + + export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST="content-type,custom_request_header" + +will extract ``content-type`` and ``custom_request_header`` from the request headers and add them as span attributes. + +Request header names in FastAPI are case-insensitive. So, giving the header name as ``CUStom-Header`` in the environment +variable will capture the header named ``custom-header``. + +Regular expressions may also be used to match multiple headers that correspond to the given pattern. For example: +:: + + export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST="Accept.*,X-.*" + +Would match all request headers that start with ``Accept`` and ``X-``. + +To capture all request headers, set ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST`` to ``".*"``. +:: + + export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST=".*" + +The name of the added span attribute will follow the format ``http.request.header.`` where ```` +is the normalized HTTP header name (lowercase, with ``-`` replaced by ``_``). The value of the attribute will be a +single item list containing all the header values. + +For example: +``http.request.header.custom_request_header = [","]`` + +Response headers +**************** +To capture HTTP response headers as span attributes, set the environment variable +``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE`` to a comma delimited list of HTTP header names. + +For example, +:: + + export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE="content-type,custom_response_header" + +will extract ``content-type`` and ``custom_response_header`` from the response headers and add them as span attributes. + +Response header names in FastAPI are case-insensitive. So, giving the header name as ``CUStom-Header`` in the environment +variable will capture the header named ``custom-header``. + +Regular expressions may also be used to match multiple headers that correspond to the given pattern. For example: +:: + + export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE="Content.*,X-.*" + +Would match all response headers that start with ``Content`` and ``X-``. + +To capture all response headers, set ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE`` to ``".*"``. +:: + + export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE=".*" + +The name of the added span attribute will follow the format ``http.response.header.`` where ```` +is the normalized HTTP header name (lowercase, with ``-`` replaced by ``_``). The value of the attribute will be a +single item list containing all the header values. + +For example: +``http.response.header.custom_response_header = [","]`` + +Sanitizing headers +****************** +In order to prevent storing sensitive data such as personally identifiable information (PII), session keys, passwords, +etc, set the environment variable ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS`` +to a comma delimited list of HTTP header names to be sanitized. Regexes may be used, and all header names will be +matched in a case-insensitive manner. + +For example, +:: + + export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS=".*session.*,set-cookie" + +will replace the value of headers such as ``session-id`` and ``set-cookie`` with ``[REDACTED]`` in the span. + +Note: + The environment variable names used to capture HTTP headers are still experimental, and thus are subject to change. + +API +--- +""" +import logging +import typing +from typing import Collection + +import fastapi +from starlette.routing import Match + +from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.asgi import ( + OpenTelemetryMiddleware, +) +from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.fastapi.package import ( + _instruments, +) +from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.fastapi.version import ( + __version__, +) +from opentelemetry.instrumentation.instrumentor import BaseInstrumentor +from opentelemetry.metrics import get_meter +from opentelemetry.semconv.trace import SpanAttributes +from opentelemetry.trace import Span +from azure.monitor.opentelemetry.vendor.opentelemetry.util.http import ( + get_excluded_urls, + parse_excluded_urls, +) + +_excluded_urls_from_env = get_excluded_urls("FASTAPI") +_logger = logging.getLogger(__name__) + +_ServerRequestHookT = typing.Optional[typing.Callable[[Span, dict], None]] +_ClientRequestHookT = typing.Optional[typing.Callable[[Span, dict], None]] +_ClientResponseHookT = typing.Optional[typing.Callable[[Span, dict], None]] + + +class FastAPIInstrumentor(BaseInstrumentor): + """An instrumentor for FastAPI + + See `BaseInstrumentor` + """ + + _original_fastapi = None + + @staticmethod + def instrument_app( + app: fastapi.FastAPI, + server_request_hook: _ServerRequestHookT = None, + client_request_hook: _ClientRequestHookT = None, + client_response_hook: _ClientResponseHookT = None, + tracer_provider=None, + meter_provider=None, + excluded_urls=None, + ): + """Instrument an uninstrumented FastAPI application.""" + if not hasattr(app, "_is_instrumented_by_opentelemetry"): + app._is_instrumented_by_opentelemetry = False + + if not getattr(app, "_is_instrumented_by_opentelemetry", False): + if excluded_urls is None: + excluded_urls = _excluded_urls_from_env + else: + excluded_urls = parse_excluded_urls(excluded_urls) + meter = get_meter(__name__, __version__, meter_provider) + + app.add_middleware( + OpenTelemetryMiddleware, + excluded_urls=excluded_urls, + default_span_details=_get_route_details, + server_request_hook=server_request_hook, + client_request_hook=client_request_hook, + client_response_hook=client_response_hook, + tracer_provider=tracer_provider, + meter=meter, + ) + app._is_instrumented_by_opentelemetry = True + if app not in _InstrumentedFastAPI._instrumented_fastapi_apps: + _InstrumentedFastAPI._instrumented_fastapi_apps.add(app) + else: + _logger.warning( + "Attempting to instrument FastAPI app while already instrumented" + ) + + @staticmethod + def uninstrument_app(app: fastapi.FastAPI): + app.user_middleware = [ + x + for x in app.user_middleware + if x.cls is not OpenTelemetryMiddleware + ] + app.middleware_stack = app.build_middleware_stack() + app._is_instrumented_by_opentelemetry = False + + def instrumentation_dependencies(self) -> Collection[str]: + return _instruments + + def _instrument(self, **kwargs): + self._original_fastapi = fastapi.FastAPI + _InstrumentedFastAPI._tracer_provider = kwargs.get("tracer_provider") + _InstrumentedFastAPI._server_request_hook = kwargs.get( + "server_request_hook" + ) + _InstrumentedFastAPI._client_request_hook = kwargs.get( + "client_request_hook" + ) + _InstrumentedFastAPI._client_response_hook = kwargs.get( + "client_response_hook" + ) + _excluded_urls = kwargs.get("excluded_urls") + _InstrumentedFastAPI._excluded_urls = ( + _excluded_urls_from_env + if _excluded_urls is None + else parse_excluded_urls(_excluded_urls) + ) + _InstrumentedFastAPI._meter_provider = kwargs.get("meter_provider") + fastapi.FastAPI = _InstrumentedFastAPI + + def _uninstrument(self, **kwargs): + for instance in _InstrumentedFastAPI._instrumented_fastapi_apps: + self.uninstrument_app(instance) + _InstrumentedFastAPI._instrumented_fastapi_apps.clear() + fastapi.FastAPI = self._original_fastapi + + +class _InstrumentedFastAPI(fastapi.FastAPI): + _tracer_provider = None + _meter_provider = None + _excluded_urls = None + _server_request_hook: _ServerRequestHookT = None + _client_request_hook: _ClientRequestHookT = None + _client_response_hook: _ClientResponseHookT = None + _instrumented_fastapi_apps = set() + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + meter = get_meter( + __name__, __version__, _InstrumentedFastAPI._meter_provider + ) + self.add_middleware( + OpenTelemetryMiddleware, + excluded_urls=_InstrumentedFastAPI._excluded_urls, + default_span_details=_get_route_details, + server_request_hook=_InstrumentedFastAPI._server_request_hook, + client_request_hook=_InstrumentedFastAPI._client_request_hook, + client_response_hook=_InstrumentedFastAPI._client_response_hook, + tracer_provider=_InstrumentedFastAPI._tracer_provider, + meter=meter, + ) + self._is_instrumented_by_opentelemetry = True + _InstrumentedFastAPI._instrumented_fastapi_apps.add(self) + + def __del__(self): + if self in _InstrumentedFastAPI._instrumented_fastapi_apps: + _InstrumentedFastAPI._instrumented_fastapi_apps.remove(self) + + +def _get_route_details(scope): + """Callback to retrieve the fastapi route being served. + + TODO: there is currently no way to retrieve http.route from + a starlette application from scope. + + See: https://github.com/encode/starlette/pull/804 + """ + app = scope["app"] + route = None + for starlette_route in app.routes: + match, _ = starlette_route.matches(scope) + if match == Match.FULL: + route = starlette_route.path + break + if match == Match.PARTIAL: + route = starlette_route.path + # method only exists for http, if websocket + # leave it blank. + span_name = route or scope.get("method", "") + attributes = {} + if route: + attributes[SpanAttributes.HTTP_ROUTE] = route + return span_name, attributes diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/fastapi/package.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/fastapi/package.py new file mode 100644 index 00000000..8df84fc9 --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/fastapi/package.py @@ -0,0 +1,18 @@ +# 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 = ("fastapi ~= 0.58",) + +_supports_metrics = True diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/fastapi/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/fastapi/version.py new file mode 100644 index 00000000..88e9292a --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/fastapi/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.38b0" diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/flask/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/flask/__init__.py new file mode 100644 index 00000000..062570d1 --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/flask/__init__.py @@ -0,0 +1,649 @@ +# 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. + +# Note: This package is not named "flask" because of +# https://github.com/PyCQA/pylint/issues/2648 + +""" +This library builds on the OpenTelemetry WSGI middleware to track web requests +in Flask applications. In addition to opentelemetry-util-http, it +supports Flask-specific features such as: + +* The Flask url rule pattern is used as the Span name. +* The ``http.route`` Span attribute is set so that one can see which URL rule + matched a request. + +SQLCOMMENTER +***************************************** +You can optionally configure Flask instrumentation to enable sqlcommenter which enriches +the query with contextual information. + +Usage +----- + +.. code:: python + + from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.flask import FlaskInstrumentor + + FlaskInstrumentor().instrument(enable_commenter=True, commenter_options={}) + +For example, FlaskInstrumentor when used with SQLAlchemyInstrumentor or Psycopg2Instrumentor, +invoking ``cursor.execute("select * from auth_users")`` will lead to sql query +``select * from auth_users`` but when SQLCommenter is enabled the query will get appended with +some configurable tags like: + +.. code:: + + select * from auth_users /*metrics=value*/;" + +Inorder for the commenter to append flask related tags to sql queries, the commenter needs +to enabled on the respective SQLAlchemyInstrumentor or Psycopg2Instrumentor framework too. + +SQLCommenter Configurations +*************************** +We can configure the tags to be appended to the sqlquery log by adding configuration +inside ``commenter_options={}`` dict. + +For example, enabling this flag will add flask and it's version which +is ``/*flask%%3A2.9.3*/`` to the SQL query as a comment (default is True): + +.. code:: python + + framework = True + +For example, enabling this flag will add route uri ``/*route='/home'*/`` +to the SQL query as a comment (default is True): + +.. code:: python + + route = True + +For example, enabling this flag will add controller name ``/*controller='home_view'*/`` +to the SQL query as a comment (default is True): + +.. code:: python + + controller = True + +Usage +----- + +.. code-block:: python + + from flask import Flask + from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.flask import FlaskInstrumentor + + app = Flask(__name__) + + FlaskInstrumentor().instrument_app(app) + + @app.route("/") + def hello(): + return "Hello!" + + if __name__ == "__main__": + app.run(debug=True) + +Configuration +------------- + +Exclude lists +************* +To exclude certain URLs from tracking, set the environment variable ``OTEL_PYTHON_FLASK_EXCLUDED_URLS`` +(or ``OTEL_PYTHON_EXCLUDED_URLS`` to cover all instrumentations) to a string of comma delimited regexes that match the +URLs. + +For example, + +:: + + export OTEL_PYTHON_FLASK_EXCLUDED_URLS="client/.*/info,healthcheck" + +will exclude requests such as ``https://site/client/123/info`` and ``https://site/xyz/healthcheck``. + +You can also pass comma delimited regexes directly to the ``instrument_app`` method: + +.. code-block:: python + + FlaskInstrumentor().instrument_app(app, excluded_urls="client/.*/info,healthcheck") + +Request/Response hooks +********************** + +This instrumentation supports request and response hooks. These are functions that get called +right after a span is created for a request and right before the span is finished for the response. + +- The client request hook is called with the internal span and an instance of WSGIEnvironment (flask.request.environ) + when the method ``receive`` is called. +- The client response hook is called with the internal span, the status of the response and a list of key-value (tuples) + representing the response headers returned from the response when the method ``send`` is called. + +For example, + +.. code-block:: python + + def request_hook(span: Span, environ: WSGIEnvironment): + if span and span.is_recording(): + span.set_attribute("custom_user_attribute_from_request_hook", "some-value") + + def response_hook(span: Span, status: str, response_headers: List): + if span and span.is_recording(): + span.set_attribute("custom_user_attribute_from_response_hook", "some-value") + + FlaskInstrumentation().instrument(request_hook=request_hook, response_hook=response_hook) + +Flask Request object reference: https://flask.palletsprojects.com/en/2.1.x/api/#flask.Request + +Capture HTTP request and response headers +***************************************** +You can configure the agent to capture specified HTTP headers as span attributes, according to the +`semantic convention `_. + +Request headers +*************** +To capture HTTP request headers as span attributes, set the environment variable +``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST`` to a comma delimited list of HTTP header names. + +For example, +:: + + export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST="content-type,custom_request_header" + +will extract ``content-type`` and ``custom_request_header`` from the request headers and add them as span attributes. + +Request header names in Flask are case-insensitive and ``-`` characters are replaced by ``_``. So, giving the header +name as ``CUStom_Header`` in the environment variable will capture the header named ``custom-header``. + +Regular expressions may also be used to match multiple headers that correspond to the given pattern. For example: +:: + + export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST="Accept.*,X-.*" + +Would match all request headers that start with ``Accept`` and ``X-``. + +To capture all request headers, set ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST`` to ``".*"``. +:: + + export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST=".*" + +The name of the added span attribute will follow the format ``http.request.header.`` where ```` +is the normalized HTTP header name (lowercase, with ``-`` replaced by ``_``). The value of the attribute will be a +single item list containing all the header values. + +For example: +``http.request.header.custom_request_header = [","]`` + +Response headers +**************** +To capture HTTP response headers as span attributes, set the environment variable +``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE`` to a comma delimited list of HTTP header names. + +For example, +:: + + export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE="content-type,custom_response_header" + +will extract ``content-type`` and ``custom_response_header`` from the response headers and add them as span attributes. + +Response header names in Flask are case-insensitive. So, giving the header name as ``CUStom-Header`` in the environment +variable will capture the header named ``custom-header``. + +Regular expressions may also be used to match multiple headers that correspond to the given pattern. For example: +:: + + export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE="Content.*,X-.*" + +Would match all response headers that start with ``Content`` and ``X-``. + +To capture all response headers, set ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE`` to ``".*"``. +:: + + export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE=".*" + +The name of the added span attribute will follow the format ``http.response.header.`` where ```` +is the normalized HTTP header name (lowercase, with ``-`` replaced by ``_``). The value of the attribute will be a +single item list containing all the header values. + +For example: +``http.response.header.custom_response_header = [","]`` + +Sanitizing headers +****************** +In order to prevent storing sensitive data such as personally identifiable information (PII), session keys, passwords, +etc, set the environment variable ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS`` +to a comma delimited list of HTTP header names to be sanitized. Regexes may be used, and all header names will be +matched in a case-insensitive manner. + +For example, +:: + + export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS=".*session.*,set-cookie" + +will replace the value of headers such as ``session-id`` and ``set-cookie`` with ``[REDACTED]`` in the span. + +Note: + The environment variable names used to capture HTTP headers are still experimental, and thus are subject to change. + +API +--- +""" +from logging import getLogger +from threading import get_ident +from time import time_ns +from timeit import default_timer +from typing import Collection + +import flask + +import azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.wsgi as otel_wsgi +from opentelemetry import context, trace +from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.flask.package import ( + _instruments, +) +from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.flask.version import ( + __version__, +) +from opentelemetry.instrumentation.instrumentor import BaseInstrumentor +from opentelemetry.instrumentation.propagators import ( + get_global_response_propagator, +) +from opentelemetry.instrumentation.utils import _start_internal_or_server_span +from opentelemetry.metrics import get_meter +from opentelemetry.semconv.metrics import MetricInstruments +from opentelemetry.semconv.trace import SpanAttributes +from azure.monitor.opentelemetry.vendor.opentelemetry.util.http import ( + get_excluded_urls, + parse_excluded_urls, +) + +_logger = getLogger(__name__) + +_ENVIRON_STARTTIME_KEY = "opentelemetry-flask.starttime_key" +_ENVIRON_SPAN_KEY = "opentelemetry-flask.span_key" +_ENVIRON_ACTIVATION_KEY = "opentelemetry-flask.activation_key" +_ENVIRON_THREAD_ID_KEY = "opentelemetry-flask.thread_id_key" +_ENVIRON_TOKEN = "opentelemetry-flask.token" + +_excluded_urls_from_env = get_excluded_urls("FLASK") + + +def get_default_span_name(): + try: + span_name = flask.request.url_rule.rule + except AttributeError: + span_name = otel_wsgi.get_default_span_name(flask.request.environ) + return span_name + + +def _rewrapped_app( + wsgi_app, + active_requests_counter, + duration_histogram, + response_hook=None, + excluded_urls=None, +): + def _wrapped_app(wrapped_app_environ, start_response): + # We want to measure the time for route matching, etc. + # In theory, we could start the span here and use + # update_name later but that API is "highly discouraged" so + # we better avoid it. + wrapped_app_environ[_ENVIRON_STARTTIME_KEY] = time_ns() + start = default_timer() + attributes = otel_wsgi.collect_request_attributes(wrapped_app_environ) + active_requests_count_attrs = ( + otel_wsgi._parse_active_request_count_attrs(attributes) + ) + duration_attrs = otel_wsgi._parse_duration_attrs(attributes) + active_requests_counter.add(1, active_requests_count_attrs) + + def _start_response(status, response_headers, *args, **kwargs): + if flask.request and ( + excluded_urls is None + or not excluded_urls.url_disabled(flask.request.url) + ): + span = flask.request.environ.get(_ENVIRON_SPAN_KEY) + + propagator = get_global_response_propagator() + if propagator: + propagator.inject( + response_headers, + setter=otel_wsgi.default_response_propagation_setter, + ) + + if span: + otel_wsgi.add_response_attributes( + span, status, response_headers + ) + status_code = otel_wsgi._parse_status_code(status) + if status_code is not None: + duration_attrs[ + SpanAttributes.HTTP_STATUS_CODE + ] = status_code + if ( + span.is_recording() + and span.kind == trace.SpanKind.SERVER + ): + custom_attributes = otel_wsgi.collect_custom_response_headers_attributes( + response_headers + ) + if len(custom_attributes) > 0: + span.set_attributes(custom_attributes) + else: + _logger.warning( + "Flask environ's OpenTelemetry span " + "missing at _start_response(%s)", + status, + ) + if response_hook is not None: + response_hook(span, status, response_headers) + return start_response(status, response_headers, *args, **kwargs) + + result = wsgi_app(wrapped_app_environ, _start_response) + duration = max(round((default_timer() - start) * 1000), 0) + duration_histogram.record(duration, duration_attrs) + active_requests_counter.add(-1, active_requests_count_attrs) + return result + + return _wrapped_app + + +def _wrapped_before_request( + request_hook=None, + tracer=None, + excluded_urls=None, + enable_commenter=True, + commenter_options=None, +): + def _before_request(): + if excluded_urls and excluded_urls.url_disabled(flask.request.url): + return + flask_request_environ = flask.request.environ + span_name = get_default_span_name() + + span, token = _start_internal_or_server_span( + tracer=tracer, + span_name=span_name, + start_time=flask_request_environ.get(_ENVIRON_STARTTIME_KEY), + context_carrier=flask_request_environ, + context_getter=otel_wsgi.wsgi_getter, + ) + + if request_hook: + request_hook(span, flask_request_environ) + + if span.is_recording(): + attributes = otel_wsgi.collect_request_attributes( + flask_request_environ + ) + if flask.request.url_rule: + # For 404 that result from no route found, etc, we + # don't have a url_rule. + attributes[ + SpanAttributes.HTTP_ROUTE + ] = flask.request.url_rule.rule + for key, value in attributes.items(): + span.set_attribute(key, value) + if span.is_recording() and span.kind == trace.SpanKind.SERVER: + custom_attributes = ( + otel_wsgi.collect_custom_request_headers_attributes( + flask_request_environ + ) + ) + if len(custom_attributes) > 0: + span.set_attributes(custom_attributes) + + activation = trace.use_span(span, end_on_exit=True) + activation.__enter__() # pylint: disable=E1101 + flask_request_environ[_ENVIRON_ACTIVATION_KEY] = activation + flask_request_environ[_ENVIRON_THREAD_ID_KEY] = get_ident() + flask_request_environ[_ENVIRON_SPAN_KEY] = span + flask_request_environ[_ENVIRON_TOKEN] = token + + if enable_commenter: + current_context = context.get_current() + flask_info = {} + + # https://flask.palletsprojects.com/en/1.1.x/api/#flask.has_request_context + if flask and flask.request: + if commenter_options.get("framework", True): + flask_info["framework"] = f"flask:{flask.__version__}" + if ( + commenter_options.get("controller", True) + and flask.request.endpoint + ): + flask_info["controller"] = flask.request.endpoint + if ( + commenter_options.get("route", True) + and flask.request.url_rule + and flask.request.url_rule.rule + ): + flask_info["route"] = flask.request.url_rule.rule + sqlcommenter_context = context.set_value( + "SQLCOMMENTER_ORM_TAGS_AND_VALUES", flask_info, current_context + ) + context.attach(sqlcommenter_context) + + return _before_request + + +def _wrapped_teardown_request( + excluded_urls=None, +): + def _teardown_request(exc): + # pylint: disable=E1101 + if excluded_urls and excluded_urls.url_disabled(flask.request.url): + return + + activation = flask.request.environ.get(_ENVIRON_ACTIVATION_KEY) + thread_id = flask.request.environ.get(_ENVIRON_THREAD_ID_KEY) + if not activation or thread_id != get_ident(): + # This request didn't start a span, maybe because it was created in + # a way that doesn't run `before_request`, like when it is created + # with `app.test_request_context`. + # + # Similarly, check the thread_id against the current thread to ensure + # tear down only happens on the original thread. This situation can + # arise if the original thread handling the request spawn children + # threads and then uses something like copy_current_request_context + # to copy the request context. + return + if exc is None: + activation.__exit__(None, None, None) + else: + activation.__exit__( + type(exc), exc, getattr(exc, "__traceback__", None) + ) + + if flask.request.environ.get(_ENVIRON_TOKEN, None): + context.detach(flask.request.environ.get(_ENVIRON_TOKEN)) + + return _teardown_request + + +class _InstrumentedFlask(flask.Flask): + _excluded_urls = None + _tracer_provider = None + _request_hook = None + _response_hook = None + _enable_commenter = True + _commenter_options = None + _meter_provider = None + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self._original_wsgi_app = self.wsgi_app + self._is_instrumented_by_opentelemetry = True + + meter = get_meter( + __name__, __version__, _InstrumentedFlask._meter_provider + ) + duration_histogram = meter.create_histogram( + name=MetricInstruments.HTTP_SERVER_DURATION, + unit="ms", + description="measures the duration of the inbound HTTP request", + ) + active_requests_counter = meter.create_up_down_counter( + name=MetricInstruments.HTTP_SERVER_ACTIVE_REQUESTS, + unit="requests", + description="measures the number of concurrent HTTP requests that are currently in-flight", + ) + + self.wsgi_app = _rewrapped_app( + self.wsgi_app, + active_requests_counter, + duration_histogram, + _InstrumentedFlask._response_hook, + excluded_urls=_InstrumentedFlask._excluded_urls, + ) + + tracer = trace.get_tracer( + __name__, __version__, _InstrumentedFlask._tracer_provider + ) + + _before_request = _wrapped_before_request( + _InstrumentedFlask._request_hook, + tracer, + excluded_urls=_InstrumentedFlask._excluded_urls, + enable_commenter=_InstrumentedFlask._enable_commenter, + commenter_options=_InstrumentedFlask._commenter_options, + ) + self._before_request = _before_request + self.before_request(_before_request) + + _teardown_request = _wrapped_teardown_request( + excluded_urls=_InstrumentedFlask._excluded_urls, + ) + self.teardown_request(_teardown_request) + + +class FlaskInstrumentor(BaseInstrumentor): + # pylint: disable=protected-access,attribute-defined-outside-init + """An instrumentor for flask.Flask + + See `BaseInstrumentor` + """ + + def instrumentation_dependencies(self) -> Collection[str]: + return _instruments + + def _instrument(self, **kwargs): + self._original_flask = flask.Flask + request_hook = kwargs.get("request_hook") + response_hook = kwargs.get("response_hook") + if callable(request_hook): + _InstrumentedFlask._request_hook = request_hook + if callable(response_hook): + _InstrumentedFlask._response_hook = response_hook + tracer_provider = kwargs.get("tracer_provider") + _InstrumentedFlask._tracer_provider = tracer_provider + excluded_urls = kwargs.get("excluded_urls") + _InstrumentedFlask._excluded_urls = ( + _excluded_urls_from_env + if excluded_urls is None + else parse_excluded_urls(excluded_urls) + ) + enable_commenter = kwargs.get("enable_commenter", True) + _InstrumentedFlask._enable_commenter = enable_commenter + + commenter_options = kwargs.get("commenter_options", {}) + _InstrumentedFlask._commenter_options = commenter_options + meter_provider = kwargs.get("meter_provider") + _InstrumentedFlask._meter_provider = meter_provider + flask.Flask = _InstrumentedFlask + + def _uninstrument(self, **kwargs): + flask.Flask = self._original_flask + + @staticmethod + def instrument_app( + app, + request_hook=None, + response_hook=None, + tracer_provider=None, + excluded_urls=None, + enable_commenter=True, + commenter_options=None, + meter_provider=None, + ): + if not hasattr(app, "_is_instrumented_by_opentelemetry"): + app._is_instrumented_by_opentelemetry = False + + if not app._is_instrumented_by_opentelemetry: + excluded_urls = ( + parse_excluded_urls(excluded_urls) + if excluded_urls is not None + else _excluded_urls_from_env + ) + meter = get_meter(__name__, __version__, meter_provider) + duration_histogram = meter.create_histogram( + name=MetricInstruments.HTTP_SERVER_DURATION, + unit="ms", + description="measures the duration of the inbound HTTP request", + ) + active_requests_counter = meter.create_up_down_counter( + name=MetricInstruments.HTTP_SERVER_ACTIVE_REQUESTS, + unit="requests", + description="measures the number of concurrent HTTP requests that are currently in-flight", + ) + + app._original_wsgi_app = app.wsgi_app + app.wsgi_app = _rewrapped_app( + app.wsgi_app, + active_requests_counter, + duration_histogram, + response_hook, + excluded_urls=excluded_urls, + ) + + tracer = trace.get_tracer(__name__, __version__, tracer_provider) + + _before_request = _wrapped_before_request( + request_hook, + tracer, + excluded_urls=excluded_urls, + enable_commenter=enable_commenter, + commenter_options=commenter_options + if commenter_options + else {}, + ) + app._before_request = _before_request + app.before_request(_before_request) + + _teardown_request = _wrapped_teardown_request( + excluded_urls=excluded_urls, + ) + app._teardown_request = _teardown_request + app.teardown_request(_teardown_request) + app._is_instrumented_by_opentelemetry = True + else: + _logger.warning( + "Attempting to instrument Flask app while already instrumented" + ) + + @staticmethod + def uninstrument_app(app): + if hasattr(app, "_original_wsgi_app"): + app.wsgi_app = app._original_wsgi_app + + # FIXME add support for other Flask blueprints that are not None + app.before_request_funcs[None].remove(app._before_request) + app.teardown_request_funcs[None].remove(app._teardown_request) + del app._original_wsgi_app + app._is_instrumented_by_opentelemetry = False + else: + _logger.warning( + "Attempting to uninstrument Flask " + "app while already uninstrumented" + ) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/flask/package.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/flask/package.py new file mode 100644 index 00000000..33bfe4cc --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/flask/package.py @@ -0,0 +1,18 @@ +# 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 = ("flask >= 1.0, < 3.0",) + +_supports_metrics = True diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/flask/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/flask/version.py new file mode 100644 index 00000000..88e9292a --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/flask/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.38b0" diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/psycopg2/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/psycopg2/__init__.py new file mode 100644 index 00000000..0ff18571 --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/psycopg2/__init__.py @@ -0,0 +1,269 @@ +# 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. + +""" +The integration with PostgreSQL supports the `Psycopg`_ library, it can be enabled by +using ``Psycopg2Instrumentor``. + +.. _Psycopg: http://initd.org/psycopg/ + +SQLCOMMENTER +***************************************** +You can optionally configure Psycopg2 instrumentation to enable sqlcommenter which enriches +the query with contextual information. + +Usage +----- + +.. code:: python + + from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.psycopg2 import Psycopg2Instrumentor + + Psycopg2Instrumentor().instrument(enable_commenter=True, commenter_options={}) + + +For example, +:: + + Invoking cursor.execute("select * from auth_users") will lead to sql query "select * from auth_users" but when SQLCommenter is enabled + the query will get appended with some configurable tags like "select * from auth_users /*tag=value*/;" + + +SQLCommenter Configurations +*************************** +We can configure the tags to be appended to the sqlquery log by adding configuration inside commenter_options(default:{}) keyword + +db_driver = True(Default) or False + +For example, +:: +Enabling this flag will add psycopg2 and it's version which is /*psycopg2%%3A2.9.3*/ + +dbapi_threadsafety = True(Default) or False + +For example, +:: +Enabling this flag will add threadsafety /*dbapi_threadsafety=2*/ + +dbapi_level = True(Default) or False + +For example, +:: +Enabling this flag will add dbapi_level /*dbapi_level='2.0'*/ + +libpq_version = True(Default) or False + +For example, +:: +Enabling this flag will add libpq_version /*libpq_version=140001*/ + +driver_paramstyle = True(Default) or False + +For example, +:: +Enabling this flag will add driver_paramstyle /*driver_paramstyle='pyformat'*/ + +opentelemetry_values = True(Default) or False + +For example, +:: +Enabling this flag will add traceparent values /*traceparent='00-03afa25236b8cd948fa853d67038ac79-405ff022e8247c46-01'*/ + +Usage +----- + +.. code-block:: python + + import psycopg2 + from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.psycopg2 import Psycopg2Instrumentor + + + Psycopg2Instrumentor().instrument() + + cnx = psycopg2.connect(database='Database') + cursor = cnx.cursor() + cursor.execute("INSERT INTO test (testField) VALUES (123)") + cursor.close() + cnx.close() + +API +--- +""" + +import logging +import typing +from typing import Collection + +import psycopg2 +from psycopg2.extensions import ( + cursor as pg_cursor, # pylint: disable=no-name-in-module +) +from psycopg2.sql import Composed # pylint: disable=no-name-in-module + +from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation import ( + dbapi, +) +from opentelemetry.instrumentation.instrumentor import BaseInstrumentor +from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.psycopg2.package import ( + _instruments, +) +from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.psycopg2.version import ( + __version__, +) + +_logger = logging.getLogger(__name__) +_OTEL_CURSOR_FACTORY_KEY = "_otel_orig_cursor_factory" + + +class Psycopg2Instrumentor(BaseInstrumentor): + _CONNECTION_ATTRIBUTES = { + "database": "info.dbname", + "port": "info.port", + "host": "info.host", + "user": "info.user", + } + + _DATABASE_SYSTEM = "postgresql" + + def instrumentation_dependencies(self) -> Collection[str]: + return _instruments + + def _instrument(self, **kwargs): + """Integrate with PostgreSQL Psycopg library. + Psycopg: http://initd.org/psycopg/ + """ + tracer_provider = kwargs.get("tracer_provider") + enable_sqlcommenter = kwargs.get("enable_commenter", False) + commenter_options = kwargs.get("commenter_options", {}) + dbapi.wrap_connect( + __name__, + psycopg2, + "connect", + self._DATABASE_SYSTEM, + self._CONNECTION_ATTRIBUTES, + version=__version__, + tracer_provider=tracer_provider, + db_api_integration_factory=DatabaseApiIntegration, + enable_commenter=enable_sqlcommenter, + commenter_options=commenter_options, + ) + + def _uninstrument(self, **kwargs): + """ "Disable Psycopg2 instrumentation""" + dbapi.unwrap_connect(psycopg2, "connect") + + # TODO(owais): check if core dbapi can do this for all dbapi implementations e.g, pymysql and mysql + @staticmethod + def instrument_connection(connection, tracer_provider=None): + if not hasattr(connection, "_is_instrumented_by_opentelemetry"): + connection._is_instrumented_by_opentelemetry = False + + if not connection._is_instrumented_by_opentelemetry: + setattr( + connection, _OTEL_CURSOR_FACTORY_KEY, connection.cursor_factory + ) + connection.cursor_factory = _new_cursor_factory( + tracer_provider=tracer_provider + ) + connection._is_instrumented_by_opentelemetry = True + else: + _logger.warning( + "Attempting to instrument Psycopg connection while already instrumented" + ) + return connection + + # TODO(owais): check if core dbapi can do this for all dbapi implementations e.g, pymysql and mysql + @staticmethod + def uninstrument_connection(connection): + connection.cursor_factory = getattr( + connection, _OTEL_CURSOR_FACTORY_KEY, None + ) + + return connection + + +# TODO(owais): check if core dbapi can do this for all dbapi implementations e.g, pymysql and mysql +class DatabaseApiIntegration(dbapi.DatabaseApiIntegration): + def wrapped_connection( + self, + connect_method: typing.Callable[..., typing.Any], + args: typing.Tuple[typing.Any, typing.Any], + kwargs: typing.Dict[typing.Any, typing.Any], + ): + """Add object proxy to connection object.""" + base_cursor_factory = kwargs.pop("cursor_factory", None) + new_factory_kwargs = {"db_api": self} + if base_cursor_factory: + new_factory_kwargs["base_factory"] = base_cursor_factory + kwargs["cursor_factory"] = _new_cursor_factory(**new_factory_kwargs) + connection = connect_method(*args, **kwargs) + self.get_connection_attributes(connection) + return connection + + +class CursorTracer(dbapi.CursorTracer): + def get_operation_name(self, cursor, args): + if not args: + return "" + + statement = args[0] + if isinstance(statement, Composed): + statement = statement.as_string(cursor) + + if isinstance(statement, str): + # Strip leading comments so we get the operation name. + return self._leading_comment_remover.sub("", statement).split()[0] + + return "" + + def get_statement(self, cursor, args): + if not args: + return "" + + statement = args[0] + if isinstance(statement, Composed): + statement = statement.as_string(cursor) + return statement + + +def _new_cursor_factory(db_api=None, base_factory=None, tracer_provider=None): + if not db_api: + db_api = DatabaseApiIntegration( + __name__, + Psycopg2Instrumentor._DATABASE_SYSTEM, + connection_attributes=Psycopg2Instrumentor._CONNECTION_ATTRIBUTES, + version=__version__, + tracer_provider=tracer_provider, + ) + + base_factory = base_factory or pg_cursor + _cursor_tracer = CursorTracer(db_api) + + class TracedCursorFactory(base_factory): + def execute(self, *args, **kwargs): + return _cursor_tracer.traced_execution( + self, super().execute, *args, **kwargs + ) + + def executemany(self, *args, **kwargs): + return _cursor_tracer.traced_execution( + self, super().executemany, *args, **kwargs + ) + + def callproc(self, *args, **kwargs): + return _cursor_tracer.traced_execution( + self, super().callproc, *args, **kwargs + ) + + return TracedCursorFactory diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/psycopg2/package.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/psycopg2/package.py new file mode 100644 index 00000000..9757a8df --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/psycopg2/package.py @@ -0,0 +1,16 @@ +# 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 = ("psycopg2 >= 2.7.3.1",) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/psycopg2/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/psycopg2/version.py new file mode 100644 index 00000000..88e9292a --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/psycopg2/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.38b0" diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/requests/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/requests/__init__.py new file mode 100644 index 00000000..6ff4ea83 --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/requests/__init__.py @@ -0,0 +1,306 @@ +# 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 `_ library. + +Usage +----- + +.. code-block:: python + + import requests + from azure.monitor.opentelemetry.vendor.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 +------------- + +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 +--- +""" + +import functools +import types +from timeit import default_timer +from typing import Callable, Collection, Iterable, Optional +from urllib.parse import urlparse + +from requests.models import PreparedRequest, Response +from requests.sessions import Session +from requests.structures import CaseInsensitiveDict + +from opentelemetry import context + +# FIXME: fix the importing of this private attribute when the location of the _SUPPRESS_HTTP_INSTRUMENTATION_KEY is defined. +from opentelemetry.context import _SUPPRESS_HTTP_INSTRUMENTATION_KEY +from opentelemetry.instrumentation.instrumentor import BaseInstrumentor +from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.requests.package import ( + _instruments, +) +from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.requests.version import ( + __version__, +) +from opentelemetry.instrumentation.utils import ( + _SUPPRESS_INSTRUMENTATION_KEY, + http_status_to_status_code, +) +from opentelemetry.metrics import Histogram, get_meter +from opentelemetry.propagate import inject +from opentelemetry.semconv.metrics import MetricInstruments +from opentelemetry.semconv.trace import SpanAttributes +from opentelemetry.trace import SpanKind, Tracer, get_tracer +from opentelemetry.trace.span import Span +from opentelemetry.trace.status import Status +from azure.monitor.opentelemetry.vendor.opentelemetry.util.http import ( + get_excluded_urls, + parse_excluded_urls, + remove_url_credentials, +) +from azure.monitor.opentelemetry.vendor.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], None]] + + +# pylint: disable=unused-argument +# pylint: disable=R0915 +def _instrument( + tracer: Tracer, + duration_histogram: Histogram, + request_hook: _RequestHookT = None, + response_hook: _ResponseHookT = None, + excluded_urls: Iterable[str] = None, +): + """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, request, **kwargs): + 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 context.get_value( + _SUPPRESS_INSTRUMENTATION_KEY + ) or context.get_value(_SUPPRESS_HTTP_INSTRUMENTATION_KEY): + return wrapped_send(self, request, **kwargs) + + # See + # https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#http-client + method = request.method.upper() + span_name = get_default_span_name(method) + + url = remove_url_credentials(request.url) + + span_attributes = { + SpanAttributes.HTTP_METHOD: method, + SpanAttributes.HTTP_URL: url, + } + + metric_labels = { + SpanAttributes.HTTP_METHOD: method, + } + + try: + parsed_url = urlparse(url) + metric_labels[SpanAttributes.HTTP_SCHEME] = parsed_url.scheme + if parsed_url.hostname: + metric_labels[SpanAttributes.HTTP_HOST] = parsed_url.hostname + metric_labels[ + SpanAttributes.NET_PEER_NAME + ] = parsed_url.hostname + if parsed_url.port: + metric_labels[SpanAttributes.NET_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) + + token = context.attach( + context.set_value(_SUPPRESS_HTTP_INSTRUMENTATION_KEY, True) + ) + + 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( + round((default_timer() - start_time) * 1000), 0 + ) + context.detach(token) + + if isinstance(result, Response): + if span.is_recording(): + span.set_attribute( + SpanAttributes.HTTP_STATUS_CODE, result.status_code + ) + span.set_status( + Status(http_status_to_status_code(result.status_code)) + ) + + metric_labels[ + SpanAttributes.HTTP_STATUS_CODE + ] = result.status_code + + if result.raw is not None: + version = getattr(result.raw, "version", None) + if version: + metric_labels[SpanAttributes.HTTP_FLAVOR] = ( + "1.1" if version == 11 else "1.0" + ) + + if callable(response_hook): + response_hook(span, request, result) + + duration_histogram.record(elapsed_time, attributes=metric_labels) + + 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=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): + """Default implementation for name_callback, returns HTTP {method_name}.""" + return f"HTTP {method.strip()}" + + +class RequestsInstrumentor(BaseInstrumentor): + """An instrumentor for requests + See `BaseInstrumentor` + """ + + def instrumentation_dependencies(self) -> Collection[str]: + return _instruments + + def _instrument(self, **kwargs): + """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 + """ + tracer_provider = kwargs.get("tracer_provider") + tracer = get_tracer(__name__, __version__, tracer_provider) + excluded_urls = kwargs.get("excluded_urls") + meter_provider = kwargs.get("meter_provider") + meter = get_meter( + __name__, + __version__, + meter_provider, + ) + duration_histogram = meter.create_histogram( + name=MetricInstruments.HTTP_CLIENT_DURATION, + unit="ms", + description="measures the duration of the outbound HTTP request", + ) + _instrument( + tracer, + duration_histogram, + 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), + ) + + def _uninstrument(self, **kwargs): + _uninstrument() + + @staticmethod + def uninstrument_session(session): + """Disables instrumentation on the session object.""" + _uninstrument_from(session, restore_as_bound_func=True) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/requests/package.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/requests/package.py new file mode 100644 index 00000000..8424bfeb --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/requests/package.py @@ -0,0 +1,18 @@ +# 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 diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/requests/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/requests/version.py new file mode 100644 index 00000000..88e9292a --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/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.38b0" diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib/__init__.py new file mode 100644 index 00000000..f13bbcb7 --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib/__init__.py @@ -0,0 +1,310 @@ +# 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 +`urllib `_ library. + +Usage +----- +.. code-block:: python + + from urllib import request + from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.urllib import URLLibInstrumentor + + # You can optionally pass a custom TracerProvider to + # URLLibInstrumentor().instrument() + + URLLibInstrumentor().instrument() + req = request.Request('https://postman-echo.com/post', method="POST") + r = request.urlopen(req) + +Configuration +------------- + +Request/Response hooks +********************** + +The urllib 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 + + # `request_obj` is an instance of urllib.request.Request + def request_hook(span, request_obj): + pass + + # `request_obj` is an instance of urllib.request.Request + # `response` is an instance of http.client.HTTPResponse + def response_hook(span, request_obj, response) + pass + + URLLibInstrumentor.instrument( + request_hook=request_hook, response_hook=response_hook) + ) + +API +--- +""" + +import functools +import types +import typing +from http import client +from timeit import default_timer +from typing import Collection, Dict +from urllib.request import ( # pylint: disable=no-name-in-module,import-error + OpenerDirector, + Request, +) + +from opentelemetry import context + +# FIXME: fix the importing of this private attribute when the location of the _SUPPRESS_HTTP_INSTRUMENTATION_KEY is defined. +from opentelemetry.context import _SUPPRESS_HTTP_INSTRUMENTATION_KEY +from opentelemetry.instrumentation.instrumentor import BaseInstrumentor +from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.urllib.package import ( + _instruments, +) +from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.urllib.version import ( + __version__, +) +from opentelemetry.instrumentation.utils import ( + _SUPPRESS_INSTRUMENTATION_KEY, + http_status_to_status_code, +) +from opentelemetry.metrics import Histogram, get_meter +from opentelemetry.propagate import inject +from opentelemetry.semconv.metrics import MetricInstruments +from opentelemetry.semconv.trace import SpanAttributes +from opentelemetry.trace import Span, SpanKind, get_tracer +from opentelemetry.trace.status import Status +from azure.monitor.opentelemetry.vendor.opentelemetry.util.http import ( + remove_url_credentials, +) + +_RequestHookT = typing.Optional[typing.Callable[[Span, Request], None]] +_ResponseHookT = typing.Optional[ + typing.Callable[[Span, Request, client.HTTPResponse], None] +] + + +class URLLibInstrumentor(BaseInstrumentor): + """An instrumentor for urllib + See `BaseInstrumentor` + """ + + def instrumentation_dependencies(self) -> Collection[str]: + return _instruments + + def _instrument(self, **kwargs): + """Instruments urllib module + + Args: + **kwargs: Optional arguments + ``tracer_provider``: a TracerProvider, defaults to global + ``request_hook``: An optional callback invoked 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 + """ + tracer_provider = kwargs.get("tracer_provider") + tracer = get_tracer(__name__, __version__, tracer_provider) + + meter_provider = kwargs.get("meter_provider") + meter = get_meter(__name__, __version__, meter_provider) + + histograms = _create_client_histograms(meter) + + _instrument( + tracer, + histograms, + request_hook=kwargs.get("request_hook"), + response_hook=kwargs.get("response_hook"), + ) + + def _uninstrument(self, **kwargs): + _uninstrument() + + def uninstrument_opener( + self, opener: OpenerDirector + ): # pylint: disable=no-self-use + """uninstrument_opener a specific instance of urllib.request.OpenerDirector""" + _uninstrument_from(opener, restore_as_bound_func=True) + + +def _instrument( + tracer, + histograms: Dict[str, Histogram], + request_hook: _RequestHookT = None, + response_hook: _ResponseHookT = None, +): + """Enables tracing of all requests calls that go through + :code:`urllib.Client._make_request`""" + + opener_open = OpenerDirector.open + + @functools.wraps(opener_open) + def instrumented_open(opener, fullurl, data=None, timeout=None): + if isinstance(fullurl, str): + request_ = Request(fullurl, data) + else: + request_ = fullurl + + def get_or_create_headers(): + return getattr(request_, "headers", {}) + + def call_wrapped(): + return opener_open(opener, request_, data=data, timeout=timeout) + + return _instrumented_open_call( + opener, request_, call_wrapped, get_or_create_headers + ) + + def _instrumented_open_call( + _, request, call_wrapped, get_or_create_headers + ): # pylint: disable=too-many-locals + if context.get_value( + _SUPPRESS_INSTRUMENTATION_KEY + ) or context.get_value(_SUPPRESS_HTTP_INSTRUMENTATION_KEY): + return call_wrapped() + + method = request.get_method().upper() + url = request.full_url + + span_name = f"HTTP {method}".strip() + + url = remove_url_credentials(url) + + labels = { + SpanAttributes.HTTP_METHOD: method, + SpanAttributes.HTTP_URL: url, + } + + with tracer.start_as_current_span( + span_name, kind=SpanKind.CLIENT, attributes=labels + ) as span: + exception = None + if callable(request_hook): + request_hook(span, request) + + headers = get_or_create_headers() + inject(headers) + + token = context.attach( + context.set_value(_SUPPRESS_HTTP_INSTRUMENTATION_KEY, True) + ) + try: + start_time = default_timer() + result = call_wrapped() # *** PROCEED + except Exception as exc: # pylint: disable=W0703 + exception = exc + result = getattr(exc, "file", None) + finally: + elapsed_time = round((default_timer() - start_time) * 1000) + context.detach(token) + + if result is not None: + code_ = result.getcode() + labels[SpanAttributes.HTTP_STATUS_CODE] = str(code_) + + if span.is_recording() and code_ is not None: + span.set_attribute(SpanAttributes.HTTP_STATUS_CODE, code_) + span.set_status(Status(http_status_to_status_code(code_))) + + ver_ = str(getattr(result, "version", "")) + if ver_: + labels[ + SpanAttributes.HTTP_FLAVOR + ] = f"{ver_[:1]}.{ver_[:-1]}" + + _record_histograms( + histograms, labels, request, result, elapsed_time + ) + + if callable(response_hook): + response_hook(span, request, result) + + if exception is not None: + raise exception.with_traceback(exception.__traceback__) + + return result + + instrumented_open.opentelemetry_instrumentation_urllib_applied = True + OpenerDirector.open = instrumented_open + + +def _uninstrument(): + """Disables instrumentation of :code:`urllib` through this module. + + Note that this only works if no other module also patches urllib.""" + _uninstrument_from(OpenerDirector) + + +def _uninstrument_from(instr_root, restore_as_bound_func=False): + instr_func_name = "open" + instr_func = getattr(instr_root, instr_func_name) + if not getattr( + instr_func, + "opentelemetry_instrumentation_urllib_applied", + False, + ): + return + + 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 _create_client_histograms(meter) -> Dict[str, Histogram]: + histograms = { + MetricInstruments.HTTP_CLIENT_DURATION: meter.create_histogram( + name=MetricInstruments.HTTP_CLIENT_DURATION, + unit="ms", + description="measures the duration outbound HTTP requests", + ), + MetricInstruments.HTTP_CLIENT_REQUEST_SIZE: meter.create_histogram( + name=MetricInstruments.HTTP_CLIENT_REQUEST_SIZE, + unit="By", + description="measures the size of HTTP request messages (compressed)", + ), + MetricInstruments.HTTP_CLIENT_RESPONSE_SIZE: meter.create_histogram( + name=MetricInstruments.HTTP_CLIENT_RESPONSE_SIZE, + unit="By", + description="measures the size of HTTP response messages (compressed)", + ), + } + + return histograms + + +def _record_histograms( + histograms, metric_attributes, request, response, elapsed_time +): + histograms[MetricInstruments.HTTP_CLIENT_DURATION].record( + elapsed_time, attributes=metric_attributes + ) + + data = getattr(request, "data", None) + request_size = 0 if data is None else len(data) + histograms[MetricInstruments.HTTP_CLIENT_REQUEST_SIZE].record( + request_size, attributes=metric_attributes + ) + + if response is not None: + response_size = int(response.headers.get("Content-Length", 0)) + histograms[MetricInstruments.HTTP_CLIENT_RESPONSE_SIZE].record( + response_size, attributes=metric_attributes + ) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib/package.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib/package.py new file mode 100644 index 00000000..942f175d --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib/package.py @@ -0,0 +1,18 @@ +# 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 = tuple() + +_supports_metrics = True diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib/version.py new file mode 100644 index 00000000..2cfda14d --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib/version.py @@ -0,0 +1,17 @@ +# 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.38b0" + +_instruments = tuple() diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib3/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib3/__init__.py new file mode 100644 index 00000000..2ec1d57d --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib3/__init__.py @@ -0,0 +1,361 @@ +# 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 +`urllib3 `_ library. + +Usage +----- +.. code-block:: python + + import urllib3 + from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.urllib3 import URLLib3Instrumentor + + def strip_query_params(url: str) -> str: + return url.split("?")[0] + + URLLib3Instrumentor().instrument( + # Remove all query params from the URL attribute on the span. + url_filter=strip_query_params, + ) + + http = urllib3.PoolManager() + response = http.request("GET", "https://www.example.org/") + +Configuration +------------- + +Request/Response hooks +********************** + +The urllib3 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 + + # `request` is an instance of urllib3.connectionpool.HTTPConnectionPool + def request_hook(span, request): + pass + + # `request` is an instance of urllib3.connectionpool.HTTPConnectionPool + # `response` is an instance of urllib3.response.HTTPResponse + def response_hook(span, request, response): + pass + + URLLib3Instrumentor.instrument( + request_hook=request_hook, response_hook=response_hook) + ) + +API +--- +""" + +import collections.abc +import contextlib +import io +import typing +from timeit import default_timer +from typing import Collection + +import urllib3.connectionpool +import wrapt + +from opentelemetry import context + +# FIXME: fix the importing of this private attribute when the location of the _SUPPRESS_HTTP_INSTRUMENTATION_KEY is defined. +from opentelemetry.context import _SUPPRESS_HTTP_INSTRUMENTATION_KEY +from opentelemetry.instrumentation.instrumentor import BaseInstrumentor +from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.urllib3.package import ( + _instruments, +) +from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.urllib3.version import ( + __version__, +) +from opentelemetry.instrumentation.utils import ( + _SUPPRESS_INSTRUMENTATION_KEY, + http_status_to_status_code, + unwrap, +) +from opentelemetry.metrics import Histogram, get_meter +from opentelemetry.propagate import inject +from opentelemetry.semconv.metrics import MetricInstruments +from opentelemetry.semconv.trace import SpanAttributes +from opentelemetry.trace import Span, SpanKind, Tracer, get_tracer +from opentelemetry.trace.status import Status +from azure.monitor.opentelemetry.vendor.opentelemetry.util.http.httplib import ( + set_ip_on_next_http_connection, +) + +_UrlFilterT = typing.Optional[typing.Callable[[str], str]] +_RequestHookT = typing.Optional[ + typing.Callable[ + [ + Span, + urllib3.connectionpool.HTTPConnectionPool, + typing.Dict, + typing.Optional[str], + ], + None, + ] +] +_ResponseHookT = typing.Optional[ + typing.Callable[ + [ + Span, + urllib3.connectionpool.HTTPConnectionPool, + urllib3.response.HTTPResponse, + ], + None, + ] +] + +_URL_OPEN_ARG_TO_INDEX_MAPPING = { + "method": 0, + "url": 1, + "body": 2, +} + + +class URLLib3Instrumentor(BaseInstrumentor): + def instrumentation_dependencies(self) -> Collection[str]: + return _instruments + + def _instrument(self, **kwargs): + """Instruments the urllib3 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. + ``url_filter``: A callback to process the requested URL prior + to adding it as a span attribute. + """ + tracer_provider = kwargs.get("tracer_provider") + tracer = get_tracer(__name__, __version__, tracer_provider) + + meter_provider = kwargs.get("meter_provider") + meter = get_meter(__name__, __version__, meter_provider) + + duration_histogram = meter.create_histogram( + name=MetricInstruments.HTTP_CLIENT_DURATION, + unit="ms", + description="measures the duration outbound HTTP requests", + ) + request_size_histogram = meter.create_histogram( + name=MetricInstruments.HTTP_CLIENT_REQUEST_SIZE, + unit="By", + description="measures the size of HTTP request messages (compressed)", + ) + response_size_histogram = meter.create_histogram( + name=MetricInstruments.HTTP_CLIENT_RESPONSE_SIZE, + unit="By", + description="measures the size of HTTP response messages (compressed)", + ) + + _instrument( + tracer, + duration_histogram, + request_size_histogram, + response_size_histogram, + request_hook=kwargs.get("request_hook"), + response_hook=kwargs.get("response_hook"), + url_filter=kwargs.get("url_filter"), + ) + + def _uninstrument(self, **kwargs): + _uninstrument() + + +def _instrument( + tracer: Tracer, + duration_histogram: Histogram, + request_size_histogram: Histogram, + response_size_histogram: Histogram, + request_hook: _RequestHookT = None, + response_hook: _ResponseHookT = None, + url_filter: _UrlFilterT = None, +): + def instrumented_urlopen(wrapped, instance, args, kwargs): + if _is_instrumentation_suppressed(): + return wrapped(*args, **kwargs) + + method = _get_url_open_arg("method", args, kwargs).upper() + url = _get_url(instance, args, kwargs, url_filter) + headers = _prepare_headers(kwargs) + body = _get_url_open_arg("body", args, kwargs) + + span_name = f"HTTP {method.strip()}" + span_attributes = { + SpanAttributes.HTTP_METHOD: method, + SpanAttributes.HTTP_URL: url, + } + + with tracer.start_as_current_span( + span_name, kind=SpanKind.CLIENT, attributes=span_attributes + ) as span, set_ip_on_next_http_connection(span): + if callable(request_hook): + request_hook(span, instance, headers, body) + inject(headers) + + with _suppress_further_instrumentation(): + start_time = default_timer() + response = wrapped(*args, **kwargs) + elapsed_time = round((default_timer() - start_time) * 1000) + + _apply_response(span, response) + if callable(response_hook): + response_hook(span, instance, response) + + request_size = _get_body_size(body) + response_size = int(response.headers.get("Content-Length", 0)) + + metric_attributes = _create_metric_attributes( + instance, response, method + ) + + duration_histogram.record( + elapsed_time, attributes=metric_attributes + ) + if request_size is not None: + request_size_histogram.record( + request_size, attributes=metric_attributes + ) + response_size_histogram.record( + response_size, attributes=metric_attributes + ) + + return response + + wrapt.wrap_function_wrapper( + urllib3.connectionpool.HTTPConnectionPool, + "urlopen", + instrumented_urlopen, + ) + + +def _get_url_open_arg(name: str, args: typing.List, kwargs: typing.Mapping): + arg_idx = _URL_OPEN_ARG_TO_INDEX_MAPPING.get(name) + if arg_idx is not None: + try: + return args[arg_idx] + except IndexError: + pass + return kwargs.get(name) + + +def _get_url( + instance: urllib3.connectionpool.HTTPConnectionPool, + args: typing.List, + kwargs: typing.Mapping, + url_filter: _UrlFilterT, +) -> str: + url_or_path = _get_url_open_arg("url", args, kwargs) + if not url_or_path.startswith("/"): + url = url_or_path + else: + url = instance.scheme + "://" + instance.host + if _should_append_port(instance.scheme, instance.port): + url += ":" + str(instance.port) + url += url_or_path + + if url_filter: + return url_filter(url) + return url + + +def _get_body_size(body: object) -> typing.Optional[int]: + if body is None: + return 0 + if isinstance(body, collections.abc.Sized): + return len(body) + if isinstance(body, io.BytesIO): + return body.getbuffer().nbytes + return None + + +def _should_append_port(scheme: str, port: typing.Optional[int]) -> bool: + if not port: + return False + if scheme == "http" and port == 80: + return False + if scheme == "https" and port == 443: + return False + return True + + +def _prepare_headers(urlopen_kwargs: typing.Dict) -> typing.Dict: + headers = urlopen_kwargs.get("headers") + + # avoid modifying original headers on inject + headers = headers.copy() if headers is not None else {} + urlopen_kwargs["headers"] = headers + + return headers + + +def _apply_response(span: Span, response: urllib3.response.HTTPResponse): + if not span.is_recording(): + return + + span.set_attribute(SpanAttributes.HTTP_STATUS_CODE, response.status) + span.set_status(Status(http_status_to_status_code(response.status))) + + +def _is_instrumentation_suppressed() -> bool: + return bool( + context.get_value(_SUPPRESS_INSTRUMENTATION_KEY) + or context.get_value(_SUPPRESS_HTTP_INSTRUMENTATION_KEY) + ) + + +def _create_metric_attributes( + instance: urllib3.connectionpool.HTTPConnectionPool, + response: urllib3.response.HTTPResponse, + method: str, +) -> dict: + metric_attributes = { + SpanAttributes.HTTP_METHOD: method, + SpanAttributes.HTTP_HOST: instance.host, + SpanAttributes.HTTP_SCHEME: instance.scheme, + SpanAttributes.HTTP_STATUS_CODE: response.status, + SpanAttributes.NET_PEER_NAME: instance.host, + SpanAttributes.NET_PEER_PORT: instance.port, + } + + version = getattr(response, "version") + if version: + metric_attributes[SpanAttributes.HTTP_FLAVOR] = ( + "1.1" if version == 11 else "1.0" + ) + + return metric_attributes + + +@contextlib.contextmanager +def _suppress_further_instrumentation(): + token = context.attach( + context.set_value(_SUPPRESS_HTTP_INSTRUMENTATION_KEY, True) + ) + try: + yield + finally: + context.detach(token) + + +def _uninstrument(): + unwrap(urllib3.connectionpool.HTTPConnectionPool, "urlopen") diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib3/package.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib3/package.py new file mode 100644 index 00000000..2f5df62d --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib3/package.py @@ -0,0 +1,18 @@ +# 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 = ("urllib3 >= 1.0.0, < 2.0.0",) + +_supports_metrics = True diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib3/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib3/version.py new file mode 100644 index 00000000..88e9292a --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib3/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.38b0" diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/util/http/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/util/http/__init__.py new file mode 100644 index 00000000..f3d39ab0 --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/util/http/__init__.py @@ -0,0 +1,213 @@ +# 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 os import environ +from re import IGNORECASE as RE_IGNORECASE +from re import compile as re_compile +from re import search +from typing import Iterable, List +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" +) + +# 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: dict, header_regexes: list, normalize_function: callable + ) -> dict: + values = {} + + if header_regexes: + header_regexes_compiled = re_compile( + "|".join("^" + i + "$" for i in header_regexes), + RE_IGNORECASE, + ) + + for header_name in list( + filter( + header_regexes_compiled.match, + headers.keys(), + ) + ): + header_values = headers.get(header_name) + if header_values: + key = normalize_function(header_name.lower()) + values[key] = [ + self.sanitize_header_value( + header=header_name, value=header_values + ) + ] + + return values + + +_root = r"OTEL_PYTHON_{}" + + +def get_traced_request_attrs(instrumentation): + traced_request_attrs = environ.get( + _root.format(f"{instrumentation}_TRACED_REQUEST_ATTRS"), [] + ) + + if traced_request_attrs: + traced_request_attrs = [ + traced_request_attr.strip() + for traced_request_attr in traced_request_attrs.split(",") + ] + + return traced_request_attrs + + +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 = ( + (":".join(((parsed_url.hostname or ""), str(parsed_url.port)))) + if parsed_url.port + else (parsed_url.hostname or "") + ) + 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}" + + +def get_custom_headers(env_var: str) -> List[str]: + custom_headers = environ.get(env_var, []) + if custom_headers: + custom_headers = [ + custom_headers.strip() + for custom_headers in custom_headers.split(",") + ] + return custom_headers + + +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 diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/util/http/httplib.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/util/http/httplib.py new file mode 100644 index 00000000..de95a0aa --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/util/http/httplib.py @@ -0,0 +1,179 @@ +# 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. +""" + +import contextlib +import http.client +import logging +import socket # pylint:disable=unused-import # Used for typing +import typing +from typing import Collection + +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__) + + +class HttpClientInstrumentor(BaseInstrumentor): + def instrumentation_dependencies(self) -> Collection[str]: + return () # This instruments http.client from stdlib; no extra deps. + + def _instrument(self, **kwargs): + """Instruments the http.client module (not creating spans on its own)""" + _instrument() + + def _uninstrument(self, **kwargs): + _uninstrument() + + +def _remove_nonrecording(spanlist: typing.List[Span]): + 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=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 = state.get("need_ip") # type: typing.List[Span] + if not spanlist: + return True + + # Remove all non-recording spans from the list. + if not _remove_nonrecording(spanlist): + return True + + sock = "" + try: + sock = conn.sock # type: typing.Optional[socket.socket] + 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, instance: http.client.HTTPConnection, args, kwargs +): + result = wrapped(*args, **kwargs) + trysetip(instance, loglevel=logging.WARNING) + return result + + +def instrument_connect(module, name="connect"): + """Instrument additional connect() methods, e.g. for derived classes.""" + + wrapt.wrap_function_wrapper( + module, + name, + _instrumented_connect, + ) + + +def _instrument(): + def instrumented_send( + wrapped, instance: http.client.HTTPConnection, args, kwargs + ): + 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() + + +def _getstate() -> typing.Optional[dict]: + return 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"] # type: typing.List[Span] + 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/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/util/http/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/util/http/version.py new file mode 100644 index 00000000..eb62a67e --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/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.39b0.dev" diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/wsgi/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/wsgi/__init__.py new file mode 100644 index 00000000..0f7409fe --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/wsgi/__init__.py @@ -0,0 +1,594 @@ +# 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 a WSGI middleware that can be used on any WSGI framework +(such as Django / Flask / Web.py) to track requests timing through OpenTelemetry. + +Usage (Flask) +------------- + +.. code-block:: python + + from flask import Flask + from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.wsgi import OpenTelemetryMiddleware + + app = Flask(__name__) + app.wsgi_app = OpenTelemetryMiddleware(app.wsgi_app) + + @app.route("/") + def hello(): + return "Hello!" + + if __name__ == "__main__": + app.run(debug=True) + + +Usage (Django) +-------------- + +Modify the application's ``wsgi.py`` file as shown below. + +.. code-block:: python + + import os + from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.wsgi import OpenTelemetryMiddleware + from django.core.wsgi import get_wsgi_application + + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings') + + application = get_wsgi_application() + application = OpenTelemetryMiddleware(application) + +Usage (Web.py) +-------------- + +.. code-block:: python + + import web + from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.wsgi import OpenTelemetryMiddleware + from cheroot import wsgi + + urls = ('/', 'index') + + + class index: + + def GET(self): + return "Hello, world!" + + + if __name__ == "__main__": + app = web.application(urls, globals()) + func = app.wsgifunc() + + func = OpenTelemetryMiddleware(func) + + server = wsgi.WSGIServer( + ("localhost", 5100), func, server_name="localhost" + ) + server.start() + +Configuration +------------- + +Request/Response hooks +********************** + +This instrumentation supports request and response hooks. These are functions that get called +right after a span is created for a request and right before the span is finished for the response. + +- The client request hook is called with the internal span and an instance of WSGIEnvironment when the method + ``receive`` is called. +- The client response hook is called with the internal span, the status of the response and a list of key-value (tuples) + representing the response headers returned from the response when the method ``send`` is called. + +For example, + +.. code-block:: python + + def request_hook(span: Span, environ: WSGIEnvironment): + if span and span.is_recording(): + span.set_attribute("custom_user_attribute_from_request_hook", "some-value") + + def response_hook(span: Span, environ: WSGIEnvironment, status: str, response_headers: List): + if span and span.is_recording(): + span.set_attribute("custom_user_attribute_from_response_hook", "some-value") + + OpenTelemetryMiddleware(request_hook=request_hook, response_hook=response_hook) + +Capture HTTP request and response headers +***************************************** +You can configure the agent to capture specified HTTP headers as span attributes, according to the +`semantic convention `_. + +Request headers +*************** +To capture HTTP request headers as span attributes, set the environment variable +``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST`` to a comma delimited list of HTTP header names. + +For example, +:: + + export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST="content-type,custom_request_header" + +will extract ``content-type`` and ``custom_request_header`` from the request headers and add them as span attributes. + +Request header names in WSGI are case-insensitive and ``-`` characters are replaced by ``_``. So, giving the header +name as ``CUStom_Header`` in the environment variable will capture the header named ``custom-header``. + +Regular expressions may also be used to match multiple headers that correspond to the given pattern. For example: +:: + + export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST="Accept.*,X-.*" + +Would match all request headers that start with ``Accept`` and ``X-``. + +To capture all request headers, set ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST`` to ``".*"``. +:: + + export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST=".*" + +The name of the added span attribute will follow the format ``http.request.header.`` where ```` +is the normalized HTTP header name (lowercase, with ``-`` replaced by ``_``). The value of the attribute will be a +single item list containing all the header values. + +For example: +``http.request.header.custom_request_header = [","]`` + +Response headers +**************** +To capture HTTP response headers as span attributes, set the environment variable +``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE`` to a comma delimited list of HTTP header names. + +For example, +:: + + export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE="content-type,custom_response_header" + +will extract ``content-type`` and ``custom_response_header`` from the response headers and add them as span attributes. + +Response header names in WSGI are case-insensitive. So, giving the header name as ``CUStom-Header`` in the environment +variable will capture the header named ``custom-header``. + +Regular expressions may also be used to match multiple headers that correspond to the given pattern. For example: +:: + + export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE="Content.*,X-.*" + +Would match all response headers that start with ``Content`` and ``X-``. + +To capture all response headers, set ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE`` to ``".*"``. +:: + + export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE=".*" + +The name of the added span attribute will follow the format ``http.response.header.`` where ```` +is the normalized HTTP header name (lowercase, with ``-`` replaced by ``_``). The value of the attribute will be a +single item list containing all the header values. + +For example: +``http.response.header.custom_response_header = [","]`` + +Sanitizing headers +****************** +In order to prevent storing sensitive data such as personally identifiable information (PII), session keys, passwords, +etc, set the environment variable ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS`` +to a comma delimited list of HTTP header names to be sanitized. Regexes may be used, and all header names will be +matched in a case-insensitive manner. + +For example, +:: + + export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS=".*session.*,set-cookie" + +will replace the value of headers such as ``session-id`` and ``set-cookie`` with ``[REDACTED]`` in the span. + +Note: + The environment variable names used to capture HTTP headers are still experimental, and thus are subject to change. + +API +--- +""" + +import functools +import typing +import wsgiref.util as wsgiref_util +from timeit import default_timer + +from opentelemetry import context, trace +from opentelemetry.instrumentation.utils import ( + _start_internal_or_server_span, + http_status_to_status_code, +) +from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.wsgi.version import ( + __version__, +) +from opentelemetry.metrics import get_meter +from opentelemetry.propagators.textmap import Getter +from opentelemetry.semconv.metrics import MetricInstruments +from opentelemetry.semconv.trace import SpanAttributes +from opentelemetry.trace.status import Status, StatusCode +from azure.monitor.opentelemetry.vendor.opentelemetry.util.http import ( + OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS, + OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST, + OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE, + SanitizeValue, + get_custom_headers, + normalise_request_header_name, + normalise_response_header_name, + remove_url_credentials, +) + +_HTTP_VERSION_PREFIX = "HTTP/" +_CARRIER_KEY_PREFIX = "HTTP_" +_CARRIER_KEY_PREFIX_LEN = len(_CARRIER_KEY_PREFIX) + +# List of recommended 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 WSGIGetter(Getter[dict]): + def get( + self, carrier: dict, key: str + ) -> typing.Optional[typing.List[str]]: + """Getter implementation to retrieve a HTTP header value from the + PEP3333-conforming WSGI environ + + Args: + carrier: WSGI environ object + key: header name in environ object + Returns: + A list with a single string with the header value if it exists, + else None. + """ + environ_key = "HTTP_" + key.upper().replace("-", "_") + value = carrier.get(environ_key) + if value is not None: + return [value] + return None + + def keys(self, carrier): + return [ + key[_CARRIER_KEY_PREFIX_LEN:].lower().replace("_", "-") + for key in carrier + if key.startswith(_CARRIER_KEY_PREFIX) + ] + + +wsgi_getter = WSGIGetter() + + +def setifnotnone(dic, key, value): + if value is not None: + dic[key] = value + + +def collect_request_attributes(environ): + """Collects HTTP request attributes from the PEP3333-conforming + WSGI environ and returns a dictionary to be used as span creation attributes. + """ + + result = { + SpanAttributes.HTTP_METHOD: environ.get("REQUEST_METHOD"), + SpanAttributes.HTTP_SERVER_NAME: environ.get("SERVER_NAME"), + SpanAttributes.HTTP_SCHEME: environ.get("wsgi.url_scheme"), + } + + host_port = environ.get("SERVER_PORT") + if host_port is not None and not host_port == "": + result.update({SpanAttributes.NET_HOST_PORT: int(host_port)}) + + setifnotnone(result, SpanAttributes.HTTP_HOST, environ.get("HTTP_HOST")) + target = environ.get("RAW_URI") + if target is None: # Note: `"" or None is None` + target = environ.get("REQUEST_URI") + if target is not None: + result[SpanAttributes.HTTP_TARGET] = target + else: + result[SpanAttributes.HTTP_URL] = remove_url_credentials( + wsgiref_util.request_uri(environ) + ) + + remote_addr = environ.get("REMOTE_ADDR") + if remote_addr: + result[SpanAttributes.NET_PEER_IP] = remote_addr + remote_host = environ.get("REMOTE_HOST") + if remote_host and remote_host != remote_addr: + result[SpanAttributes.NET_PEER_NAME] = remote_host + + user_agent = environ.get("HTTP_USER_AGENT") + if user_agent is not None and len(user_agent) > 0: + result[SpanAttributes.HTTP_USER_AGENT] = user_agent + + setifnotnone( + result, SpanAttributes.NET_PEER_PORT, environ.get("REMOTE_PORT") + ) + flavor = environ.get("SERVER_PROTOCOL", "") + if flavor.upper().startswith(_HTTP_VERSION_PREFIX): + flavor = flavor[len(_HTTP_VERSION_PREFIX) :] + if flavor: + result[SpanAttributes.HTTP_FLAVOR] = flavor + + return result + + +def collect_custom_request_headers_attributes(environ): + """Returns custom HTTP request headers which are configured by the user + from the PEP3333-conforming WSGI environ to be used as span creation attributes as described + in the specification https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#http-request-and-response-headers + """ + + sanitize = SanitizeValue( + get_custom_headers( + OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS + ) + ) + + headers = { + key[_CARRIER_KEY_PREFIX_LEN:].replace("_", "-"): val + for key, val in environ.items() + if key.startswith(_CARRIER_KEY_PREFIX) + } + + return sanitize.sanitize_header_values( + headers, + get_custom_headers( + OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST + ), + normalise_request_header_name, + ) + + +def collect_custom_response_headers_attributes(response_headers): + """Returns custom HTTP response headers which are configured by the user from the + PEP3333-conforming WSGI environ as described in the specification + https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#http-request-and-response-headers + """ + + sanitize = SanitizeValue( + get_custom_headers( + OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS + ) + ) + response_headers_dict = {} + if response_headers: + response_headers_dict = dict(response_headers) + + return sanitize.sanitize_header_values( + response_headers_dict, + get_custom_headers( + OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE + ), + normalise_response_header_name, + ) + + +def _parse_status_code(resp_status): + status_code, _ = resp_status.split(" ", 1) + try: + return int(status_code) + except ValueError: + return None + + +def _parse_active_request_count_attrs(req_attrs): + active_requests_count_attrs = {} + for attr_key in _active_requests_count_attrs: + if req_attrs.get(attr_key) is not None: + active_requests_count_attrs[attr_key] = req_attrs[attr_key] + return active_requests_count_attrs + + +def _parse_duration_attrs(req_attrs): + duration_attrs = {} + for attr_key in _duration_attrs: + if req_attrs.get(attr_key) is not None: + duration_attrs[attr_key] = req_attrs[attr_key] + return duration_attrs + + +def add_response_attributes( + span, start_response_status, response_headers +): # pylint: disable=unused-argument + """Adds HTTP response attributes to span using the arguments + passed to a PEP3333-conforming start_response callable. + """ + if not span.is_recording(): + return + status_code, _ = start_response_status.split(" ", 1) + + try: + status_code = int(status_code) + except ValueError: + span.set_status( + Status( + StatusCode.ERROR, + "Non-integer HTTP status: " + repr(status_code), + ) + ) + else: + span.set_attribute(SpanAttributes.HTTP_STATUS_CODE, status_code) + span.set_status( + Status(http_status_to_status_code(status_code, server_span=True)) + ) + + +def get_default_span_name(environ): + """Default implementation for name_callback, returns HTTP {METHOD_NAME}.""" + return f"HTTP {environ.get('REQUEST_METHOD', '')}".strip() + + +class OpenTelemetryMiddleware: + """The WSGI application middleware. + + This class is a PEP 3333 conforming WSGI middleware that starts and + annotates spans for any requests it is invoked with. + + Args: + wsgi: The WSGI application callable to forward requests to. + request_hook: Optional callback which is called with the server span and WSGI + environ object for every incoming request. + response_hook: Optional callback which is called with the server span, + WSGI environ, status_code and response_headers for every + incoming request. + tracer_provider: Optional tracer provider to use. If omitted the current + globally configured one is used. + """ + + def __init__( + self, + wsgi, + request_hook=None, + response_hook=None, + tracer_provider=None, + meter_provider=None, + ): + self.wsgi = wsgi + self.tracer = trace.get_tracer(__name__, __version__, tracer_provider) + self.meter = get_meter(__name__, __version__, meter_provider) + self.duration_histogram = self.meter.create_histogram( + name=MetricInstruments.HTTP_SERVER_DURATION, + unit="ms", + description="measures the duration of the inbound HTTP request", + ) + self.active_requests_counter = self.meter.create_up_down_counter( + name=MetricInstruments.HTTP_SERVER_ACTIVE_REQUESTS, + unit="requests", + description="measures the number of concurrent HTTP requests that are currently in-flight", + ) + self.request_hook = request_hook + self.response_hook = response_hook + + @staticmethod + def _create_start_response( + span, start_response, response_hook, duration_attrs + ): + @functools.wraps(start_response) + def _start_response(status, response_headers, *args, **kwargs): + add_response_attributes(span, status, response_headers) + status_code = _parse_status_code(status) + if status_code is not None: + duration_attrs[SpanAttributes.HTTP_STATUS_CODE] = status_code + if span.is_recording() and span.kind == trace.SpanKind.SERVER: + custom_attributes = collect_custom_response_headers_attributes( + response_headers + ) + if len(custom_attributes) > 0: + span.set_attributes(custom_attributes) + if response_hook: + response_hook(status, response_headers) + return start_response(status, response_headers, *args, **kwargs) + + return _start_response + + # pylint: disable=too-many-branches + def __call__(self, environ, start_response): + """The WSGI application + + Args: + environ: A WSGI environment. + start_response: The WSGI start_response callable. + """ + req_attrs = collect_request_attributes(environ) + active_requests_count_attrs = _parse_active_request_count_attrs( + req_attrs + ) + duration_attrs = _parse_duration_attrs(req_attrs) + + span, token = _start_internal_or_server_span( + tracer=self.tracer, + span_name=get_default_span_name(environ), + start_time=None, + context_carrier=environ, + context_getter=wsgi_getter, + attributes=req_attrs, + ) + if span.is_recording() and span.kind == trace.SpanKind.SERVER: + custom_attributes = collect_custom_request_headers_attributes( + environ + ) + if len(custom_attributes) > 0: + span.set_attributes(custom_attributes) + + if self.request_hook: + self.request_hook(span, environ) + + response_hook = self.response_hook + if response_hook: + response_hook = functools.partial(response_hook, span, environ) + + start = default_timer() + self.active_requests_counter.add(1, active_requests_count_attrs) + try: + with trace.use_span(span): + start_response = self._create_start_response( + span, start_response, response_hook, duration_attrs + ) + iterable = self.wsgi(environ, start_response) + return _end_span_after_iterating(iterable, span, token) + except Exception as ex: + if span.is_recording(): + span.set_status(Status(StatusCode.ERROR, str(ex))) + span.end() + if token is not None: + context.detach(token) + raise + finally: + duration = max(round((default_timer() - start) * 1000), 0) + self.duration_histogram.record(duration, duration_attrs) + self.active_requests_counter.add(-1, active_requests_count_attrs) + + +# Put this in a subfunction to not delay the call to the wrapped +# WSGI application (instrumentation should change the application +# behavior as little as possible). +def _end_span_after_iterating(iterable, span, token): + try: + with trace.use_span(span): + yield from iterable + finally: + close = getattr(iterable, "close", None) + if close: + close() + span.end() + if token is not None: + context.detach(token) + + +# TODO: inherit from opentelemetry.instrumentation.propagators.Setter + + +class ResponsePropagationSetter: + def set(self, carrier, key, value): # pylint: disable=no-self-use + carrier.append((key, value)) + + +default_response_propagation_setter = ResponsePropagationSetter() diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/wsgi/package.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/wsgi/package.py new file mode 100644 index 00000000..942f175d --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/wsgi/package.py @@ -0,0 +1,18 @@ +# 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 = tuple() + +_supports_metrics = True diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/wsgi/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/wsgi/version.py new file mode 100644 index 00000000..88e9292a --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/wsgi/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.38b0" diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/util/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/util/__init__.py new file mode 100644 index 00000000..0bdee620 --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/util/__init__.py @@ -0,0 +1,5 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License in the project root for +# license information. +# -------------------------------------------------------------------------- diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/util/http/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/util/http/__init__.py new file mode 100644 index 00000000..f3d39ab0 --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/util/http/__init__.py @@ -0,0 +1,213 @@ +# 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 os import environ +from re import IGNORECASE as RE_IGNORECASE +from re import compile as re_compile +from re import search +from typing import Iterable, List +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" +) + +# 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: dict, header_regexes: list, normalize_function: callable + ) -> dict: + values = {} + + if header_regexes: + header_regexes_compiled = re_compile( + "|".join("^" + i + "$" for i in header_regexes), + RE_IGNORECASE, + ) + + for header_name in list( + filter( + header_regexes_compiled.match, + headers.keys(), + ) + ): + header_values = headers.get(header_name) + if header_values: + key = normalize_function(header_name.lower()) + values[key] = [ + self.sanitize_header_value( + header=header_name, value=header_values + ) + ] + + return values + + +_root = r"OTEL_PYTHON_{}" + + +def get_traced_request_attrs(instrumentation): + traced_request_attrs = environ.get( + _root.format(f"{instrumentation}_TRACED_REQUEST_ATTRS"), [] + ) + + if traced_request_attrs: + traced_request_attrs = [ + traced_request_attr.strip() + for traced_request_attr in traced_request_attrs.split(",") + ] + + return traced_request_attrs + + +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 = ( + (":".join(((parsed_url.hostname or ""), str(parsed_url.port)))) + if parsed_url.port + else (parsed_url.hostname or "") + ) + 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}" + + +def get_custom_headers(env_var: str) -> List[str]: + custom_headers = environ.get(env_var, []) + if custom_headers: + custom_headers = [ + custom_headers.strip() + for custom_headers in custom_headers.split(",") + ] + return custom_headers + + +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 diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/util/http/httplib.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/util/http/httplib.py new file mode 100644 index 00000000..de95a0aa --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/util/http/httplib.py @@ -0,0 +1,179 @@ +# 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. +""" + +import contextlib +import http.client +import logging +import socket # pylint:disable=unused-import # Used for typing +import typing +from typing import Collection + +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__) + + +class HttpClientInstrumentor(BaseInstrumentor): + def instrumentation_dependencies(self) -> Collection[str]: + return () # This instruments http.client from stdlib; no extra deps. + + def _instrument(self, **kwargs): + """Instruments the http.client module (not creating spans on its own)""" + _instrument() + + def _uninstrument(self, **kwargs): + _uninstrument() + + +def _remove_nonrecording(spanlist: typing.List[Span]): + 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=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 = state.get("need_ip") # type: typing.List[Span] + if not spanlist: + return True + + # Remove all non-recording spans from the list. + if not _remove_nonrecording(spanlist): + return True + + sock = "" + try: + sock = conn.sock # type: typing.Optional[socket.socket] + 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, instance: http.client.HTTPConnection, args, kwargs +): + result = wrapped(*args, **kwargs) + trysetip(instance, loglevel=logging.WARNING) + return result + + +def instrument_connect(module, name="connect"): + """Instrument additional connect() methods, e.g. for derived classes.""" + + wrapt.wrap_function_wrapper( + module, + name, + _instrumented_connect, + ) + + +def _instrument(): + def instrumented_send( + wrapped, instance: http.client.HTTPConnection, args, kwargs + ): + 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() + + +def _getstate() -> typing.Optional[dict]: + return 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"] # type: typing.List[Span] + 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/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/util/http/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/util/http/version.py new file mode 100644 index 00000000..88e9292a --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/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.38b0" diff --git a/tox.ini b/tox.ini index b68dfd20..1cd93874 100644 --- a/tox.ini +++ b/tox.ini @@ -29,8 +29,8 @@ commands_pre = python -m pip install -e {toxinidir}/azure-monitor-opentelemetry commands = - black --config pyproject.toml {toxinidir} --diff --check - isort --settings-path .isort.cfg {toxinidir} --diff --check-only - flake8 --config .flake8 {toxinidir} + black --config pyproject.toml {toxinidir} --diff --check --exclude={toxinidir}/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor + isort --settings-path .isort.cfg {toxinidir} --diff --check-only --exclude={toxinidir}/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor + flake8 --config .flake8 {toxinidir} --exclude={toxinidir}/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor ; TODO ; pylint {toxinidir} From 49f78740e48601f18f236dc69ed159b364fa1b0b Mon Sep 17 00:00:00 2001 From: jerevoss Date: Mon, 22 May 2023 15:15:18 -0700 Subject: [PATCH 02/10] Changed distro to use vendored packages --- CHANGELOG.md | 2 + .../azure/monitor/opentelemetry/_configure.py | 34 +++++++++-------- azure-monitor-opentelemetry/setup.py | 24 ++++++++---- .../tests/configuration/test_configure.py | 38 +++++++++++-------- .../tests/instrumentation/test_django.py | 4 +- .../tests/instrumentation/test_fastapi.py | 4 +- .../tests/instrumentation/test_flask.py | 4 +- .../tests/instrumentation/test_psycopg2.py | 4 +- .../tests/instrumentation/test_requests.py | 4 +- .../tests/instrumentation/test_urllib.py | 4 +- .../tests/instrumentation/test_urllib3.py | 4 +- 11 files changed, 81 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13ffca69..db402498 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Vendor Instrumentations + ([#280](https://github.com/microsoft/ApplicationInsights-Python/pull/280)) - Update samples ([#281](https://github.com/microsoft/ApplicationInsights-Python/pull/281)) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py index 47873cbc..e3ca58be 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py @@ -22,9 +22,7 @@ ) from azure.monitor.opentelemetry.util.configurations import _get_configurations from opentelemetry._logs import get_logger_provider, set_logger_provider -from opentelemetry.instrumentation.dependencies import ( - get_dist_dependency_conflicts, -) +from opentelemetry.instrumentation.dependencies import get_dependency_conflicts from opentelemetry.instrumentation.instrumentor import BaseInstrumentor from opentelemetry.metrics import set_meter_provider from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler @@ -39,15 +37,15 @@ _logger = getLogger(__name__) -_SUPPORTED_INSTRUMENTED_LIBRARIES = ( - "django", - "fastapi", - "flask", - "psycopg2", - "requests", - "urllib", - "urllib3", -) +_SUPPORTED_INSTRUMENTED_LIBRARIES_TO_INSTRUMENTS_MAP = { + "django": ("django >= 1.10",), + "fastapi": ("fastapi ~= 0.58",), + "flask": ("flask >= 1.0, < 3.0",), + "psycopg2": ("psycopg2 >= 2.7.3.1",), + "requests": ("requests ~= 2.0",), + "urllib": tuple(), + "urllib3": ("urllib3 >= 1.0.0, < 2.0.0",), +} def configure_azure_monitor(**kwargs) -> None: @@ -125,13 +123,19 @@ def _setup_metrics(configurations: Dict[str, ConfigurationValue]): def _setup_instrumentations(): # use pkg_resources for now until https://github.com/open-telemetry/opentelemetry-python/pull/3168 is merged - for entry_point in iter_entry_points("opentelemetry_instrumentor"): + for entry_point in iter_entry_points("azure_monitor_opentelemetry_instrumentor"): lib_name = entry_point.name - if lib_name not in _SUPPORTED_INSTRUMENTED_LIBRARIES: + if ( + lib_name + not in _SUPPORTED_INSTRUMENTED_LIBRARIES_TO_INSTRUMENTS_MAP + ): continue try: # Check if dependent libraries/version are installed - conflict = get_dist_dependency_conflicts(entry_point.dist) + instruments = _SUPPORTED_INSTRUMENTED_LIBRARIES_TO_INSTRUMENTS_MAP[ + lib_name + ] + conflict = get_dependency_conflicts(instruments) if conflict: _logger.debug( "Skipping instrumentation %s: %s", diff --git a/azure-monitor-opentelemetry/setup.py b/azure-monitor-opentelemetry/setup.py index b53f370b..9844da4f 100644 --- a/azure-monitor-opentelemetry/setup.py +++ b/azure-monitor-opentelemetry/setup.py @@ -86,15 +86,16 @@ install_requires=[ "azure-monitor-opentelemetry-exporter>=1.0.0b13", "opentelemetry-instrumentation~=0.38b0", - "opentelemetry-instrumentation-django~=0.38b0", - "opentelemetry-instrumentation-fastapi~=0.38b0", - "opentelemetry-instrumentation-flask~=0.38b0", - "opentelemetry-instrumentation-psycopg2~=0.38b0", - "opentelemetry-instrumentation-requests~=0.38b0", - "opentelemetry-instrumentation-urllib~=0.38b0", - "opentelemetry-instrumentation-urllib3~=0.38b0", + # "opentelemetry-instrumentation-django~=0.38b0", + # "opentelemetry-instrumentation-fastapi~=0.38b0", + # "opentelemetry-instrumentation-flask~=0.38b0", + # "opentelemetry-instrumentation-psycopg2~=0.38b0", + # "opentelemetry-instrumentation-requests~=0.38b0", + # "opentelemetry-instrumentation-urllib~=0.38b0", + # "opentelemetry-instrumentation-urllib3~=0.38b0", "opentelemetry-api==1.17.0", "opentelemetry-sdk==1.17.0", + "wrapt >= 1.0.0, < 2.0.0", ], entry_points={ "opentelemetry_distro": [ @@ -103,5 +104,14 @@ "opentelemetry_configurator": [ "azure_monitor_opentelemetry_configurator = azure.monitor.opentelemetry.autoinstrumentation._configurator:AzureMonitorConfigurator" ], + "azure_monitor_opentelemetry_instrumentor": [ + "django = azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.django:DjangoInstrumentor", + "fastapi = azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.fastapi:FastAPIInstrumentor", + "flask = azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.flask:FlaskInstrumentor", + "psycopg2 = azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.psycopg2:Psycopg2Instrumentor", + "requests = azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.requests:RequestsInstrumentor", + "urllib = azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.urllib:URLLibInstrumentor", + "urllib3 = azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.urllib3:URLLib3Instrumentor", + ], }, ) diff --git a/azure-monitor-opentelemetry/tests/configuration/test_configure.py b/azure-monitor-opentelemetry/tests/configuration/test_configure.py index a313e634..b2057098 100644 --- a/azure-monitor-opentelemetry/tests/configuration/test_configure.py +++ b/azure-monitor-opentelemetry/tests/configuration/test_configure.py @@ -16,7 +16,7 @@ from unittest.mock import Mock, patch from azure.monitor.opentelemetry._configure import ( - _SUPPORTED_INSTRUMENTED_LIBRARIES, + _SUPPORTED_INSTRUMENTED_LIBRARIES_TO_INSTRUMENTS_MAP, _setup_instrumentations, _setup_logging, _setup_metrics, @@ -321,9 +321,7 @@ def test_setup_metrics( metric_exporter_mock.assert_called_once_with(**configurations) reader_mock.assert_called_once_with(metric_exp_init_mock) - @patch( - "azure.monitor.opentelemetry._configure.get_dist_dependency_conflicts" - ) + @patch("azure.monitor.opentelemetry._configure.get_dependency_conflicts") @patch("azure.monitor.opentelemetry._configure.iter_entry_points") def test_setup_instrumentations_lib_not_supported( self, @@ -337,19 +335,21 @@ def test_setup_instrumentations_lib_not_supported( instr_class_mock = Mock() instr_class_mock.return_value = instrumentor_mock ep_mock.name = "test_instr" - ep2_mock.name = _SUPPORTED_INSTRUMENTED_LIBRARIES[1] + ep2_mock.name = list( + _SUPPORTED_INSTRUMENTED_LIBRARIES_TO_INSTRUMENTS_MAP.keys() + )[0] ep2_mock.load.return_value = instr_class_mock dep_mock.return_value = None _setup_instrumentations() - dep_mock.assert_called_with(ep2_mock.dist) + dep_mock.assert_called_with( + _SUPPORTED_INSTRUMENTED_LIBRARIES_TO_INSTRUMENTS_MAP[ep2_mock.name] + ) ep_mock.load.assert_not_called() ep2_mock.load.assert_called_once() instrumentor_mock.instrument.assert_called_once() @patch("azure.monitor.opentelemetry._configure._logger") - @patch( - "azure.monitor.opentelemetry._configure.get_dist_dependency_conflicts" - ) + @patch("azure.monitor.opentelemetry._configure.get_dependency_conflicts") @patch("azure.monitor.opentelemetry._configure.iter_entry_points") def test_setup_instrumentations_conflict( self, @@ -362,19 +362,21 @@ def test_setup_instrumentations_conflict( instrumentor_mock = Mock() instr_class_mock = Mock() instr_class_mock.return_value = instrumentor_mock - ep_mock.name = _SUPPORTED_INSTRUMENTED_LIBRARIES[0] + ep_mock.name = list( + _SUPPORTED_INSTRUMENTED_LIBRARIES_TO_INSTRUMENTS_MAP.keys() + )[0] ep_mock.load.return_value = instr_class_mock dep_mock.return_value = True _setup_instrumentations() - dep_mock.assert_called_with(ep_mock.dist) + dep_mock.assert_called_with( + _SUPPORTED_INSTRUMENTED_LIBRARIES_TO_INSTRUMENTS_MAP[ep_mock.name] + ) ep_mock.load.assert_not_called() instrumentor_mock.instrument.assert_not_called() logger_mock.debug.assert_called_once() @patch("azure.monitor.opentelemetry._configure._logger") - @patch( - "azure.monitor.opentelemetry._configure.get_dist_dependency_conflicts" - ) + @patch("azure.monitor.opentelemetry._configure.get_dependency_conflicts") @patch("azure.monitor.opentelemetry._configure.iter_entry_points") def test_setup_instrumentations_exception( self, @@ -387,11 +389,15 @@ def test_setup_instrumentations_exception( instrumentor_mock = Mock() instr_class_mock = Mock() instr_class_mock.return_value = instrumentor_mock - ep_mock.name = _SUPPORTED_INSTRUMENTED_LIBRARIES[0] + ep_mock.name = list( + _SUPPORTED_INSTRUMENTED_LIBRARIES_TO_INSTRUMENTS_MAP.keys() + )[0] ep_mock.load.side_effect = Exception() dep_mock.return_value = None _setup_instrumentations() - dep_mock.assert_called_with(ep_mock.dist) + dep_mock.assert_called_with( + _SUPPORTED_INSTRUMENTED_LIBRARIES_TO_INSTRUMENTS_MAP[ep_mock.name] + ) ep_mock.load.assert_called_once() instrumentor_mock.instrument.assert_not_called() logger_mock.warning.assert_called_once() diff --git a/azure-monitor-opentelemetry/tests/instrumentation/test_django.py b/azure-monitor-opentelemetry/tests/instrumentation/test_django.py index 42992967..8033853d 100644 --- a/azure-monitor-opentelemetry/tests/instrumentation/test_django.py +++ b/azure-monitor-opentelemetry/tests/instrumentation/test_django.py @@ -6,7 +6,9 @@ import unittest -from opentelemetry.instrumentation.django import DjangoInstrumentor +from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.django import ( + DjangoInstrumentor, +) class TestDjangoInstrumentation(unittest.TestCase): diff --git a/azure-monitor-opentelemetry/tests/instrumentation/test_fastapi.py b/azure-monitor-opentelemetry/tests/instrumentation/test_fastapi.py index 6611d7eb..e2b015b5 100644 --- a/azure-monitor-opentelemetry/tests/instrumentation/test_fastapi.py +++ b/azure-monitor-opentelemetry/tests/instrumentation/test_fastapi.py @@ -6,7 +6,9 @@ import unittest -from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor +from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.fastapi import ( + FastAPIInstrumentor, +) class TestFastApiInstrumentation(unittest.TestCase): diff --git a/azure-monitor-opentelemetry/tests/instrumentation/test_flask.py b/azure-monitor-opentelemetry/tests/instrumentation/test_flask.py index 4663c98e..c2e04220 100644 --- a/azure-monitor-opentelemetry/tests/instrumentation/test_flask.py +++ b/azure-monitor-opentelemetry/tests/instrumentation/test_flask.py @@ -6,7 +6,9 @@ import unittest -from opentelemetry.instrumentation.flask import FlaskInstrumentor +from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.flask import ( + FlaskInstrumentor, +) class TestFlaskInstrumentation(unittest.TestCase): diff --git a/azure-monitor-opentelemetry/tests/instrumentation/test_psycopg2.py b/azure-monitor-opentelemetry/tests/instrumentation/test_psycopg2.py index 63bcd7dc..0d74879a 100644 --- a/azure-monitor-opentelemetry/tests/instrumentation/test_psycopg2.py +++ b/azure-monitor-opentelemetry/tests/instrumentation/test_psycopg2.py @@ -6,7 +6,9 @@ import unittest -from opentelemetry.instrumentation.psycopg2 import Psycopg2Instrumentor +from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.psycopg2 import ( + Psycopg2Instrumentor, +) class TestPsycopg2Instrumentation(unittest.TestCase): diff --git a/azure-monitor-opentelemetry/tests/instrumentation/test_requests.py b/azure-monitor-opentelemetry/tests/instrumentation/test_requests.py index 3c9697d8..7aaab597 100644 --- a/azure-monitor-opentelemetry/tests/instrumentation/test_requests.py +++ b/azure-monitor-opentelemetry/tests/instrumentation/test_requests.py @@ -6,7 +6,9 @@ import unittest -from opentelemetry.instrumentation.requests import RequestsInstrumentor +from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.requests import ( + RequestsInstrumentor, +) class TestRequestsInstrumentation(unittest.TestCase): diff --git a/azure-monitor-opentelemetry/tests/instrumentation/test_urllib.py b/azure-monitor-opentelemetry/tests/instrumentation/test_urllib.py index bcd40384..56c79ccf 100644 --- a/azure-monitor-opentelemetry/tests/instrumentation/test_urllib.py +++ b/azure-monitor-opentelemetry/tests/instrumentation/test_urllib.py @@ -6,7 +6,9 @@ import unittest -from opentelemetry.instrumentation.urllib import URLLibInstrumentor +from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.urllib import ( + URLLibInstrumentor, +) class TestUrllibInstrumentation(unittest.TestCase): diff --git a/azure-monitor-opentelemetry/tests/instrumentation/test_urllib3.py b/azure-monitor-opentelemetry/tests/instrumentation/test_urllib3.py index 55511ab1..0143ff2e 100644 --- a/azure-monitor-opentelemetry/tests/instrumentation/test_urllib3.py +++ b/azure-monitor-opentelemetry/tests/instrumentation/test_urllib3.py @@ -6,7 +6,9 @@ import unittest -from opentelemetry.instrumentation.urllib3 import URLLib3Instrumentor +from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.urllib3 import ( + URLLib3Instrumentor, +) class TestUrllib3Instrumentation(unittest.TestCase): From 9348ee403826e0d1a7e12f1578454a89028420cd Mon Sep 17 00:00:00 2001 From: jerevoss Date: Mon, 22 May 2023 15:38:48 -0700 Subject: [PATCH 03/10] lint --- .flake8 | 1 + .isort.cfg | 2 +- .../azure/monitor/opentelemetry/_configure.py | 4 +++- pyproject.toml | 3 ++- tox.ini | 6 +++--- 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.flake8 b/.flake8 index 9ec8d18b..c725e893 100644 --- a/.flake8 +++ b/.flake8 @@ -25,3 +25,4 @@ exclude = target __pycache__ */build/lib/* + azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/* diff --git a/.isort.cfg b/.isort.cfg index 31658510..1cebe04e 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -14,5 +14,5 @@ profile=black ; docs: https://github.com/timothycrosley/isort#multi-line-output-modes multi_line_output=3 skip=target -skip_glob=**/gen/*,.venv*/*,venv*/*,**/proto/*,.tox/* +skip_glob=**/gen/*,.venv*/*,venv*/*,**/proto/*,.tox/*, azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/* known_third_party=opentelemetry,psutil,pytest,redis,redis_opentracing diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py index e3ca58be..e30261f4 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py @@ -123,7 +123,9 @@ def _setup_metrics(configurations: Dict[str, ConfigurationValue]): def _setup_instrumentations(): # use pkg_resources for now until https://github.com/open-telemetry/opentelemetry-python/pull/3168 is merged - for entry_point in iter_entry_points("azure_monitor_opentelemetry_instrumentor"): + for entry_point in iter_entry_points( + "azure_monitor_opentelemetry_instrumentor" + ): lib_name = entry_point.name if ( lib_name diff --git a/pyproject.toml b/pyproject.toml index 7034b902..7e62d134 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,8 @@ exclude = ''' .vscode| venv| .*/build/lib/.*| - scripts + scripts| + azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor| )/ ) ''' diff --git a/tox.ini b/tox.ini index 1cd93874..b68dfd20 100644 --- a/tox.ini +++ b/tox.ini @@ -29,8 +29,8 @@ commands_pre = python -m pip install -e {toxinidir}/azure-monitor-opentelemetry commands = - black --config pyproject.toml {toxinidir} --diff --check --exclude={toxinidir}/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor - isort --settings-path .isort.cfg {toxinidir} --diff --check-only --exclude={toxinidir}/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor - flake8 --config .flake8 {toxinidir} --exclude={toxinidir}/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor + black --config pyproject.toml {toxinidir} --diff --check + isort --settings-path .isort.cfg {toxinidir} --diff --check-only + flake8 --config .flake8 {toxinidir} ; TODO ; pylint {toxinidir} From 3d0dc624034fe4e354920ca106b4dcf5e6c89f56 Mon Sep 17 00:00:00 2001 From: jerevoss Date: Wed, 31 May 2023 14:36:57 -0700 Subject: [PATCH 04/10] Switched to _vendor, vendored instrumentation, removed comments+docstrings --- .../azure/monitor/opentelemetry/_configure.py | 17 +- .../{vendor => _vendor}/__init__.py | 0 .../opentelemetry/__init__.py | 0 .../opentelemetry/instrumentation/__init__.py | 0 .../instrumentation/asgi/__init__.py | 183 +-------- .../instrumentation/asgi/package.py | 0 .../instrumentation/asgi/version.py | 0 .../auto_instrumentation/__init__.py | 117 ++++++ .../auto_instrumentation/sitecustomize.py | 134 ++++++ .../instrumentation/bootstrap.py | 163 ++++++++ .../instrumentation/bootstrap_gen.py | 175 ++++++++ .../instrumentation/dbapi/__init__.py | 32 +- .../instrumentation/dbapi/package.py | 0 .../instrumentation/dbapi/version.py | 0 .../instrumentation/dependencies.py | 62 +++ .../opentelemetry/instrumentation/distro.py | 73 ++++ .../instrumentation/django/__init__.py | 167 ++++++++ .../django/environment_variables.py | 0 .../django/middleware/__init__.py | 0 .../django/middleware/otel_middleware.py | 26 +- .../middleware/sqlcommenter_middleware.py | 8 +- .../instrumentation/django/package.py | 0 .../instrumentation/django/version.py | 0 .../instrumentation/environment_variables.py | 18 + .../instrumentation/fastapi/__init__.py | 194 +++++++++ .../instrumentation/fastapi/package.py | 0 .../instrumentation/fastapi/version.py | 0 .../instrumentation/flask/__init__.py | 240 +---------- .../instrumentation/flask/package.py | 0 .../instrumentation/flask/version.py | 0 .../instrumentation/instrumentor.py | 131 ++++++ .../instrumentation/propagators.py | 124 ++++++ .../instrumentation/psycopg2/__init__.py | 98 +---- .../instrumentation/psycopg2/package.py | 0 .../instrumentation/psycopg2/version.py | 0 .../opentelemetry/instrumentation/py.typed | 0 .../instrumentation/requests/__init__.py | 49 +-- .../instrumentation/requests/package.py | 0 .../instrumentation/requests/version.py | 0 .../instrumentation/sqlcommenter_utils.py | 68 +++ .../instrumentation/urllib/__init__.py | 59 +-- .../instrumentation/urllib/package.py | 0 .../instrumentation/urllib/version.py | 0 .../instrumentation/urllib3/__init__.py | 63 +-- .../instrumentation/urllib3/package.py | 0 .../instrumentation/urllib3/version.py | 0 .../opentelemetry/instrumentation/utils.py | 154 +++++++ .../opentelemetry/instrumentation}/version.py | 0 .../instrumentation/wsgi/__init__.py | 196 +-------- .../instrumentation/wsgi/package.py | 0 .../instrumentation/wsgi}/version.py | 0 .../opentelemetry/util/__init__.py | 0 .../opentelemetry}/util/http/__init__.py | 0 .../opentelemetry/util/http/httplib.py | 8 +- .../opentelemetry}/util/http/version.py | 2 +- .../instrumentation/django/__init__.py | 387 ------------------ .../instrumentation/fastapi/__init__.py | 350 ---------------- .../instrumentation/util/http/httplib.py | 179 -------- .../opentelemetry/util/http/__init__.py | 213 ---------- azure-monitor-opentelemetry/setup.py | 22 +- .../tests/configuration/test_configure.py | 14 +- .../tests/instrumentation/test_django.py | 2 +- .../tests/instrumentation/test_fastapi.py | 2 +- .../tests/instrumentation/test_flask.py | 2 +- .../tests/instrumentation/test_psycopg2.py | 2 +- .../tests/instrumentation/test_requests.py | 2 +- .../tests/instrumentation/test_urllib.py | 2 +- .../tests/instrumentation/test_urllib3.py | 2 +- 68 files changed, 1688 insertions(+), 2052 deletions(-) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/__init__.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/__init__.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/__init__.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/asgi/__init__.py (71%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/asgi/package.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/asgi/version.py (100%) create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/auto_instrumentation/__init__.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/auto_instrumentation/sitecustomize.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/bootstrap.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/bootstrap_gen.py rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/dbapi/__init__.py (95%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/dbapi/package.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/dbapi/version.py (100%) create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/dependencies.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/distro.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/__init__.py rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/django/environment_variables.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/django/middleware/__init__.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/django/middleware/otel_middleware.py (92%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/django/middleware/sqlcommenter_middleware.py (94%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/django/package.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/django/version.py (100%) create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/environment_variables.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/fastapi/__init__.py rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/fastapi/package.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/fastapi/version.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/flask/__init__.py (63%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/flask/package.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/flask/version.py (100%) create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/instrumentor.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/propagators.py rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/psycopg2/__init__.py (69%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/psycopg2/package.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/psycopg2/version.py (100%) create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/py.typed rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/requests/__init__.py (86%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/requests/package.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/requests/version.py (100%) create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/sqlcommenter_utils.py rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/urllib/__init__.py (83%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/urllib/package.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/urllib/version.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/urllib3/__init__.py (83%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/urllib3/package.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/urllib3/version.py (100%) create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/utils.py rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor/opentelemetry/instrumentation/wsgi => _vendor/opentelemetry/instrumentation}/version.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/wsgi/__init__.py (65%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/wsgi/package.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor/opentelemetry/util/http => _vendor/opentelemetry/instrumentation/wsgi}/version.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/util/__init__.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor/opentelemetry/instrumentation => _vendor/opentelemetry}/util/http/__init__.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/util/http/httplib.py (95%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor/opentelemetry/instrumentation => _vendor/opentelemetry}/util/http/version.py (95%) delete mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/__init__.py delete mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/fastapi/__init__.py delete mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/util/http/httplib.py delete mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/util/http/__init__.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py index e30261f4..979a8254 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py @@ -22,8 +22,12 @@ ) from azure.monitor.opentelemetry.util.configurations import _get_configurations from opentelemetry._logs import get_logger_provider, set_logger_provider -from opentelemetry.instrumentation.dependencies import get_dependency_conflicts -from opentelemetry.instrumentation.instrumentor import BaseInstrumentor +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.dependencies import ( + get_dependency_conflicts, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.instrumentor import ( + BaseInstrumentor, +) from opentelemetry.metrics import set_meter_provider from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler from opentelemetry.sdk._logs.export import BatchLogRecordProcessor @@ -37,7 +41,7 @@ _logger = getLogger(__name__) -_SUPPORTED_INSTRUMENTED_LIBRARIES_TO_INSTRUMENTS_MAP = { +_SUPPORTED_INSTRUMENTED_LIBRARIES_DEPENDENCIES_MAP = { "django": ("django >= 1.10",), "fastapi": ("fastapi ~= 0.58",), "flask": ("flask >= 1.0, < 3.0",), @@ -127,14 +131,11 @@ def _setup_instrumentations(): "azure_monitor_opentelemetry_instrumentor" ): lib_name = entry_point.name - if ( - lib_name - not in _SUPPORTED_INSTRUMENTED_LIBRARIES_TO_INSTRUMENTS_MAP - ): + if lib_name not in _SUPPORTED_INSTRUMENTED_LIBRARIES_DEPENDENCIES_MAP: continue try: # Check if dependent libraries/version are installed - instruments = _SUPPORTED_INSTRUMENTED_LIBRARIES_TO_INSTRUMENTS_MAP[ + instruments = _SUPPORTED_INSTRUMENTED_LIBRARIES_DEPENDENCIES_MAP[ lib_name ] conflict = get_dependency_conflicts(instruments) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/__init__.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/__init__.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/__init__.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/__init__.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/__init__.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/__init__.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/asgi/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/asgi/__init__.py similarity index 71% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/asgi/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/asgi/__init__.py index da78196d..02395303 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/asgi/__init__.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/asgi/__init__.py @@ -13,181 +13,6 @@ # limitations under the License. # pylint: disable=too-many-locals -""" -The opentelemetry-instrumentation-asgi package provides an ASGI middleware that can be used -on any ASGI framework (such as Django-channels / Quart) to track request timing through OpenTelemetry. - -Usage (Quart) -------------- - -.. code-block:: python - - from quart import Quart - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware - - app = Quart(__name__) - app.asgi_app = OpenTelemetryMiddleware(app.asgi_app) - - @app.route("/") - async def hello(): - return "Hello!" - - if __name__ == "__main__": - app.run(debug=True) - - -Usage (Django 3.0) ------------------- - -Modify the application's ``asgi.py`` file as shown below. - -.. code-block:: python - - import os - from django.core.asgi import get_asgi_application - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware - - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'asgi_example.settings') - - application = get_asgi_application() - application = OpenTelemetryMiddleware(application) - - -Usage (Raw ASGI) ----------------- - -.. code-block:: python - - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware - - app = ... # An ASGI application. - app = OpenTelemetryMiddleware(app) - - -Configuration -------------- - -Request/Response hooks -********************** - -This instrumentation supports request and response hooks. These are functions that get called -right after a span is created for a request and right before the span is finished for the response. - -- The server request hook is passed a server span and ASGI scope object for every incoming request. -- The client request hook is called with the internal span and an ASGI scope when the method ``receive`` is called. -- The client response hook is called with the internal span and an ASGI event when the method ``send`` is called. - -For example, - -.. code-block:: python - - def server_request_hook(span: Span, scope: dict): - if span and span.is_recording(): - span.set_attribute("custom_user_attribute_from_request_hook", "some-value") - - def client_request_hook(span: Span, scope: dict): - if span and span.is_recording(): - span.set_attribute("custom_user_attribute_from_client_request_hook", "some-value") - - def client_response_hook(span: Span, message: dict): - if span and span.is_recording(): - span.set_attribute("custom_user_attribute_from_response_hook", "some-value") - - OpenTelemetryMiddleware().(application, server_request_hook=server_request_hook, client_request_hook=client_request_hook, client_response_hook=client_response_hook) - -Capture HTTP request and response headers -***************************************** -You can configure the agent to capture specified HTTP headers as span attributes, according to the -`semantic convention `_. - -Request headers -*************** -To capture HTTP request headers as span attributes, set the environment variable -``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST`` to a comma delimited list of HTTP header names. - -For example, -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST="content-type,custom_request_header" - -will extract ``content-type`` and ``custom_request_header`` from the request headers and add them as span attributes. - -Request header names in ASGI are case-insensitive. So, giving the header name as ``CUStom-Header`` in the environment -variable will capture the header named ``custom-header``. - -Regular expressions may also be used to match multiple headers that correspond to the given pattern. For example: -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST="Accept.*,X-.*" - -Would match all request headers that start with ``Accept`` and ``X-``. - -To capture all request headers, set ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST`` to ``".*"``. -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST=".*" - -The name of the added span attribute will follow the format ``http.request.header.`` where ```` -is the normalized HTTP header name (lowercase, with ``-`` replaced by ``_``). The value of the attribute will be a -single item list containing all the header values. - -For example: -``http.request.header.custom_request_header = [","]`` - -Response headers -**************** -To capture HTTP response headers as span attributes, set the environment variable -``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE`` to a comma delimited list of HTTP header names. - -For example, -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE="content-type,custom_response_header" - -will extract ``content-type`` and ``custom_response_header`` from the response headers and add them as span attributes. - -Response header names in ASGI are case-insensitive. So, giving the header name as ``CUStom-Header`` in the environment -variable will capture the header named ``custom-header``. - -Regular expressions may also be used to match multiple headers that correspond to the given pattern. For example: -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE="Content.*,X-.*" - -Would match all response headers that start with ``Content`` and ``X-``. - -To capture all response headers, set ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE`` to ``".*"``. -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE=".*" - -The name of the added span attribute will follow the format ``http.response.header.`` where ```` -is the normalized HTTP header name (lowercase, with ``-`` replaced by ``_``). The value of the attribute will be a -single item list containing all the header values. - -For example: -``http.response.header.custom_response_header = [","]`` - -Sanitizing headers -****************** -In order to prevent storing sensitive data such as personally identifiable information (PII), session keys, passwords, -etc, set the environment variable ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS`` -to a comma delimited list of HTTP header names to be sanitized. Regexes may be used, and all header names will be -matched in a case-insensitive manner. - -For example, -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS=".*session.*,set-cookie" - -will replace the value of headers such as ``session-id`` and ``set-cookie`` with ``[REDACTED]`` in the span. - -Note: - The environment variable names used to capture HTTP headers are still experimental, and thus are subject to change. - -API ---- -""" import typing import urllib @@ -198,13 +23,13 @@ def client_response_hook(span: Span, message: dict): from asgiref.compatibility import guarantee_single_callable from opentelemetry import context, trace -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.asgi.version import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.asgi.version import ( __version__, ) # noqa -from opentelemetry.instrumentation.propagators import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.propagators import ( get_global_response_propagator, ) -from opentelemetry.instrumentation.utils import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.utils import ( _start_internal_or_server_span, http_status_to_status_code, ) @@ -214,7 +39,7 @@ def client_response_hook(span: Span, message: dict): from opentelemetry.semconv.trace import SpanAttributes from opentelemetry.trace import Span, set_span_in_context from opentelemetry.trace.status import Status, StatusCode -from azure.monitor.opentelemetry.vendor.opentelemetry.util.http import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.util.http import ( OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS, OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST, OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE, diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/asgi/package.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/asgi/package.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/asgi/package.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/asgi/package.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/asgi/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/asgi/version.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/asgi/version.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/asgi/version.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/auto_instrumentation/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/auto_instrumentation/__init__.py new file mode 100644 index 00000000..025d6588 --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/auto_instrumentation/__init__.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 + +# 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 argparse import REMAINDER, ArgumentParser +from logging import getLogger +from os import environ, execl, getcwd +from os.path import abspath, dirname, pathsep +from re import sub +from shutil import which + +from pkg_resources import iter_entry_points + +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.version import ( + __version__, +) + +_logger = getLogger(__name__) + + +def run() -> None: + parser = ArgumentParser( + description=""" + opentelemetry-instrument automatically instruments a Python + program and its dependencies and then runs the program. + """, + epilog=""" + Optional arguments (except for --help and --version) for opentelemetry-instrument + directly correspond with OpenTelemetry environment variables. The + corresponding optional argument is formed by removing the OTEL_ or + OTEL_PYTHON_ prefix from the environment variable and lower casing the + rest. For example, the optional argument --attribute_value_length_limit + corresponds with the environment variable + OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT. + + These optional arguments will override the current value of the + corresponding environment variable during the execution of the command. + """, + ) + + argument_otel_environment_variable = {} + + for entry_point in iter_entry_points( + "opentelemetry_environment_variables" + ): + environment_variable_module = entry_point.load() + + for attribute in dir(environment_variable_module): + if attribute.startswith("OTEL_"): + argument = sub(r"OTEL_(PYTHON_)?", "", attribute).lower() + + parser.add_argument( + f"--{argument}", + required=False, + ) + argument_otel_environment_variable[argument] = attribute + + parser.add_argument( + "--version", + help="print version information", + action="version", + version="%(prog)s " + __version__, + ) + parser.add_argument("command", help="Your Python application.") + parser.add_argument( + "command_args", + help="Arguments for your application.", + nargs=REMAINDER, + ) + + args = parser.parse_args() + + for argument, otel_environment_variable in ( + argument_otel_environment_variable + ).items(): + value = getattr(args, argument) + if value is not None: + environ[otel_environment_variable] = value + + python_path = environ.get("PYTHONPATH") + + if not python_path: + python_path = [] + + else: + python_path = python_path.split(pathsep) + + cwd_path = getcwd() + + # This is being added to support applications that are being run from their + # own executable, like Django. + # FIXME investigate if there is another way to achieve this + if cwd_path not in python_path: + python_path.insert(0, cwd_path) + + filedir_path = dirname(abspath(__file__)) + + python_path = [path for path in python_path if path != filedir_path] + + python_path.insert(0, filedir_path) + + environ["PYTHONPATH"] = pathsep.join(python_path) + + executable = which(args.command) + execl(executable, executable, *args.command_args) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/auto_instrumentation/sitecustomize.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/auto_instrumentation/sitecustomize.py new file mode 100644 index 00000000..3751a40c --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/auto_instrumentation/sitecustomize.py @@ -0,0 +1,134 @@ +# 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 os.path import abspath, dirname, pathsep + +from pkg_resources import iter_entry_points + +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.dependencies import ( + get_dist_dependency_conflicts, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.distro import ( + BaseDistro, + DefaultDistro, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.environment_variables import ( + OTEL_PYTHON_DISABLED_INSTRUMENTATIONS, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.utils import ( + _python_path_without_directory, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.version import ( + __version__, +) + +logger = getLogger(__name__) + + +def _load_distros() -> BaseDistro: + for entry_point in iter_entry_points("opentelemetry_distro"): + try: + distro = entry_point.load()() + if not isinstance(distro, BaseDistro): + logger.debug( + "%s is not an OpenTelemetry Distro. Skipping", + entry_point.name, + ) + continue + logger.debug( + "Distribution %s will be configured", entry_point.name + ) + return distro + except Exception as exc: # pylint: disable=broad-except + logger.exception( + "Distribution %s configuration failed", entry_point.name + ) + raise exc + return DefaultDistro() + + +def _load_instrumentors(distro): + package_to_exclude = environ.get(OTEL_PYTHON_DISABLED_INSTRUMENTATIONS, []) + if isinstance(package_to_exclude, str): + package_to_exclude = package_to_exclude.split(",") + # to handle users entering "requests , flask" or "requests, flask" with spaces + package_to_exclude = [x.strip() for x in package_to_exclude] + + for entry_point in iter_entry_points("opentelemetry_pre_instrument"): + entry_point.load()() + + for entry_point in iter_entry_points("opentelemetry_instrumentor"): + if entry_point.name in package_to_exclude: + logger.debug( + "Instrumentation skipped for library %s", entry_point.name + ) + continue + + try: + conflict = get_dist_dependency_conflicts(entry_point.dist) + if conflict: + logger.debug( + "Skipping instrumentation %s: %s", + entry_point.name, + conflict, + ) + continue + + # tell instrumentation to not run dep checks again as we already did it above + distro.load_instrumentor(entry_point, skip_dep_check=True) + logger.debug("Instrumented %s", entry_point.name) + except Exception as exc: # pylint: disable=broad-except + logger.exception("Instrumenting of %s failed", entry_point.name) + raise exc + + for entry_point in iter_entry_points("opentelemetry_post_instrument"): + entry_point.load()() + + +def _load_configurators(): + configured = None + for entry_point in iter_entry_points("opentelemetry_configurator"): + if configured is not None: + logger.warning( + "Configuration of %s not loaded, %s already loaded", + entry_point.name, + configured, + ) + continue + try: + entry_point.load()().configure(auto_instrumentation_version=__version__) # type: ignore + configured = entry_point.name + except Exception as exc: # pylint: disable=broad-except + logger.exception("Configuration of %s failed", entry_point.name) + raise exc + + +def initialize(): + # prevents auto-instrumentation of subprocesses if code execs another python process + environ["PYTHONPATH"] = _python_path_without_directory( + environ["PYTHONPATH"], dirname(abspath(__file__)), pathsep + ) + + try: + distro = _load_distros() + distro.configure() + _load_configurators() + _load_instrumentors(distro) + except Exception: # pylint: disable=broad-except + logger.exception("Failed to auto initialize opentelemetry") + + +initialize() diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/bootstrap.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/bootstrap.py new file mode 100644 index 00000000..afb6ed34 --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/bootstrap.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python3 + +# 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 argparse +import logging +import subprocess +import sys + +import pkg_resources + +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.bootstrap_gen import ( + default_instrumentations, + libraries, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.version import ( + __version__, +) + +logger = logging.getLogger(__name__) + + +def _syscall(func): + def wrapper(package=None): + try: + if package: + return func(package) + return func() + except subprocess.SubprocessError as exp: + cmd = getattr(exp, "cmd", None) + if cmd: + msg = f'Error calling system command "{" ".join(cmd)}"' + if package: + msg = f'{msg} for package "{package}"' + raise RuntimeError(msg) + + return wrapper + + +@_syscall +def _sys_pip_install(package): + # explicit upgrade strategy to override potential pip config + subprocess.check_call( + [ + sys.executable, + "-m", + "pip", + "install", + "-U", + "--upgrade-strategy", + "only-if-needed", + package, + ] + ) + + +def _pip_check(): + """Ensures none of the instrumentations have dependency conflicts. + Clean check reported as: + 'No broken requirements found.' + Dependency conflicts are reported as: + 'opentelemetry-instrumentation-flask 1.0.1 has requirement opentelemetry-sdk<2.0,>=1.0, but you have opentelemetry-sdk 0.5.' + To not be too restrictive, we'll only check for relevant packages. + """ + with subprocess.Popen( + [sys.executable, "-m", "pip", "check"], stdout=subprocess.PIPE + ) as check_pipe: + pip_check = check_pipe.communicate()[0].decode() + pip_check_lower = pip_check.lower() + for package_tup in libraries.values(): + for package in package_tup: + if package.lower() in pip_check_lower: + raise RuntimeError(f"Dependency conflict found: {pip_check}") + + +def _is_installed(req): + if req in sys.modules: + return True + + try: + pkg_resources.get_distribution(req) + except pkg_resources.DistributionNotFound: + return False + except pkg_resources.VersionConflict as exc: + logger.warning( + "instrumentation for package %s is available but version %s is installed. Skipping.", + exc.req, + exc.dist.as_requirement(), # pylint: disable=no-member + ) + return False + return True + + +def _find_installed_libraries(): + libs = default_instrumentations[:] + libs.extend( + [ + v["instrumentation"] + for _, v in libraries.items() + if _is_installed(v["library"]) + ] + ) + return libs + + +def _run_requirements(): + logger.setLevel(logging.ERROR) + print("\n".join(_find_installed_libraries()), end="") + + +def _run_install(): + for lib in _find_installed_libraries(): + _sys_pip_install(lib) + _pip_check() + + +def run() -> None: + action_install = "install" + action_requirements = "requirements" + + parser = argparse.ArgumentParser( + description=""" + opentelemetry-bootstrap detects installed libraries and automatically + installs the relevant instrumentation packages for them. + """ + ) + parser.add_argument( + "--version", + help="print version information", + action="version", + version="%(prog)s " + __version__, + ) + parser.add_argument( + "-a", + "--action", + choices=[action_install, action_requirements], + default=action_requirements, + help=""" + install - uses pip to install the new requirements using to the + currently active site-package. + requirements - prints out the new requirements to stdout. Action can + be piped and appended to a requirements.txt file. + """, + ) + args = parser.parse_args() + + cmd = { + action_install: _run_install, + action_requirements: _run_requirements, + }[args.action] + cmd() diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/bootstrap_gen.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/bootstrap_gen.py new file mode 100644 index 00000000..a2ee1dc1 --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/bootstrap_gen.py @@ -0,0 +1,175 @@ +# 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. + +# DO NOT EDIT. THIS FILE WAS AUTOGENERATED FROM INSTRUMENTATION PACKAGES. +# RUN `python scripts/generate_instrumentation_bootstrap.py` TO REGENERATE. + +libraries = { + "aio_pika": { + "library": "aio_pika >= 7.2.0, < 10.0.0", + "instrumentation": "opentelemetry-instrumentation-aio-pika==0.38b0", + }, + "aiohttp": { + "library": "aiohttp ~= 3.0", + "instrumentation": "opentelemetry-instrumentation-aiohttp-client==0.38b0", + }, + "aiopg": { + "library": "aiopg >= 0.13.0, < 2.0.0", + "instrumentation": "opentelemetry-instrumentation-aiopg==0.38b0", + }, + "asgiref": { + "library": "asgiref ~= 3.0", + "instrumentation": "opentelemetry-instrumentation-asgi==0.38b0", + }, + "asyncpg": { + "library": "asyncpg >= 0.12.0", + "instrumentation": "opentelemetry-instrumentation-asyncpg==0.38b0", + }, + "boto": { + "library": "boto~=2.0", + "instrumentation": "opentelemetry-instrumentation-boto==0.38b0", + }, + "boto3": { + "library": "boto3 ~= 1.0", + "instrumentation": "opentelemetry-instrumentation-boto3sqs==0.38b0", + }, + "botocore": { + "library": "botocore ~= 1.0", + "instrumentation": "opentelemetry-instrumentation-botocore==0.38b0", + }, + "celery": { + "library": "celery >= 4.0, < 6.0", + "instrumentation": "opentelemetry-instrumentation-celery==0.38b0", + }, + "confluent-kafka": { + "library": "confluent-kafka >= 1.8.2, < 2.0.0", + "instrumentation": "opentelemetry-instrumentation-confluent-kafka==0.38b0", + }, + "django": { + "library": "django >= 1.10", + "instrumentation": "opentelemetry-instrumentation-django==0.38b0", + }, + "elasticsearch": { + "library": "elasticsearch >= 2.0", + "instrumentation": "opentelemetry-instrumentation-elasticsearch==0.38b0", + }, + "falcon": { + "library": "falcon >= 1.4.1, < 4.0.0", + "instrumentation": "opentelemetry-instrumentation-falcon==0.38b0", + }, + "fastapi": { + "library": "fastapi ~= 0.58", + "instrumentation": "opentelemetry-instrumentation-fastapi==0.38b0", + }, + "flask": { + "library": "flask >= 1.0, < 3.0", + "instrumentation": "opentelemetry-instrumentation-flask==0.38b0", + }, + "grpcio": { + "library": "grpcio ~= 1.27", + "instrumentation": "opentelemetry-instrumentation-grpc==0.38b0", + }, + "httpx": { + "library": "httpx >= 0.18.0, <= 0.23.0", + "instrumentation": "opentelemetry-instrumentation-httpx==0.38b0", + }, + "jinja2": { + "library": "jinja2 >= 2.7, < 4.0", + "instrumentation": "opentelemetry-instrumentation-jinja2==0.38b0", + }, + "kafka-python": { + "library": "kafka-python >= 2.0", + "instrumentation": "opentelemetry-instrumentation-kafka-python==0.38b0", + }, + "mysql-connector-python": { + "library": "mysql-connector-python ~= 8.0", + "instrumentation": "opentelemetry-instrumentation-mysql==0.38b0", + }, + "pika": { + "library": "pika >= 0.12.0", + "instrumentation": "opentelemetry-instrumentation-pika==0.38b0", + }, + "psycopg2": { + "library": "psycopg2 >= 2.7.3.1", + "instrumentation": "opentelemetry-instrumentation-psycopg2==0.38b0", + }, + "pymemcache": { + "library": "pymemcache >= 1.3.5, < 4", + "instrumentation": "opentelemetry-instrumentation-pymemcache==0.38b0", + }, + "pymongo": { + "library": "pymongo >= 3.1, < 5.0", + "instrumentation": "opentelemetry-instrumentation-pymongo==0.38b0", + }, + "PyMySQL": { + "library": "PyMySQL < 2", + "instrumentation": "opentelemetry-instrumentation-pymysql==0.38b0", + }, + "pyramid": { + "library": "pyramid >= 1.7", + "instrumentation": "opentelemetry-instrumentation-pyramid==0.38b0", + }, + "redis": { + "library": "redis >= 2.6", + "instrumentation": "opentelemetry-instrumentation-redis==0.38b0", + }, + "remoulade": { + "library": "remoulade >= 0.50", + "instrumentation": "opentelemetry-instrumentation-remoulade==0.38b0", + }, + "requests": { + "library": "requests ~= 2.0", + "instrumentation": "opentelemetry-instrumentation-requests==0.38b0", + }, + "scikit-learn": { + "library": "scikit-learn ~= 0.24.0", + "instrumentation": "opentelemetry-instrumentation-sklearn==0.38b0", + }, + "sqlalchemy": { + "library": "sqlalchemy", + "instrumentation": "opentelemetry-instrumentation-sqlalchemy==0.38b0", + }, + "starlette": { + "library": "starlette ~= 0.13.0", + "instrumentation": "opentelemetry-instrumentation-starlette==0.38b0", + }, + "psutil": { + "library": "psutil >= 5", + "instrumentation": "opentelemetry-instrumentation-system-metrics==0.38b0", + }, + "tornado": { + "library": "tornado >= 5.1.1", + "instrumentation": "opentelemetry-instrumentation-tornado==0.38b0", + }, + "tortoise-orm": { + "library": "tortoise-orm >= 0.17.0", + "instrumentation": "opentelemetry-instrumentation-tortoiseorm==0.38b0", + }, + "pydantic": { + "library": "pydantic >= 1.10.2", + "instrumentation": "opentelemetry-instrumentation-tortoiseorm==0.38b0", + }, + "urllib3": { + "library": "urllib3 >= 1.0.0, < 2.0.0", + "instrumentation": "opentelemetry-instrumentation-urllib3==0.38b0", + }, +} +default_instrumentations = [ + "opentelemetry-instrumentation-aws-lambda==0.38b0", + "opentelemetry-instrumentation-dbapi==0.38b0", + "opentelemetry-instrumentation-logging==0.38b0", + "opentelemetry-instrumentation-sqlite3==0.38b0", + "opentelemetry-instrumentation-urllib==0.38b0", + "opentelemetry-instrumentation-wsgi==0.38b0", +] diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/dbapi/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/dbapi/__init__.py similarity index 95% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/dbapi/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/dbapi/__init__.py index e6382aad..09b2d673 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/dbapi/__init__.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/dbapi/__init__.py @@ -12,30 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" -The trace integration with Database API supports libraries that follow the -Python Database API Specification v2.0. -``_ - -Usage ------ - -.. code-block:: python - - import mysql.connector - import pyodbc - - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.dbapi import trace_integration - - - # Ex: mysql.connector - trace_integration(mysql.connector, "connect", "mysql") - # Ex: pyodbc - trace_integration(pyodbc, "Connection", "odbc") - -API ---- -""" import functools import logging @@ -45,11 +21,13 @@ import wrapt from opentelemetry import trace as trace_api -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.dbapi.version import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.dbapi.version import ( __version__, ) -from opentelemetry.instrumentation.sqlcommenter_utils import _add_sql_comment -from opentelemetry.instrumentation.utils import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.sqlcommenter_utils import ( + _add_sql_comment, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.utils import ( _get_opentelemetry_values, unwrap, ) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/dbapi/package.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/dbapi/package.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/dbapi/package.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/dbapi/package.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/dbapi/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/dbapi/version.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/dbapi/version.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/dbapi/version.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/dependencies.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/dependencies.py new file mode 100644 index 00000000..2da0a3d1 --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/dependencies.py @@ -0,0 +1,62 @@ +from logging import getLogger +from typing import Collection, Optional + +from pkg_resources import ( + Distribution, + DistributionNotFound, + RequirementParseError, + VersionConflict, + get_distribution, +) + +logger = getLogger(__name__) + + +class DependencyConflict: + required: str = None + found: Optional[str] = None + + def __init__(self, required, found=None): + self.required = required + self.found = found + + def __str__(self): + return f'DependencyConflict: requested: "{self.required}" but found: "{self.found}"' + + +def get_dist_dependency_conflicts( + dist: Distribution, +) -> Optional[DependencyConflict]: + main_deps = dist.requires() + instrumentation_deps = [] + for dep in dist.requires(("instruments",)): + if dep not in main_deps: + # we set marker to none so string representation of the dependency looks like + # requests ~= 1.0 + # instead of + # requests ~= 1.0; extra = "instruments" + # which does not work with `get_distribution()` + dep.marker = None + instrumentation_deps.append(str(dep)) + + return get_dependency_conflicts(instrumentation_deps) + + +def get_dependency_conflicts( + deps: Collection[str], +) -> Optional[DependencyConflict]: + for dep in deps: + try: + get_distribution(dep) + except VersionConflict as exc: + return DependencyConflict(dep, exc.dist) + except DistributionNotFound: + return DependencyConflict(dep) + except RequirementParseError as exc: + logger.warning( + 'error parsing dependency, reporting as a conflict: "%s" - %s', + dep, + exc, + ) + return DependencyConflict(dep) + return None diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/distro.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/distro.py new file mode 100644 index 00000000..e7403bfb --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/distro.py @@ -0,0 +1,73 @@ +# 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. +# type: ignore + +""" +OpenTelemetry Base Distribution (Distro) +""" + +from abc import ABC, abstractmethod +from logging import getLogger + +from pkg_resources import EntryPoint + +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.instrumentor import ( + BaseInstrumentor, +) + +_LOG = getLogger(__name__) + + +class BaseDistro(ABC): + """An ABC for distro""" + + _instance = None + + def __new__(cls, *args, **kwargs): + + if cls._instance is None: + cls._instance = object.__new__(cls, *args, **kwargs) + + return cls._instance + + @abstractmethod + def _configure(self, **kwargs): + """Configure the distribution""" + + def configure(self, **kwargs): + """Configure the distribution""" + self._configure(**kwargs) + + def load_instrumentor( # pylint: disable=no-self-use + self, entry_point: EntryPoint, **kwargs + ): + """Takes a collection of instrumentation entry points + and activates them by instantiating and calling instrument() + on each one. + + Distros can override this method to customize the behavior by + inspecting each entry point and configuring them in special ways, + passing additional arguments, load a replacement/fork instead, + skip loading entirely, etc. + """ + instrumentor: BaseInstrumentor = entry_point.load() + instrumentor().instrument(**kwargs) + + +class DefaultDistro(BaseDistro): + def _configure(self, **kwargs): + pass + + +__all__ = ["BaseDistro", "DefaultDistro"] diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/__init__.py new file mode 100644 index 00000000..f47af874 --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/__init__.py @@ -0,0 +1,167 @@ +# 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 Collection + +from django import VERSION as django_version +from django.conf import settings +from django.core.exceptions import ImproperlyConfigured + +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.django.environment_variables import ( + OTEL_PYTHON_DJANGO_INSTRUMENT, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.django.middleware.otel_middleware import ( + _DjangoMiddleware, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.django.package import ( + _instruments, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.django.version import ( + __version__, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.instrumentor import ( + BaseInstrumentor, +) +from opentelemetry.metrics import get_meter +from opentelemetry.semconv.metrics import MetricInstruments +from opentelemetry.trace import get_tracer +from azure.monitor.opentelemetry._vendor.opentelemetry.util.http import ( + get_excluded_urls, + parse_excluded_urls, +) + +DJANGO_2_0 = django_version >= (2, 0) + +_excluded_urls_from_env = get_excluded_urls("DJANGO") +_logger = getLogger(__name__) + + +def _get_django_middleware_setting() -> str: + # In Django versions 1.x, setting MIDDLEWARE_CLASSES can be used as a legacy + # alternative to MIDDLEWARE. This is the case when `settings.MIDDLEWARE` has + # its default value (`None`). + if not DJANGO_2_0 and getattr(settings, "MIDDLEWARE", None) is None: + return "MIDDLEWARE_CLASSES" + return "MIDDLEWARE" + + +class DjangoInstrumentor(BaseInstrumentor): + """An instrumentor for Django + + See `BaseInstrumentor` + """ + + _opentelemetry_middleware = ".".join( + [_DjangoMiddleware.__module__, _DjangoMiddleware.__qualname__] + ) + + _sql_commenter_middleware = "azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.django.middleware.sqlcommenter_middleware.SqlCommenter" + + def instrumentation_dependencies(self) -> Collection[str]: + return _instruments + + def _instrument(self, **kwargs): + # FIXME this is probably a pattern that will show up in the rest of the + # ext. Find a better way of implementing this. + if environ.get(OTEL_PYTHON_DJANGO_INSTRUMENT) == "False": + return + + tracer_provider = kwargs.get("tracer_provider") + meter_provider = kwargs.get("meter_provider") + _excluded_urls = kwargs.get("excluded_urls") + tracer = get_tracer( + __name__, + __version__, + tracer_provider=tracer_provider, + ) + meter = get_meter(__name__, __version__, meter_provider=meter_provider) + _DjangoMiddleware._tracer = tracer + _DjangoMiddleware._meter = meter + _DjangoMiddleware._excluded_urls = ( + _excluded_urls_from_env + if _excluded_urls is None + else parse_excluded_urls(_excluded_urls) + ) + _DjangoMiddleware._otel_request_hook = kwargs.pop("request_hook", None) + _DjangoMiddleware._otel_response_hook = kwargs.pop( + "response_hook", None + ) + _DjangoMiddleware._duration_histogram = meter.create_histogram( + name=MetricInstruments.HTTP_SERVER_DURATION, + unit="ms", + description="measures the duration of the inbound http request", + ) + _DjangoMiddleware._active_request_counter = meter.create_up_down_counter( + name=MetricInstruments.HTTP_SERVER_ACTIVE_REQUESTS, + unit="requests", + description="measures the number of concurrent HTTP requests those are currently in flight", + ) + # This can not be solved, but is an inherent problem of this approach: + # the order of middleware entries matters, and here you have no control + # on that: + # https://docs.djangoproject.com/en/3.0/topics/http/middleware/#activating-middleware + # https://docs.djangoproject.com/en/3.0/ref/middleware/#middleware-ordering + + _middleware_setting = _get_django_middleware_setting() + settings_middleware = [] + try: + settings_middleware = getattr(settings, _middleware_setting, []) + except ImproperlyConfigured as exception: + _logger.debug( + "DJANGO_SETTINGS_MODULE environment variable not configured. Defaulting to empty settings: %s", + exception, + ) + settings.configure() + settings_middleware = getattr(settings, _middleware_setting, []) + except ModuleNotFoundError as exception: + _logger.debug( + "DJANGO_SETTINGS_MODULE points to a non-existent module. Defaulting to empty settings: %s", + exception, + ) + settings.configure() + settings_middleware = getattr(settings, _middleware_setting, []) + + # Django allows to specify middlewares as a tuple, so we convert this tuple to a + # list, otherwise we wouldn't be able to call append/remove + if isinstance(settings_middleware, tuple): + settings_middleware = list(settings_middleware) + + is_sql_commentor_enabled = kwargs.pop("is_sql_commentor_enabled", None) + + if is_sql_commentor_enabled: + settings_middleware.insert(0, self._sql_commenter_middleware) + + settings_middleware.insert(0, self._opentelemetry_middleware) + + setattr(settings, _middleware_setting, settings_middleware) + + def _uninstrument(self, **kwargs): + _middleware_setting = _get_django_middleware_setting() + settings_middleware = getattr(settings, _middleware_setting, None) + + # FIXME This is starting to smell like trouble. We have 2 mechanisms + # that may make this condition be True, one implemented in + # BaseInstrumentor and another one implemented in _instrument. Both + # stop _instrument from running and thus, settings_middleware not being + # set. + if settings_middleware is None or ( + self._opentelemetry_middleware not in settings_middleware + ): + return + + settings_middleware.remove(self._opentelemetry_middleware) + setattr(settings, _middleware_setting, settings_middleware) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/environment_variables.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/environment_variables.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/environment_variables.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/environment_variables.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/middleware/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/middleware/__init__.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/middleware/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/middleware/__init__.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/middleware/otel_middleware.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/middleware/otel_middleware.py similarity index 92% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/middleware/otel_middleware.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/middleware/otel_middleware.py index 5ac16097..f7e34c3c 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/middleware/otel_middleware.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/middleware/otel_middleware.py @@ -22,31 +22,31 @@ from django.http import HttpRequest, HttpResponse from opentelemetry.context import detach -from opentelemetry.instrumentation.propagators import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.propagators import ( get_global_response_propagator, ) -from opentelemetry.instrumentation.utils import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.utils import ( _start_internal_or_server_span, extract_attributes_from_object, ) -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.wsgi import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.wsgi import ( add_response_attributes, ) -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.wsgi import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.wsgi import ( collect_custom_request_headers_attributes as wsgi_collect_custom_request_headers_attributes, ) -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.wsgi import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.wsgi import ( collect_custom_response_headers_attributes as wsgi_collect_custom_response_headers_attributes, ) -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.wsgi import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.wsgi import ( collect_request_attributes as wsgi_collect_request_attributes, ) -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.wsgi import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.wsgi import ( wsgi_getter, ) from opentelemetry.semconv.trace import SpanAttributes from opentelemetry.trace import Span, SpanKind, use_span -from azure.monitor.opentelemetry.vendor.opentelemetry.util.http import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.util.http import ( _parse_active_request_count_attrs, _parse_duration_attrs, get_excluded_urls, @@ -93,20 +93,20 @@ def __call__(self, request): # try/except block exclusive for optional ASGI imports. try: - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.asgi import ( + from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.asgi import ( asgi_getter, asgi_setter, ) - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.asgi import ( + from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.asgi import ( collect_custom_request_headers_attributes as asgi_collect_custom_request_attributes, ) - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.asgi import ( + from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.asgi import ( collect_custom_response_headers_attributes as asgi_collect_custom_response_attributes, ) - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.asgi import ( + from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.asgi import ( collect_request_attributes as asgi_collect_request_attributes, ) - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.asgi import ( + from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.asgi import ( set_status_code, ) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/middleware/sqlcommenter_middleware.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/middleware/sqlcommenter_middleware.py similarity index 94% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/middleware/sqlcommenter_middleware.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/middleware/sqlcommenter_middleware.py index 89d8a9b7..5a4a0149 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/middleware/sqlcommenter_middleware.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/middleware/sqlcommenter_middleware.py @@ -22,8 +22,12 @@ from django.db import connections from django.db.backends.utils import CursorDebugWrapper -from opentelemetry.instrumentation.sqlcommenter_utils import _add_sql_comment -from opentelemetry.instrumentation.utils import _get_opentelemetry_values +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.sqlcommenter_utils import ( + _add_sql_comment, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.utils import ( + _get_opentelemetry_values, +) from opentelemetry.trace.propagation.tracecontext import ( TraceContextTextMapPropagator, ) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/package.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/package.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/package.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/package.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/version.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/version.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/version.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/environment_variables.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/environment_variables.py new file mode 100644 index 00000000..ad28f068 --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/environment_variables.py @@ -0,0 +1,18 @@ +# 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. + +OTEL_PYTHON_DISABLED_INSTRUMENTATIONS = "OTEL_PYTHON_DISABLED_INSTRUMENTATIONS" +""" +.. envvar:: OTEL_PYTHON_DISABLED_INSTRUMENTATIONS +""" diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/fastapi/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/fastapi/__init__.py new file mode 100644 index 00000000..42b86cf9 --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/fastapi/__init__.py @@ -0,0 +1,194 @@ +# 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 logging +import typing +from typing import Collection + +import fastapi +from starlette.routing import Match + +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.asgi import ( + OpenTelemetryMiddleware, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.fastapi.package import ( + _instruments, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.fastapi.version import ( + __version__, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.instrumentor import ( + BaseInstrumentor, +) +from opentelemetry.metrics import get_meter +from opentelemetry.semconv.trace import SpanAttributes +from opentelemetry.trace import Span +from azure.monitor.opentelemetry._vendor.opentelemetry.util.http import ( + get_excluded_urls, + parse_excluded_urls, +) + +_excluded_urls_from_env = get_excluded_urls("FASTAPI") +_logger = logging.getLogger(__name__) + +_ServerRequestHookT = typing.Optional[typing.Callable[[Span, dict], None]] +_ClientRequestHookT = typing.Optional[typing.Callable[[Span, dict], None]] +_ClientResponseHookT = typing.Optional[typing.Callable[[Span, dict], None]] + + +class FastAPIInstrumentor(BaseInstrumentor): + """An instrumentor for FastAPI + + See `BaseInstrumentor` + """ + + _original_fastapi = None + + @staticmethod + def instrument_app( + app: fastapi.FastAPI, + server_request_hook: _ServerRequestHookT = None, + client_request_hook: _ClientRequestHookT = None, + client_response_hook: _ClientResponseHookT = None, + tracer_provider=None, + meter_provider=None, + excluded_urls=None, + ): + """Instrument an uninstrumented FastAPI application.""" + if not hasattr(app, "_is_instrumented_by_opentelemetry"): + app._is_instrumented_by_opentelemetry = False + + if not getattr(app, "_is_instrumented_by_opentelemetry", False): + if excluded_urls is None: + excluded_urls = _excluded_urls_from_env + else: + excluded_urls = parse_excluded_urls(excluded_urls) + meter = get_meter(__name__, __version__, meter_provider) + + app.add_middleware( + OpenTelemetryMiddleware, + excluded_urls=excluded_urls, + default_span_details=_get_route_details, + server_request_hook=server_request_hook, + client_request_hook=client_request_hook, + client_response_hook=client_response_hook, + tracer_provider=tracer_provider, + meter=meter, + ) + app._is_instrumented_by_opentelemetry = True + if app not in _InstrumentedFastAPI._instrumented_fastapi_apps: + _InstrumentedFastAPI._instrumented_fastapi_apps.add(app) + else: + _logger.warning( + "Attempting to instrument FastAPI app while already instrumented" + ) + + @staticmethod + def uninstrument_app(app: fastapi.FastAPI): + app.user_middleware = [ + x + for x in app.user_middleware + if x.cls is not OpenTelemetryMiddleware + ] + app.middleware_stack = app.build_middleware_stack() + app._is_instrumented_by_opentelemetry = False + + def instrumentation_dependencies(self) -> Collection[str]: + return _instruments + + def _instrument(self, **kwargs): + self._original_fastapi = fastapi.FastAPI + _InstrumentedFastAPI._tracer_provider = kwargs.get("tracer_provider") + _InstrumentedFastAPI._server_request_hook = kwargs.get( + "server_request_hook" + ) + _InstrumentedFastAPI._client_request_hook = kwargs.get( + "client_request_hook" + ) + _InstrumentedFastAPI._client_response_hook = kwargs.get( + "client_response_hook" + ) + _excluded_urls = kwargs.get("excluded_urls") + _InstrumentedFastAPI._excluded_urls = ( + _excluded_urls_from_env + if _excluded_urls is None + else parse_excluded_urls(_excluded_urls) + ) + _InstrumentedFastAPI._meter_provider = kwargs.get("meter_provider") + fastapi.FastAPI = _InstrumentedFastAPI + + def _uninstrument(self, **kwargs): + for instance in _InstrumentedFastAPI._instrumented_fastapi_apps: + self.uninstrument_app(instance) + _InstrumentedFastAPI._instrumented_fastapi_apps.clear() + fastapi.FastAPI = self._original_fastapi + + +class _InstrumentedFastAPI(fastapi.FastAPI): + _tracer_provider = None + _meter_provider = None + _excluded_urls = None + _server_request_hook: _ServerRequestHookT = None + _client_request_hook: _ClientRequestHookT = None + _client_response_hook: _ClientResponseHookT = None + _instrumented_fastapi_apps = set() + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + meter = get_meter( + __name__, __version__, _InstrumentedFastAPI._meter_provider + ) + self.add_middleware( + OpenTelemetryMiddleware, + excluded_urls=_InstrumentedFastAPI._excluded_urls, + default_span_details=_get_route_details, + server_request_hook=_InstrumentedFastAPI._server_request_hook, + client_request_hook=_InstrumentedFastAPI._client_request_hook, + client_response_hook=_InstrumentedFastAPI._client_response_hook, + tracer_provider=_InstrumentedFastAPI._tracer_provider, + meter=meter, + ) + self._is_instrumented_by_opentelemetry = True + _InstrumentedFastAPI._instrumented_fastapi_apps.add(self) + + def __del__(self): + if self in _InstrumentedFastAPI._instrumented_fastapi_apps: + _InstrumentedFastAPI._instrumented_fastapi_apps.remove(self) + + +def _get_route_details(scope): + """Callback to retrieve the fastapi route being served. + + TODO: there is currently no way to retrieve http.route from + a starlette application from scope. + + See: https://github.com/encode/starlette/pull/804 + """ + app = scope["app"] + route = None + for starlette_route in app.routes: + match, _ = starlette_route.matches(scope) + if match == Match.FULL: + route = starlette_route.path + break + if match == Match.PARTIAL: + route = starlette_route.path + # method only exists for http, if websocket + # leave it blank. + span_name = route or scope.get("method", "") + attributes = {} + if route: + attributes[SpanAttributes.HTTP_ROUTE] = route + return span_name, attributes diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/fastapi/package.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/fastapi/package.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/fastapi/package.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/fastapi/package.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/fastapi/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/fastapi/version.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/fastapi/version.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/fastapi/version.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/flask/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/flask/__init__.py similarity index 63% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/flask/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/flask/__init__.py index 062570d1..c1681da7 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/flask/__init__.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/flask/__init__.py @@ -15,229 +15,7 @@ # Note: This package is not named "flask" because of # https://github.com/PyCQA/pylint/issues/2648 -""" -This library builds on the OpenTelemetry WSGI middleware to track web requests -in Flask applications. In addition to opentelemetry-util-http, it -supports Flask-specific features such as: -* The Flask url rule pattern is used as the Span name. -* The ``http.route`` Span attribute is set so that one can see which URL rule - matched a request. - -SQLCOMMENTER -***************************************** -You can optionally configure Flask instrumentation to enable sqlcommenter which enriches -the query with contextual information. - -Usage ------ - -.. code:: python - - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.flask import FlaskInstrumentor - - FlaskInstrumentor().instrument(enable_commenter=True, commenter_options={}) - -For example, FlaskInstrumentor when used with SQLAlchemyInstrumentor or Psycopg2Instrumentor, -invoking ``cursor.execute("select * from auth_users")`` will lead to sql query -``select * from auth_users`` but when SQLCommenter is enabled the query will get appended with -some configurable tags like: - -.. code:: - - select * from auth_users /*metrics=value*/;" - -Inorder for the commenter to append flask related tags to sql queries, the commenter needs -to enabled on the respective SQLAlchemyInstrumentor or Psycopg2Instrumentor framework too. - -SQLCommenter Configurations -*************************** -We can configure the tags to be appended to the sqlquery log by adding configuration -inside ``commenter_options={}`` dict. - -For example, enabling this flag will add flask and it's version which -is ``/*flask%%3A2.9.3*/`` to the SQL query as a comment (default is True): - -.. code:: python - - framework = True - -For example, enabling this flag will add route uri ``/*route='/home'*/`` -to the SQL query as a comment (default is True): - -.. code:: python - - route = True - -For example, enabling this flag will add controller name ``/*controller='home_view'*/`` -to the SQL query as a comment (default is True): - -.. code:: python - - controller = True - -Usage ------ - -.. code-block:: python - - from flask import Flask - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.flask import FlaskInstrumentor - - app = Flask(__name__) - - FlaskInstrumentor().instrument_app(app) - - @app.route("/") - def hello(): - return "Hello!" - - if __name__ == "__main__": - app.run(debug=True) - -Configuration -------------- - -Exclude lists -************* -To exclude certain URLs from tracking, set the environment variable ``OTEL_PYTHON_FLASK_EXCLUDED_URLS`` -(or ``OTEL_PYTHON_EXCLUDED_URLS`` to cover all instrumentations) to a string of comma delimited regexes that match the -URLs. - -For example, - -:: - - export OTEL_PYTHON_FLASK_EXCLUDED_URLS="client/.*/info,healthcheck" - -will exclude requests such as ``https://site/client/123/info`` and ``https://site/xyz/healthcheck``. - -You can also pass comma delimited regexes directly to the ``instrument_app`` method: - -.. code-block:: python - - FlaskInstrumentor().instrument_app(app, excluded_urls="client/.*/info,healthcheck") - -Request/Response hooks -********************** - -This instrumentation supports request and response hooks. These are functions that get called -right after a span is created for a request and right before the span is finished for the response. - -- The client request hook is called with the internal span and an instance of WSGIEnvironment (flask.request.environ) - when the method ``receive`` is called. -- The client response hook is called with the internal span, the status of the response and a list of key-value (tuples) - representing the response headers returned from the response when the method ``send`` is called. - -For example, - -.. code-block:: python - - def request_hook(span: Span, environ: WSGIEnvironment): - if span and span.is_recording(): - span.set_attribute("custom_user_attribute_from_request_hook", "some-value") - - def response_hook(span: Span, status: str, response_headers: List): - if span and span.is_recording(): - span.set_attribute("custom_user_attribute_from_response_hook", "some-value") - - FlaskInstrumentation().instrument(request_hook=request_hook, response_hook=response_hook) - -Flask Request object reference: https://flask.palletsprojects.com/en/2.1.x/api/#flask.Request - -Capture HTTP request and response headers -***************************************** -You can configure the agent to capture specified HTTP headers as span attributes, according to the -`semantic convention `_. - -Request headers -*************** -To capture HTTP request headers as span attributes, set the environment variable -``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST`` to a comma delimited list of HTTP header names. - -For example, -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST="content-type,custom_request_header" - -will extract ``content-type`` and ``custom_request_header`` from the request headers and add them as span attributes. - -Request header names in Flask are case-insensitive and ``-`` characters are replaced by ``_``. So, giving the header -name as ``CUStom_Header`` in the environment variable will capture the header named ``custom-header``. - -Regular expressions may also be used to match multiple headers that correspond to the given pattern. For example: -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST="Accept.*,X-.*" - -Would match all request headers that start with ``Accept`` and ``X-``. - -To capture all request headers, set ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST`` to ``".*"``. -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST=".*" - -The name of the added span attribute will follow the format ``http.request.header.`` where ```` -is the normalized HTTP header name (lowercase, with ``-`` replaced by ``_``). The value of the attribute will be a -single item list containing all the header values. - -For example: -``http.request.header.custom_request_header = [","]`` - -Response headers -**************** -To capture HTTP response headers as span attributes, set the environment variable -``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE`` to a comma delimited list of HTTP header names. - -For example, -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE="content-type,custom_response_header" - -will extract ``content-type`` and ``custom_response_header`` from the response headers and add them as span attributes. - -Response header names in Flask are case-insensitive. So, giving the header name as ``CUStom-Header`` in the environment -variable will capture the header named ``custom-header``. - -Regular expressions may also be used to match multiple headers that correspond to the given pattern. For example: -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE="Content.*,X-.*" - -Would match all response headers that start with ``Content`` and ``X-``. - -To capture all response headers, set ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE`` to ``".*"``. -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE=".*" - -The name of the added span attribute will follow the format ``http.response.header.`` where ```` -is the normalized HTTP header name (lowercase, with ``-`` replaced by ``_``). The value of the attribute will be a -single item list containing all the header values. - -For example: -``http.response.header.custom_response_header = [","]`` - -Sanitizing headers -****************** -In order to prevent storing sensitive data such as personally identifiable information (PII), session keys, passwords, -etc, set the environment variable ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS`` -to a comma delimited list of HTTP header names to be sanitized. Regexes may be used, and all header names will be -matched in a case-insensitive manner. - -For example, -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS=".*session.*,set-cookie" - -will replace the value of headers such as ``session-id`` and ``set-cookie`` with ``[REDACTED]`` in the span. - -Note: - The environment variable names used to capture HTTP headers are still experimental, and thus are subject to change. - -API ---- -""" from logging import getLogger from threading import get_ident from time import time_ns @@ -246,23 +24,27 @@ def response_hook(span: Span, status: str, response_headers: List): import flask -import azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.wsgi as otel_wsgi +import azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.wsgi as otel_wsgi from opentelemetry import context, trace -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.flask.package import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.flask.package import ( _instruments, ) -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.flask.version import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.flask.version import ( __version__, ) -from opentelemetry.instrumentation.instrumentor import BaseInstrumentor -from opentelemetry.instrumentation.propagators import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.instrumentor import ( + BaseInstrumentor, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.propagators import ( get_global_response_propagator, ) -from opentelemetry.instrumentation.utils import _start_internal_or_server_span +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.utils import ( + _start_internal_or_server_span, +) from opentelemetry.metrics import get_meter from opentelemetry.semconv.metrics import MetricInstruments from opentelemetry.semconv.trace import SpanAttributes -from azure.monitor.opentelemetry.vendor.opentelemetry.util.http import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.util.http import ( get_excluded_urls, parse_excluded_urls, ) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/flask/package.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/flask/package.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/flask/package.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/flask/package.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/flask/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/flask/version.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/flask/version.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/flask/version.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/instrumentor.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/instrumentor.py new file mode 100644 index 00000000..520f1dc8 --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/instrumentor.py @@ -0,0 +1,131 @@ +# 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. +# type: ignore + +""" +OpenTelemetry Base Instrumentor +""" + +from abc import ABC, abstractmethod +from logging import getLogger +from typing import Collection, Optional + +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.dependencies import ( + DependencyConflict, + get_dependency_conflicts, +) + +_LOG = getLogger(__name__) + + +class BaseInstrumentor(ABC): + """An ABC for instrumentors + + Child classes of this ABC should instrument specific third + party libraries or frameworks either by using the + ``opentelemetry-instrument`` command or by calling their methods + directly. + + Since every third party library or framework is different and has different + instrumentation needs, more methods can be added to the child classes as + needed to provide practical instrumentation to the end user. + """ + + _instance = None + _is_instrumented_by_opentelemetry = False + + def __new__(cls, *args, **kwargs): + if cls._instance is None: + cls._instance = object.__new__(cls) + + return cls._instance + + @property + def is_instrumented_by_opentelemetry(self): + return self._is_instrumented_by_opentelemetry + + @abstractmethod + def instrumentation_dependencies(self) -> Collection[str]: + """Return a list of python packages with versions that the will be instrumented. + + The format should be the same as used in requirements.txt or pyproject.toml. + + For example, if an instrumentation instruments requests 1.x, this method should look + like: + + def instrumentation_dependencies(self) -> Collection[str]: + return ['requests ~= 1.0'] + + This will ensure that the instrumentation will only be used when the specified library + is present in the environment. + """ + + def _instrument(self, **kwargs): + """Instrument the library""" + + @abstractmethod + def _uninstrument(self, **kwargs): + """Uninstrument the library""" + + def _check_dependency_conflicts(self) -> Optional[DependencyConflict]: + dependencies = self.instrumentation_dependencies() + return get_dependency_conflicts(dependencies) + + def instrument(self, **kwargs): + """Instrument the library + + This method will be called without any optional arguments by the + ``opentelemetry-instrument`` command. + + This means that calling this method directly without passing any + optional values should do the very same thing that the + ``opentelemetry-instrument`` command does. + """ + + if self._is_instrumented_by_opentelemetry: + _LOG.warning("Attempting to instrument while already instrumented") + return None + + # check if instrumentor has any missing or conflicting dependencies + skip_dep_check = kwargs.pop("skip_dep_check", False) + if not skip_dep_check: + conflict = self._check_dependency_conflicts() + if conflict: + _LOG.error(conflict) + return None + + result = self._instrument( # pylint: disable=assignment-from-no-return + **kwargs + ) + self._is_instrumented_by_opentelemetry = True + return result + + def uninstrument(self, **kwargs): + """Uninstrument the library + + See ``BaseInstrumentor.instrument`` for more information regarding the + usage of ``kwargs``. + """ + + if self._is_instrumented_by_opentelemetry: + result = self._uninstrument(**kwargs) + self._is_instrumented_by_opentelemetry = False + return result + + _LOG.warning("Attempting to uninstrument while already uninstrumented") + + return None + + +__all__ = ["BaseInstrumentor"] diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/propagators.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/propagators.py new file mode 100644 index 00000000..bc40f774 --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/propagators.py @@ -0,0 +1,124 @@ +# 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 module implements experimental propagators to inject trace context +into response carriers. This is useful for server side frameworks that start traces +when server requests and want to share the trace context with the client so the +client can add its spans to the same trace. + +This is part of an upcoming W3C spec and will eventually make it to the Otel spec. + +https://w3c.github.io/trace-context/#trace-context-http-response-headers-format +""" + +import typing +from abc import ABC, abstractmethod + +from opentelemetry import trace +from opentelemetry.context.context import Context +from opentelemetry.propagators import textmap +from opentelemetry.trace import format_span_id, format_trace_id + +_HTTP_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers" +_RESPONSE_PROPAGATOR = None + + +def get_global_response_propagator(): + return _RESPONSE_PROPAGATOR + + +def set_global_response_propagator(propagator): + global _RESPONSE_PROPAGATOR # pylint:disable=global-statement + _RESPONSE_PROPAGATOR = propagator + + +class Setter(ABC): + @abstractmethod + def set(self, carrier, key, value): + """Inject the provided key value pair in carrier.""" + + +class DictHeaderSetter(Setter): + def set(self, carrier, key, value): # pylint: disable=no-self-use + old_value = carrier.get(key, "") + if old_value: + value = f"{old_value}, {value}" + carrier[key] = value + + +class FuncSetter(Setter): + """FuncSetter coverts a function into a valid Setter. Any function that can + set values in a carrier can be converted into a Setter by using FuncSetter. + This is useful when injecting trace context into non-dict objects such + HTTP Response objects for different framework. + + For example, it can be used to create a setter for Falcon response object as: + + setter = FuncSetter(falcon.api.Response.append_header) + + and then used with the propagator as: + + propagator.inject(falcon_response, setter=setter) + + This would essentially make the propagator call `falcon_response.append_header(key, value)` + """ + + def __init__(self, func): + self._func = func + + def set(self, carrier, key, value): + self._func(carrier, key, value) + + +default_setter = DictHeaderSetter() + + +class ResponsePropagator(ABC): + @abstractmethod + def inject( + self, + carrier: textmap.CarrierT, + context: typing.Optional[Context] = None, + setter: textmap.Setter = default_setter, + ) -> None: + """Injects SpanContext into the HTTP response carrier.""" + + +class TraceResponsePropagator(ResponsePropagator): + """Experimental propagator that injects tracecontext into HTTP responses.""" + + def inject( + self, + carrier: textmap.CarrierT, + context: typing.Optional[Context] = None, + setter: textmap.Setter = default_setter, + ) -> None: + """Injects SpanContext into the HTTP response carrier.""" + span = trace.get_current_span(context) + span_context = span.get_span_context() + if span_context == trace.INVALID_SPAN_CONTEXT: + return + + header_name = "traceresponse" + setter.set( + carrier, + header_name, + f"00-{format_trace_id(span_context.trace_id)}-{format_span_id(span_context.span_id)}-{span_context.trace_flags:02x}", + ) + setter.set( + carrier, + _HTTP_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS, + header_name, + ) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/psycopg2/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/psycopg2/__init__.py similarity index 69% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/psycopg2/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/psycopg2/__init__.py index 0ff18571..820496bf 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/psycopg2/__init__.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/psycopg2/__init__.py @@ -12,94 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" -The integration with PostgreSQL supports the `Psycopg`_ library, it can be enabled by -using ``Psycopg2Instrumentor``. - -.. _Psycopg: http://initd.org/psycopg/ - -SQLCOMMENTER -***************************************** -You can optionally configure Psycopg2 instrumentation to enable sqlcommenter which enriches -the query with contextual information. - -Usage ------ - -.. code:: python - - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.psycopg2 import Psycopg2Instrumentor - - Psycopg2Instrumentor().instrument(enable_commenter=True, commenter_options={}) - - -For example, -:: - - Invoking cursor.execute("select * from auth_users") will lead to sql query "select * from auth_users" but when SQLCommenter is enabled - the query will get appended with some configurable tags like "select * from auth_users /*tag=value*/;" - - -SQLCommenter Configurations -*************************** -We can configure the tags to be appended to the sqlquery log by adding configuration inside commenter_options(default:{}) keyword - -db_driver = True(Default) or False - -For example, -:: -Enabling this flag will add psycopg2 and it's version which is /*psycopg2%%3A2.9.3*/ - -dbapi_threadsafety = True(Default) or False - -For example, -:: -Enabling this flag will add threadsafety /*dbapi_threadsafety=2*/ - -dbapi_level = True(Default) or False - -For example, -:: -Enabling this flag will add dbapi_level /*dbapi_level='2.0'*/ - -libpq_version = True(Default) or False - -For example, -:: -Enabling this flag will add libpq_version /*libpq_version=140001*/ - -driver_paramstyle = True(Default) or False - -For example, -:: -Enabling this flag will add driver_paramstyle /*driver_paramstyle='pyformat'*/ - -opentelemetry_values = True(Default) or False - -For example, -:: -Enabling this flag will add traceparent values /*traceparent='00-03afa25236b8cd948fa853d67038ac79-405ff022e8247c46-01'*/ - -Usage ------ - -.. code-block:: python - - import psycopg2 - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.psycopg2 import Psycopg2Instrumentor - - - Psycopg2Instrumentor().instrument() - - cnx = psycopg2.connect(database='Database') - cursor = cnx.cursor() - cursor.execute("INSERT INTO test (testField) VALUES (123)") - cursor.close() - cnx.close() - -API ---- -""" import logging import typing @@ -111,14 +23,16 @@ ) from psycopg2.sql import Composed # pylint: disable=no-name-in-module -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation import ( dbapi, ) -from opentelemetry.instrumentation.instrumentor import BaseInstrumentor -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.psycopg2.package import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.instrumentor import ( + BaseInstrumentor, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.psycopg2.package import ( _instruments, ) -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.psycopg2.version import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.psycopg2.version import ( __version__, ) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/psycopg2/package.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/psycopg2/package.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/psycopg2/package.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/psycopg2/package.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/psycopg2/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/psycopg2/version.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/psycopg2/version.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/psycopg2/version.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/py.typed b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/requests/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/requests/__init__.py similarity index 86% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/requests/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/requests/__init__.py index 6ff4ea83..af7a348e 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/requests/__init__.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/requests/__init__.py @@ -12,41 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" -This library allows tracing HTTP requests made by the -`requests `_ library. - -Usage ------ - -.. code-block:: python - - import requests - from azure.monitor.opentelemetry.vendor.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 -------------- - -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 ---- -""" import functools import types @@ -62,14 +27,16 @@ # FIXME: fix the importing of this private attribute when the location of the _SUPPRESS_HTTP_INSTRUMENTATION_KEY is defined. from opentelemetry.context import _SUPPRESS_HTTP_INSTRUMENTATION_KEY -from opentelemetry.instrumentation.instrumentor import BaseInstrumentor -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.requests.package import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.instrumentor import ( + BaseInstrumentor, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.requests.package import ( _instruments, ) -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.requests.version import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.requests.version import ( __version__, ) -from opentelemetry.instrumentation.utils import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.utils import ( _SUPPRESS_INSTRUMENTATION_KEY, http_status_to_status_code, ) @@ -80,12 +47,12 @@ from opentelemetry.trace import SpanKind, Tracer, get_tracer from opentelemetry.trace.span import Span from opentelemetry.trace.status import Status -from azure.monitor.opentelemetry.vendor.opentelemetry.util.http import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.util.http import ( get_excluded_urls, parse_excluded_urls, remove_url_credentials, ) -from azure.monitor.opentelemetry.vendor.opentelemetry.util.http.httplib import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.util.http.httplib import ( set_ip_on_next_http_connection, ) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/requests/package.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/requests/package.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/requests/package.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/requests/package.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/requests/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/requests/version.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/requests/version.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/requests/version.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/sqlcommenter_utils.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/sqlcommenter_utils.py new file mode 100644 index 00000000..3616ea3c --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/sqlcommenter_utils.py @@ -0,0 +1,68 @@ +# 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 opentelemetry import context +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.utils import ( + _url_quote, +) + + +def _add_sql_comment(sql, **meta) -> str: + """ + Appends comments to the sql statement and returns it + """ + meta.update(**_add_framework_tags()) + comment = _generate_sql_comment(**meta) + sql = sql.rstrip() + if sql[-1] == ";": + sql = sql[:-1] + comment + ";" + else: + sql = sql + comment + return sql + + +def _generate_sql_comment(**meta) -> str: + """ + Return a SQL comment with comma delimited key=value pairs created from + **meta kwargs. + """ + key_value_delimiter = "," + + if not meta: # No entries added. + return "" + + # Sort the keywords to ensure that caching works and that testing is + # deterministic. It eases visual inspection as well. + return ( + " /*" + + key_value_delimiter.join( + f"{_url_quote(key)}={_url_quote(value)!r}" + for key, value in sorted(meta.items()) + if value is not None + ) + + "*/" + ) + + +def _add_framework_tags() -> dict: + """ + Returns orm related tags if any set by the context + """ + + sqlcommenter_framework_values = ( + context.get_value("SQLCOMMENTER_ORM_TAGS_AND_VALUES") + if context.get_value("SQLCOMMENTER_ORM_TAGS_AND_VALUES") + else {} + ) + return sqlcommenter_framework_values diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib/__init__.py similarity index 83% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib/__init__.py index f13bbcb7..50c4ee06 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib/__init__.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib/__init__.py @@ -12,53 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" -This library allows tracing HTTP requests made by the -`urllib `_ library. - -Usage ------ -.. code-block:: python - - from urllib import request - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.urllib import URLLibInstrumentor - - # You can optionally pass a custom TracerProvider to - # URLLibInstrumentor().instrument() - - URLLibInstrumentor().instrument() - req = request.Request('https://postman-echo.com/post', method="POST") - r = request.urlopen(req) - -Configuration -------------- - -Request/Response hooks -********************** - -The urllib 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 - - # `request_obj` is an instance of urllib.request.Request - def request_hook(span, request_obj): - pass - - # `request_obj` is an instance of urllib.request.Request - # `response` is an instance of http.client.HTTPResponse - def response_hook(span, request_obj, response) - pass - - URLLibInstrumentor.instrument( - request_hook=request_hook, response_hook=response_hook) - ) - -API ---- -""" import functools import types @@ -75,14 +28,16 @@ def response_hook(span, request_obj, response) # FIXME: fix the importing of this private attribute when the location of the _SUPPRESS_HTTP_INSTRUMENTATION_KEY is defined. from opentelemetry.context import _SUPPRESS_HTTP_INSTRUMENTATION_KEY -from opentelemetry.instrumentation.instrumentor import BaseInstrumentor -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.urllib.package import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.instrumentor import ( + BaseInstrumentor, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.urllib.package import ( _instruments, ) -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.urllib.version import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.urllib.version import ( __version__, ) -from opentelemetry.instrumentation.utils import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.utils import ( _SUPPRESS_INSTRUMENTATION_KEY, http_status_to_status_code, ) @@ -92,7 +47,7 @@ def response_hook(span, request_obj, response) from opentelemetry.semconv.trace import SpanAttributes from opentelemetry.trace import Span, SpanKind, get_tracer from opentelemetry.trace.status import Status -from azure.monitor.opentelemetry.vendor.opentelemetry.util.http import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.util.http import ( remove_url_credentials, ) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib/package.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib/package.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib/package.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib/package.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib/version.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib/version.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib/version.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib3/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib3/__init__.py similarity index 83% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib3/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib3/__init__.py index 2ec1d57d..7291d0f9 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib3/__init__.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib3/__init__.py @@ -12,57 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" -This library allows tracing HTTP requests made by the -`urllib3 `_ library. - -Usage ------ -.. code-block:: python - - import urllib3 - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.urllib3 import URLLib3Instrumentor - - def strip_query_params(url: str) -> str: - return url.split("?")[0] - - URLLib3Instrumentor().instrument( - # Remove all query params from the URL attribute on the span. - url_filter=strip_query_params, - ) - - http = urllib3.PoolManager() - response = http.request("GET", "https://www.example.org/") - -Configuration -------------- - -Request/Response hooks -********************** - -The urllib3 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 - - # `request` is an instance of urllib3.connectionpool.HTTPConnectionPool - def request_hook(span, request): - pass - - # `request` is an instance of urllib3.connectionpool.HTTPConnectionPool - # `response` is an instance of urllib3.response.HTTPResponse - def response_hook(span, request, response): - pass - - URLLib3Instrumentor.instrument( - request_hook=request_hook, response_hook=response_hook) - ) - -API ---- -""" import collections.abc import contextlib @@ -78,14 +27,16 @@ def response_hook(span, request, response): # FIXME: fix the importing of this private attribute when the location of the _SUPPRESS_HTTP_INSTRUMENTATION_KEY is defined. from opentelemetry.context import _SUPPRESS_HTTP_INSTRUMENTATION_KEY -from opentelemetry.instrumentation.instrumentor import BaseInstrumentor -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.urllib3.package import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.instrumentor import ( + BaseInstrumentor, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.urllib3.package import ( _instruments, ) -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.urllib3.version import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.urllib3.version import ( __version__, ) -from opentelemetry.instrumentation.utils import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.utils import ( _SUPPRESS_INSTRUMENTATION_KEY, http_status_to_status_code, unwrap, @@ -96,7 +47,7 @@ def response_hook(span, request, response): from opentelemetry.semconv.trace import SpanAttributes from opentelemetry.trace import Span, SpanKind, Tracer, get_tracer from opentelemetry.trace.status import Status -from azure.monitor.opentelemetry.vendor.opentelemetry.util.http.httplib import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.util.http.httplib import ( set_ip_on_next_http_connection, ) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib3/package.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib3/package.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib3/package.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib3/package.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib3/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib3/version.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib3/version.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib3/version.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/utils.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/utils.py new file mode 100644 index 00000000..3022e6dd --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/utils.py @@ -0,0 +1,154 @@ +# 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 urllib.parse +from re import escape, sub +from typing import Dict, Sequence + +from wrapt import ObjectProxy + +from opentelemetry import context, trace + +# pylint: disable=unused-import +# pylint: disable=E0611 +from opentelemetry.context import _SUPPRESS_INSTRUMENTATION_KEY # noqa: F401 +from opentelemetry.propagate import extract +from opentelemetry.trace import StatusCode +from opentelemetry.trace.propagation.tracecontext import ( + TraceContextTextMapPropagator, +) + +propagator = TraceContextTextMapPropagator() + + +def extract_attributes_from_object( + obj: any, attributes: Sequence[str], existing: Dict[str, str] = None +) -> Dict[str, str]: + extracted = {} + if existing: + extracted.update(existing) + for attr in attributes: + value = getattr(obj, attr, None) + if value is not None: + extracted[attr] = str(value) + return extracted + + +def http_status_to_status_code( + status: int, + allow_redirect: bool = True, + server_span: bool = False, +) -> StatusCode: + """Converts an HTTP status code to an OpenTelemetry canonical status code + + Args: + status (int): HTTP status code + """ + # See: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#status + if not isinstance(status, int): + return StatusCode.UNSET + + if status < 100: + return StatusCode.ERROR + if status <= 299: + return StatusCode.UNSET + if status <= 399 and allow_redirect: + return StatusCode.UNSET + if status <= 499 and server_span: + return StatusCode.UNSET + return StatusCode.ERROR + + +def unwrap(obj, attr: str): + """Given a function that was wrapped by wrapt.wrap_function_wrapper, unwrap it + + Args: + obj: Object that holds a reference to the wrapped function + attr (str): Name of the wrapped function + """ + func = getattr(obj, attr, None) + if func and isinstance(func, ObjectProxy) and hasattr(func, "__wrapped__"): + setattr(obj, attr, func.__wrapped__) + + +def _start_internal_or_server_span( + tracer, + span_name, + start_time, + context_carrier, + context_getter, + attributes=None, +): + """Returns internal or server span along with the token which can be used by caller to reset context + + + Args: + tracer : tracer in use by given instrumentation library + name (string): name of the span + start_time : start time of the span + context_carrier : object which contains values that are + used to construct a Context. This object + must be paired with an appropriate getter + which understands how to extract a value from it. + context_getter : an object which contains a get function that can retrieve zero + or more values from the carrier and a keys function that can get all the keys + from carrier. + """ + + token = ctx = span_kind = None + if trace.get_current_span() is trace.INVALID_SPAN: + ctx = extract(context_carrier, getter=context_getter) + token = context.attach(ctx) + span_kind = trace.SpanKind.SERVER + else: + ctx = context.get_current() + span_kind = trace.SpanKind.INTERNAL + span = tracer.start_span( + name=span_name, + context=ctx, + kind=span_kind, + start_time=start_time, + attributes=attributes, + ) + return span, token + + +def _url_quote(s) -> str: # pylint: disable=invalid-name + if not isinstance(s, (str, bytes)): + return s + quoted = urllib.parse.quote(s) + # Since SQL uses '%' as a keyword, '%' is a by-product of url quoting + # e.g. foo,bar --> foo%2Cbar + # thus in our quoting, we need to escape it too to finally give + # foo,bar --> foo%%2Cbar + return quoted.replace("%", "%%") + + +def _get_opentelemetry_values() -> dict: + """ + Return the OpenTelemetry Trace and Span IDs if Span ID is set in the + OpenTelemetry execution context. + """ + # Insert the W3C TraceContext generated + _headers = {} + propagator.inject(_headers) + return _headers + + +def _python_path_without_directory(python_path, directory, path_separator): + return sub( + rf"{escape(directory)}{path_separator}(?!$)", + "", + python_path, + ) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/wsgi/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/version.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/wsgi/version.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/version.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/wsgi/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/wsgi/__init__.py similarity index 65% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/wsgi/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/wsgi/__init__.py index 0f7409fe..bba8211d 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/wsgi/__init__.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/wsgi/__init__.py @@ -11,195 +11,7 @@ # 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 a WSGI middleware that can be used on any WSGI framework -(such as Django / Flask / Web.py) to track requests timing through OpenTelemetry. -Usage (Flask) -------------- - -.. code-block:: python - - from flask import Flask - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.wsgi import OpenTelemetryMiddleware - - app = Flask(__name__) - app.wsgi_app = OpenTelemetryMiddleware(app.wsgi_app) - - @app.route("/") - def hello(): - return "Hello!" - - if __name__ == "__main__": - app.run(debug=True) - - -Usage (Django) --------------- - -Modify the application's ``wsgi.py`` file as shown below. - -.. code-block:: python - - import os - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.wsgi import OpenTelemetryMiddleware - from django.core.wsgi import get_wsgi_application - - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings') - - application = get_wsgi_application() - application = OpenTelemetryMiddleware(application) - -Usage (Web.py) --------------- - -.. code-block:: python - - import web - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.wsgi import OpenTelemetryMiddleware - from cheroot import wsgi - - urls = ('/', 'index') - - - class index: - - def GET(self): - return "Hello, world!" - - - if __name__ == "__main__": - app = web.application(urls, globals()) - func = app.wsgifunc() - - func = OpenTelemetryMiddleware(func) - - server = wsgi.WSGIServer( - ("localhost", 5100), func, server_name="localhost" - ) - server.start() - -Configuration -------------- - -Request/Response hooks -********************** - -This instrumentation supports request and response hooks. These are functions that get called -right after a span is created for a request and right before the span is finished for the response. - -- The client request hook is called with the internal span and an instance of WSGIEnvironment when the method - ``receive`` is called. -- The client response hook is called with the internal span, the status of the response and a list of key-value (tuples) - representing the response headers returned from the response when the method ``send`` is called. - -For example, - -.. code-block:: python - - def request_hook(span: Span, environ: WSGIEnvironment): - if span and span.is_recording(): - span.set_attribute("custom_user_attribute_from_request_hook", "some-value") - - def response_hook(span: Span, environ: WSGIEnvironment, status: str, response_headers: List): - if span and span.is_recording(): - span.set_attribute("custom_user_attribute_from_response_hook", "some-value") - - OpenTelemetryMiddleware(request_hook=request_hook, response_hook=response_hook) - -Capture HTTP request and response headers -***************************************** -You can configure the agent to capture specified HTTP headers as span attributes, according to the -`semantic convention `_. - -Request headers -*************** -To capture HTTP request headers as span attributes, set the environment variable -``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST`` to a comma delimited list of HTTP header names. - -For example, -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST="content-type,custom_request_header" - -will extract ``content-type`` and ``custom_request_header`` from the request headers and add them as span attributes. - -Request header names in WSGI are case-insensitive and ``-`` characters are replaced by ``_``. So, giving the header -name as ``CUStom_Header`` in the environment variable will capture the header named ``custom-header``. - -Regular expressions may also be used to match multiple headers that correspond to the given pattern. For example: -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST="Accept.*,X-.*" - -Would match all request headers that start with ``Accept`` and ``X-``. - -To capture all request headers, set ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST`` to ``".*"``. -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST=".*" - -The name of the added span attribute will follow the format ``http.request.header.`` where ```` -is the normalized HTTP header name (lowercase, with ``-`` replaced by ``_``). The value of the attribute will be a -single item list containing all the header values. - -For example: -``http.request.header.custom_request_header = [","]`` - -Response headers -**************** -To capture HTTP response headers as span attributes, set the environment variable -``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE`` to a comma delimited list of HTTP header names. - -For example, -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE="content-type,custom_response_header" - -will extract ``content-type`` and ``custom_response_header`` from the response headers and add them as span attributes. - -Response header names in WSGI are case-insensitive. So, giving the header name as ``CUStom-Header`` in the environment -variable will capture the header named ``custom-header``. - -Regular expressions may also be used to match multiple headers that correspond to the given pattern. For example: -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE="Content.*,X-.*" - -Would match all response headers that start with ``Content`` and ``X-``. - -To capture all response headers, set ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE`` to ``".*"``. -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE=".*" - -The name of the added span attribute will follow the format ``http.response.header.`` where ```` -is the normalized HTTP header name (lowercase, with ``-`` replaced by ``_``). The value of the attribute will be a -single item list containing all the header values. - -For example: -``http.response.header.custom_response_header = [","]`` - -Sanitizing headers -****************** -In order to prevent storing sensitive data such as personally identifiable information (PII), session keys, passwords, -etc, set the environment variable ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS`` -to a comma delimited list of HTTP header names to be sanitized. Regexes may be used, and all header names will be -matched in a case-insensitive manner. - -For example, -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS=".*session.*,set-cookie" - -will replace the value of headers such as ``session-id`` and ``set-cookie`` with ``[REDACTED]`` in the span. - -Note: - The environment variable names used to capture HTTP headers are still experimental, and thus are subject to change. - -API ---- -""" import functools import typing @@ -207,11 +19,11 @@ def response_hook(span: Span, environ: WSGIEnvironment, status: str, response_he from timeit import default_timer from opentelemetry import context, trace -from opentelemetry.instrumentation.utils import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.utils import ( _start_internal_or_server_span, http_status_to_status_code, ) -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.wsgi.version import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.wsgi.version import ( __version__, ) from opentelemetry.metrics import get_meter @@ -219,7 +31,7 @@ def response_hook(span: Span, environ: WSGIEnvironment, status: str, response_he from opentelemetry.semconv.metrics import MetricInstruments from opentelemetry.semconv.trace import SpanAttributes from opentelemetry.trace.status import Status, StatusCode -from azure.monitor.opentelemetry.vendor.opentelemetry.util.http import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.util.http import ( OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS, OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST, OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE, @@ -583,7 +395,7 @@ def _end_span_after_iterating(iterable, span, token): context.detach(token) -# TODO: inherit from opentelemetry.instrumentation.propagators.Setter +# TODO: inherit from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.propagators.Setter class ResponsePropagationSetter: diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/wsgi/package.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/wsgi/package.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/wsgi/package.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/wsgi/package.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/util/http/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/wsgi/version.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/util/http/version.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/wsgi/version.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/util/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/util/__init__.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/util/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/util/__init__.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/util/http/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/util/http/__init__.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/util/http/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/util/http/__init__.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/util/http/httplib.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/util/http/httplib.py similarity index 95% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/util/http/httplib.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/util/http/httplib.py index de95a0aa..0ccec5f9 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/util/http/httplib.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/util/http/httplib.py @@ -27,8 +27,12 @@ import wrapt from opentelemetry import context -from opentelemetry.instrumentation.instrumentor import BaseInstrumentor -from opentelemetry.instrumentation.utils import unwrap +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.instrumentor import ( + BaseInstrumentor, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.utils import ( + unwrap, +) from opentelemetry.semconv.trace import SpanAttributes from opentelemetry.trace.span import Span diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/util/http/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/util/http/version.py similarity index 95% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/util/http/version.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/util/http/version.py index eb62a67e..88e9292a 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/util/http/version.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/util/http/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.39b0.dev" +__version__ = "0.38b0" diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/__init__.py deleted file mode 100644 index b5f052c7..00000000 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/__init__.py +++ /dev/null @@ -1,387 +0,0 @@ -# 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. -""" - -Instrument `django`_ to trace Django applications. - -.. _django: https://pypi.org/project/django/ - -SQLCOMMENTER -***************************************** -You can optionally configure Django instrumentation to enable sqlcommenter which enriches -the query with contextual information. - -Usage ------ - -.. code:: python - - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.django import DjangoInstrumentor - - DjangoInstrumentor().instrument(is_sql_commentor_enabled=True) - - -For example, -:: - - Invoking Users().objects.all() will lead to sql query "select * from auth_users" but when SQLCommenter is enabled - the query will get appended with some configurable tags like "select * from auth_users /*metrics=value*/;" - - -SQLCommenter Configurations -*************************** -We can configure the tags to be appended to the sqlquery log by adding below variables to the settings.py - -SQLCOMMENTER_WITH_FRAMEWORK = True(Default) or False - -For example, -:: -Enabling this flag will add django framework and it's version which is /*framework='django%3A2.2.3*/ - -SQLCOMMENTER_WITH_CONTROLLER = True(Default) or False - -For example, -:: -Enabling this flag will add controller name that handles the request /*controller='index'*/ - -SQLCOMMENTER_WITH_ROUTE = True(Default) or False - -For example, -:: -Enabling this flag will add url path that handles the request /*route='polls/'*/ - -SQLCOMMENTER_WITH_APP_NAME = True(Default) or False - -For example, -:: -Enabling this flag will add app name that handles the request /*app_name='polls'*/ - -SQLCOMMENTER_WITH_OPENTELEMETRY = True(Default) or False - -For example, -:: -Enabling this flag will add opentelemetry traceparent /*traceparent='00-fd720cffceba94bbf75940ff3caaf3cc-4fd1a2bdacf56388-01'*/ - -SQLCOMMENTER_WITH_DB_DRIVER = True(Default) or False - -For example, -:: -Enabling this flag will add name of the db driver /*db_driver='django.db.backends.postgresql'*/ - -Usage ------ - -.. code:: python - - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.django import DjangoInstrumentor - - DjangoInstrumentor().instrument() - - -Configuration -------------- - -Exclude lists -************* -To exclude certain URLs from tracking, set the environment variable ``OTEL_PYTHON_DJANGO_EXCLUDED_URLS`` -(or ``OTEL_PYTHON_EXCLUDED_URLS`` to cover all instrumentations) to a string of comma delimited regexes that match the -URLs. - -For example, - -:: - - export OTEL_PYTHON_DJANGO_EXCLUDED_URLS="client/.*/info,healthcheck" - -will exclude requests such as ``https://site/client/123/info`` and ``https://site/xyz/healthcheck``. - -Request attributes -******************** -To extract attributes from Django's request object and use them as span attributes, set the environment variable -``OTEL_PYTHON_DJANGO_TRACED_REQUEST_ATTRS`` to a comma delimited list of request attribute names. - -For example, - -:: - - export OTEL_PYTHON_DJANGO_TRACED_REQUEST_ATTRS='path_info,content_type' - -will extract the ``path_info`` and ``content_type`` attributes from every traced request and add them as span attributes. - -Django Request object reference: https://docs.djangoproject.com/en/3.1/ref/request-response/#attributes - -Request and Response hooks -*************************** -This instrumentation supports request and response hooks. These are functions that get called -right after a span is created for a request and right before the span is finished for the response. -The hooks can be configured as follows: - -.. code:: python - - def request_hook(span, request): - pass - - def response_hook(span, request, response): - pass - - DjangoInstrumentor().instrument(request_hook=request_hook, response_hook=response_hook) - -Django Request object: https://docs.djangoproject.com/en/3.1/ref/request-response/#httprequest-objects -Django Response object: https://docs.djangoproject.com/en/3.1/ref/request-response/#httpresponse-objects - -Capture HTTP request and response headers -***************************************** -You can configure the agent to capture specified HTTP headers as span attributes, according to the -`semantic convention `_. - -Request headers -*************** -To capture HTTP request headers as span attributes, set the environment variable -``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST`` to a comma delimited list of HTTP header names. - -For example, -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST="content-type,custom_request_header" - -will extract ``content-type`` and ``custom_request_header`` from the request headers and add them as span attributes. - -Request header names in Django are case-insensitive. So, giving the header name as ``CUStom-Header`` in the environment -variable will capture the header named ``custom-header``. - -Regular expressions may also be used to match multiple headers that correspond to the given pattern. For example: -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST="Accept.*,X-.*" - -Would match all request headers that start with ``Accept`` and ``X-``. - -To capture all request headers, set ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST`` to ``".*"``. -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST=".*" - -The name of the added span attribute will follow the format ``http.request.header.`` where ```` -is the normalized HTTP header name (lowercase, with ``-`` replaced by ``_``). The value of the attribute will be a -single item list containing all the header values. - -For example: -``http.request.header.custom_request_header = [","]`` - -Response headers -**************** -To capture HTTP response headers as span attributes, set the environment variable -``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE`` to a comma delimited list of HTTP header names. - -For example, -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE="content-type,custom_response_header" - -will extract ``content-type`` and ``custom_response_header`` from the response headers and add them as span attributes. - -Response header names in Django are case-insensitive. So, giving the header name as ``CUStom-Header`` in the environment -variable will capture the header named ``custom-header``. - -Regular expressions may also be used to match multiple headers that correspond to the given pattern. For example: -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE="Content.*,X-.*" - -Would match all response headers that start with ``Content`` and ``X-``. - -To capture all response headers, set ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE`` to ``".*"``. -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE=".*" - -The name of the added span attribute will follow the format ``http.response.header.`` where ```` -is the normalized HTTP header name (lowercase, with ``-`` replaced by ``_``). The value of the attribute will be a -single item list containing all the header values. - -For example: -``http.response.header.custom_response_header = [","]`` - -Sanitizing headers -****************** -In order to prevent storing sensitive data such as personally identifiable information (PII), session keys, passwords, -etc, set the environment variable ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS`` -to a comma delimited list of HTTP header names to be sanitized. Regexes may be used, and all header names will be -matched in a case-insensitive manner. - -For example, -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS=".*session.*,set-cookie" - -will replace the value of headers such as ``session-id`` and ``set-cookie`` with ``[REDACTED]`` in the span. - -Note: - The environment variable names used to capture HTTP headers are still experimental, and thus are subject to change. - -API ---- - -""" - -from logging import getLogger -from os import environ -from typing import Collection - -from django import VERSION as django_version -from django.conf import settings -from django.core.exceptions import ImproperlyConfigured - -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.django.environment_variables import ( - OTEL_PYTHON_DJANGO_INSTRUMENT, -) -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.django.middleware.otel_middleware import ( - _DjangoMiddleware, -) -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.django.package import ( - _instruments, -) -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.django.version import ( - __version__, -) -from opentelemetry.instrumentation.instrumentor import BaseInstrumentor -from opentelemetry.metrics import get_meter -from opentelemetry.semconv.metrics import MetricInstruments -from opentelemetry.trace import get_tracer -from azure.monitor.opentelemetry.vendor.opentelemetry.util.http import ( - get_excluded_urls, - parse_excluded_urls, -) - -DJANGO_2_0 = django_version >= (2, 0) - -_excluded_urls_from_env = get_excluded_urls("DJANGO") -_logger = getLogger(__name__) - - -def _get_django_middleware_setting() -> str: - # In Django versions 1.x, setting MIDDLEWARE_CLASSES can be used as a legacy - # alternative to MIDDLEWARE. This is the case when `settings.MIDDLEWARE` has - # its default value (`None`). - if not DJANGO_2_0 and getattr(settings, "MIDDLEWARE", None) is None: - return "MIDDLEWARE_CLASSES" - return "MIDDLEWARE" - - -class DjangoInstrumentor(BaseInstrumentor): - """An instrumentor for Django - - See `BaseInstrumentor` - """ - - _opentelemetry_middleware = ".".join( - [_DjangoMiddleware.__module__, _DjangoMiddleware.__qualname__] - ) - - _sql_commenter_middleware = "azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.django.middleware.sqlcommenter_middleware.SqlCommenter" - - def instrumentation_dependencies(self) -> Collection[str]: - return _instruments - - def _instrument(self, **kwargs): - # FIXME this is probably a pattern that will show up in the rest of the - # ext. Find a better way of implementing this. - if environ.get(OTEL_PYTHON_DJANGO_INSTRUMENT) == "False": - return - - tracer_provider = kwargs.get("tracer_provider") - meter_provider = kwargs.get("meter_provider") - _excluded_urls = kwargs.get("excluded_urls") - tracer = get_tracer( - __name__, - __version__, - tracer_provider=tracer_provider, - ) - meter = get_meter(__name__, __version__, meter_provider=meter_provider) - _DjangoMiddleware._tracer = tracer - _DjangoMiddleware._meter = meter - _DjangoMiddleware._excluded_urls = ( - _excluded_urls_from_env - if _excluded_urls is None - else parse_excluded_urls(_excluded_urls) - ) - _DjangoMiddleware._otel_request_hook = kwargs.pop("request_hook", None) - _DjangoMiddleware._otel_response_hook = kwargs.pop( - "response_hook", None - ) - _DjangoMiddleware._duration_histogram = meter.create_histogram( - name=MetricInstruments.HTTP_SERVER_DURATION, - unit="ms", - description="measures the duration of the inbound http request", - ) - _DjangoMiddleware._active_request_counter = meter.create_up_down_counter( - name=MetricInstruments.HTTP_SERVER_ACTIVE_REQUESTS, - unit="requests", - description="measures the number of concurrent HTTP requests those are currently in flight", - ) - # This can not be solved, but is an inherent problem of this approach: - # the order of middleware entries matters, and here you have no control - # on that: - # https://docs.djangoproject.com/en/3.0/topics/http/middleware/#activating-middleware - # https://docs.djangoproject.com/en/3.0/ref/middleware/#middleware-ordering - - _middleware_setting = _get_django_middleware_setting() - settings_middleware = [] - try: - settings_middleware = getattr(settings, _middleware_setting, []) - except ImproperlyConfigured as exception: - _logger.debug( - "DJANGO_SETTINGS_MODULE environment variable not configured. Defaulting to empty settings: %s", - exception, - ) - settings.configure() - settings_middleware = getattr(settings, _middleware_setting, []) - except ModuleNotFoundError as exception: - _logger.debug( - "DJANGO_SETTINGS_MODULE points to a non-existent module. Defaulting to empty settings: %s", - exception, - ) - settings.configure() - settings_middleware = getattr(settings, _middleware_setting, []) - - # Django allows to specify middlewares as a tuple, so we convert this tuple to a - # list, otherwise we wouldn't be able to call append/remove - if isinstance(settings_middleware, tuple): - settings_middleware = list(settings_middleware) - - is_sql_commentor_enabled = kwargs.pop("is_sql_commentor_enabled", None) - - if is_sql_commentor_enabled: - settings_middleware.insert(0, self._sql_commenter_middleware) - - settings_middleware.insert(0, self._opentelemetry_middleware) - - setattr(settings, _middleware_setting, settings_middleware) - - def _uninstrument(self, **kwargs): - _middleware_setting = _get_django_middleware_setting() - settings_middleware = getattr(settings, _middleware_setting, None) - - # FIXME This is starting to smell like trouble. We have 2 mechanisms - # that may make this condition be True, one implemented in - # BaseInstrumentor and another one implemented in _instrument. Both - # stop _instrument from running and thus, settings_middleware not being - # set. - if settings_middleware is None or ( - self._opentelemetry_middleware not in settings_middleware - ): - return - - settings_middleware.remove(self._opentelemetry_middleware) - setattr(settings, _middleware_setting, settings_middleware) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/fastapi/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/fastapi/__init__.py deleted file mode 100644 index 3406bab7..00000000 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/fastapi/__init__.py +++ /dev/null @@ -1,350 +0,0 @@ -# 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. - -""" -Usage ------ - -.. code-block:: python - - import fastapi - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.fastapi import FastAPIInstrumentor - - app = fastapi.FastAPI() - - @app.get("/foobar") - async def foobar(): - return {"message": "hello world"} - - FastAPIInstrumentor.instrument_app(app) - -Configuration -------------- - -Exclude lists -************* -To exclude certain URLs from tracking, set the environment variable ``OTEL_PYTHON_FASTAPI_EXCLUDED_URLS`` -(or ``OTEL_PYTHON_EXCLUDED_URLS`` to cover all instrumentations) to a string of comma delimited regexes that match the -URLs. - -For example, - -:: - - export OTEL_PYTHON_FASTAPI_EXCLUDED_URLS="client/.*/info,healthcheck" - -will exclude requests such as ``https://site/client/123/info`` and ``https://site/xyz/healthcheck``. - -You can also pass comma delimited regexes directly to the ``instrument_app`` method: - -.. code-block:: python - - FastAPIInstrumentor.instrument_app(app, excluded_urls="client/.*/info,healthcheck") - -Request/Response hooks -********************** - -This instrumentation supports request and response hooks. These are functions that get called -right after a span is created for a request and right before the span is finished for the response. - -- The server request hook is passed a server span and ASGI scope object for every incoming request. -- The client request hook is called with the internal span and an ASGI scope when the method ``receive`` is called. -- The client response hook is called with the internal span and an ASGI event when the method ``send`` is called. - -.. code-block:: python - - def server_request_hook(span: Span, scope: dict): - if span and span.is_recording(): - span.set_attribute("custom_user_attribute_from_request_hook", "some-value") - - def client_request_hook(span: Span, scope: dict): - if span and span.is_recording(): - span.set_attribute("custom_user_attribute_from_client_request_hook", "some-value") - - def client_response_hook(span: Span, message: dict): - if span and span.is_recording(): - span.set_attribute("custom_user_attribute_from_response_hook", "some-value") - - FastAPIInstrumentor().instrument(server_request_hook=server_request_hook, client_request_hook=client_request_hook, client_response_hook=client_response_hook) - -Capture HTTP request and response headers -***************************************** -You can configure the agent to capture specified HTTP headers as span attributes, according to the -`semantic convention `_. - -Request headers -*************** -To capture HTTP request headers as span attributes, set the environment variable -``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST`` to a comma delimited list of HTTP header names. - -For example, -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST="content-type,custom_request_header" - -will extract ``content-type`` and ``custom_request_header`` from the request headers and add them as span attributes. - -Request header names in FastAPI are case-insensitive. So, giving the header name as ``CUStom-Header`` in the environment -variable will capture the header named ``custom-header``. - -Regular expressions may also be used to match multiple headers that correspond to the given pattern. For example: -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST="Accept.*,X-.*" - -Would match all request headers that start with ``Accept`` and ``X-``. - -To capture all request headers, set ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST`` to ``".*"``. -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST=".*" - -The name of the added span attribute will follow the format ``http.request.header.`` where ```` -is the normalized HTTP header name (lowercase, with ``-`` replaced by ``_``). The value of the attribute will be a -single item list containing all the header values. - -For example: -``http.request.header.custom_request_header = [","]`` - -Response headers -**************** -To capture HTTP response headers as span attributes, set the environment variable -``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE`` to a comma delimited list of HTTP header names. - -For example, -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE="content-type,custom_response_header" - -will extract ``content-type`` and ``custom_response_header`` from the response headers and add them as span attributes. - -Response header names in FastAPI are case-insensitive. So, giving the header name as ``CUStom-Header`` in the environment -variable will capture the header named ``custom-header``. - -Regular expressions may also be used to match multiple headers that correspond to the given pattern. For example: -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE="Content.*,X-.*" - -Would match all response headers that start with ``Content`` and ``X-``. - -To capture all response headers, set ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE`` to ``".*"``. -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE=".*" - -The name of the added span attribute will follow the format ``http.response.header.`` where ```` -is the normalized HTTP header name (lowercase, with ``-`` replaced by ``_``). The value of the attribute will be a -single item list containing all the header values. - -For example: -``http.response.header.custom_response_header = [","]`` - -Sanitizing headers -****************** -In order to prevent storing sensitive data such as personally identifiable information (PII), session keys, passwords, -etc, set the environment variable ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS`` -to a comma delimited list of HTTP header names to be sanitized. Regexes may be used, and all header names will be -matched in a case-insensitive manner. - -For example, -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS=".*session.*,set-cookie" - -will replace the value of headers such as ``session-id`` and ``set-cookie`` with ``[REDACTED]`` in the span. - -Note: - The environment variable names used to capture HTTP headers are still experimental, and thus are subject to change. - -API ---- -""" -import logging -import typing -from typing import Collection - -import fastapi -from starlette.routing import Match - -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.asgi import ( - OpenTelemetryMiddleware, -) -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.fastapi.package import ( - _instruments, -) -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.fastapi.version import ( - __version__, -) -from opentelemetry.instrumentation.instrumentor import BaseInstrumentor -from opentelemetry.metrics import get_meter -from opentelemetry.semconv.trace import SpanAttributes -from opentelemetry.trace import Span -from azure.monitor.opentelemetry.vendor.opentelemetry.util.http import ( - get_excluded_urls, - parse_excluded_urls, -) - -_excluded_urls_from_env = get_excluded_urls("FASTAPI") -_logger = logging.getLogger(__name__) - -_ServerRequestHookT = typing.Optional[typing.Callable[[Span, dict], None]] -_ClientRequestHookT = typing.Optional[typing.Callable[[Span, dict], None]] -_ClientResponseHookT = typing.Optional[typing.Callable[[Span, dict], None]] - - -class FastAPIInstrumentor(BaseInstrumentor): - """An instrumentor for FastAPI - - See `BaseInstrumentor` - """ - - _original_fastapi = None - - @staticmethod - def instrument_app( - app: fastapi.FastAPI, - server_request_hook: _ServerRequestHookT = None, - client_request_hook: _ClientRequestHookT = None, - client_response_hook: _ClientResponseHookT = None, - tracer_provider=None, - meter_provider=None, - excluded_urls=None, - ): - """Instrument an uninstrumented FastAPI application.""" - if not hasattr(app, "_is_instrumented_by_opentelemetry"): - app._is_instrumented_by_opentelemetry = False - - if not getattr(app, "_is_instrumented_by_opentelemetry", False): - if excluded_urls is None: - excluded_urls = _excluded_urls_from_env - else: - excluded_urls = parse_excluded_urls(excluded_urls) - meter = get_meter(__name__, __version__, meter_provider) - - app.add_middleware( - OpenTelemetryMiddleware, - excluded_urls=excluded_urls, - default_span_details=_get_route_details, - server_request_hook=server_request_hook, - client_request_hook=client_request_hook, - client_response_hook=client_response_hook, - tracer_provider=tracer_provider, - meter=meter, - ) - app._is_instrumented_by_opentelemetry = True - if app not in _InstrumentedFastAPI._instrumented_fastapi_apps: - _InstrumentedFastAPI._instrumented_fastapi_apps.add(app) - else: - _logger.warning( - "Attempting to instrument FastAPI app while already instrumented" - ) - - @staticmethod - def uninstrument_app(app: fastapi.FastAPI): - app.user_middleware = [ - x - for x in app.user_middleware - if x.cls is not OpenTelemetryMiddleware - ] - app.middleware_stack = app.build_middleware_stack() - app._is_instrumented_by_opentelemetry = False - - def instrumentation_dependencies(self) -> Collection[str]: - return _instruments - - def _instrument(self, **kwargs): - self._original_fastapi = fastapi.FastAPI - _InstrumentedFastAPI._tracer_provider = kwargs.get("tracer_provider") - _InstrumentedFastAPI._server_request_hook = kwargs.get( - "server_request_hook" - ) - _InstrumentedFastAPI._client_request_hook = kwargs.get( - "client_request_hook" - ) - _InstrumentedFastAPI._client_response_hook = kwargs.get( - "client_response_hook" - ) - _excluded_urls = kwargs.get("excluded_urls") - _InstrumentedFastAPI._excluded_urls = ( - _excluded_urls_from_env - if _excluded_urls is None - else parse_excluded_urls(_excluded_urls) - ) - _InstrumentedFastAPI._meter_provider = kwargs.get("meter_provider") - fastapi.FastAPI = _InstrumentedFastAPI - - def _uninstrument(self, **kwargs): - for instance in _InstrumentedFastAPI._instrumented_fastapi_apps: - self.uninstrument_app(instance) - _InstrumentedFastAPI._instrumented_fastapi_apps.clear() - fastapi.FastAPI = self._original_fastapi - - -class _InstrumentedFastAPI(fastapi.FastAPI): - _tracer_provider = None - _meter_provider = None - _excluded_urls = None - _server_request_hook: _ServerRequestHookT = None - _client_request_hook: _ClientRequestHookT = None - _client_response_hook: _ClientResponseHookT = None - _instrumented_fastapi_apps = set() - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - meter = get_meter( - __name__, __version__, _InstrumentedFastAPI._meter_provider - ) - self.add_middleware( - OpenTelemetryMiddleware, - excluded_urls=_InstrumentedFastAPI._excluded_urls, - default_span_details=_get_route_details, - server_request_hook=_InstrumentedFastAPI._server_request_hook, - client_request_hook=_InstrumentedFastAPI._client_request_hook, - client_response_hook=_InstrumentedFastAPI._client_response_hook, - tracer_provider=_InstrumentedFastAPI._tracer_provider, - meter=meter, - ) - self._is_instrumented_by_opentelemetry = True - _InstrumentedFastAPI._instrumented_fastapi_apps.add(self) - - def __del__(self): - if self in _InstrumentedFastAPI._instrumented_fastapi_apps: - _InstrumentedFastAPI._instrumented_fastapi_apps.remove(self) - - -def _get_route_details(scope): - """Callback to retrieve the fastapi route being served. - - TODO: there is currently no way to retrieve http.route from - a starlette application from scope. - - See: https://github.com/encode/starlette/pull/804 - """ - app = scope["app"] - route = None - for starlette_route in app.routes: - match, _ = starlette_route.matches(scope) - if match == Match.FULL: - route = starlette_route.path - break - if match == Match.PARTIAL: - route = starlette_route.path - # method only exists for http, if websocket - # leave it blank. - span_name = route or scope.get("method", "") - attributes = {} - if route: - attributes[SpanAttributes.HTTP_ROUTE] = route - return span_name, attributes diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/util/http/httplib.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/util/http/httplib.py deleted file mode 100644 index de95a0aa..00000000 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/util/http/httplib.py +++ /dev/null @@ -1,179 +0,0 @@ -# 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. -""" - -import contextlib -import http.client -import logging -import socket # pylint:disable=unused-import # Used for typing -import typing -from typing import Collection - -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__) - - -class HttpClientInstrumentor(BaseInstrumentor): - def instrumentation_dependencies(self) -> Collection[str]: - return () # This instruments http.client from stdlib; no extra deps. - - def _instrument(self, **kwargs): - """Instruments the http.client module (not creating spans on its own)""" - _instrument() - - def _uninstrument(self, **kwargs): - _uninstrument() - - -def _remove_nonrecording(spanlist: typing.List[Span]): - 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=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 = state.get("need_ip") # type: typing.List[Span] - if not spanlist: - return True - - # Remove all non-recording spans from the list. - if not _remove_nonrecording(spanlist): - return True - - sock = "" - try: - sock = conn.sock # type: typing.Optional[socket.socket] - 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, instance: http.client.HTTPConnection, args, kwargs -): - result = wrapped(*args, **kwargs) - trysetip(instance, loglevel=logging.WARNING) - return result - - -def instrument_connect(module, name="connect"): - """Instrument additional connect() methods, e.g. for derived classes.""" - - wrapt.wrap_function_wrapper( - module, - name, - _instrumented_connect, - ) - - -def _instrument(): - def instrumented_send( - wrapped, instance: http.client.HTTPConnection, args, kwargs - ): - 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() - - -def _getstate() -> typing.Optional[dict]: - return 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"] # type: typing.List[Span] - 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/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/util/http/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/util/http/__init__.py deleted file mode 100644 index f3d39ab0..00000000 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/util/http/__init__.py +++ /dev/null @@ -1,213 +0,0 @@ -# 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 os import environ -from re import IGNORECASE as RE_IGNORECASE -from re import compile as re_compile -from re import search -from typing import Iterable, List -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" -) - -# 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: dict, header_regexes: list, normalize_function: callable - ) -> dict: - values = {} - - if header_regexes: - header_regexes_compiled = re_compile( - "|".join("^" + i + "$" for i in header_regexes), - RE_IGNORECASE, - ) - - for header_name in list( - filter( - header_regexes_compiled.match, - headers.keys(), - ) - ): - header_values = headers.get(header_name) - if header_values: - key = normalize_function(header_name.lower()) - values[key] = [ - self.sanitize_header_value( - header=header_name, value=header_values - ) - ] - - return values - - -_root = r"OTEL_PYTHON_{}" - - -def get_traced_request_attrs(instrumentation): - traced_request_attrs = environ.get( - _root.format(f"{instrumentation}_TRACED_REQUEST_ATTRS"), [] - ) - - if traced_request_attrs: - traced_request_attrs = [ - traced_request_attr.strip() - for traced_request_attr in traced_request_attrs.split(",") - ] - - return traced_request_attrs - - -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 = ( - (":".join(((parsed_url.hostname or ""), str(parsed_url.port)))) - if parsed_url.port - else (parsed_url.hostname or "") - ) - 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}" - - -def get_custom_headers(env_var: str) -> List[str]: - custom_headers = environ.get(env_var, []) - if custom_headers: - custom_headers = [ - custom_headers.strip() - for custom_headers in custom_headers.split(",") - ] - return custom_headers - - -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 diff --git a/azure-monitor-opentelemetry/setup.py b/azure-monitor-opentelemetry/setup.py index 9844da4f..1f76d22c 100644 --- a/azure-monitor-opentelemetry/setup.py +++ b/azure-monitor-opentelemetry/setup.py @@ -85,14 +85,6 @@ python_requires=">=3.7", install_requires=[ "azure-monitor-opentelemetry-exporter>=1.0.0b13", - "opentelemetry-instrumentation~=0.38b0", - # "opentelemetry-instrumentation-django~=0.38b0", - # "opentelemetry-instrumentation-fastapi~=0.38b0", - # "opentelemetry-instrumentation-flask~=0.38b0", - # "opentelemetry-instrumentation-psycopg2~=0.38b0", - # "opentelemetry-instrumentation-requests~=0.38b0", - # "opentelemetry-instrumentation-urllib~=0.38b0", - # "opentelemetry-instrumentation-urllib3~=0.38b0", "opentelemetry-api==1.17.0", "opentelemetry-sdk==1.17.0", "wrapt >= 1.0.0, < 2.0.0", @@ -105,13 +97,13 @@ "azure_monitor_opentelemetry_configurator = azure.monitor.opentelemetry.autoinstrumentation._configurator:AzureMonitorConfigurator" ], "azure_monitor_opentelemetry_instrumentor": [ - "django = azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.django:DjangoInstrumentor", - "fastapi = azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.fastapi:FastAPIInstrumentor", - "flask = azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.flask:FlaskInstrumentor", - "psycopg2 = azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.psycopg2:Psycopg2Instrumentor", - "requests = azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.requests:RequestsInstrumentor", - "urllib = azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.urllib:URLLibInstrumentor", - "urllib3 = azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.urllib3:URLLib3Instrumentor", + "django = azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.django:DjangoInstrumentor", + "fastapi = azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.fastapi:FastAPIInstrumentor", + "flask = azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.flask:FlaskInstrumentor", + "psycopg2 = azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.psycopg2:Psycopg2Instrumentor", + "requests = azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.requests:RequestsInstrumentor", + "urllib = azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.urllib:URLLibInstrumentor", + "urllib3 = azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.urllib3:URLLib3Instrumentor", ], }, ) diff --git a/azure-monitor-opentelemetry/tests/configuration/test_configure.py b/azure-monitor-opentelemetry/tests/configuration/test_configure.py index b2057098..50c7731e 100644 --- a/azure-monitor-opentelemetry/tests/configuration/test_configure.py +++ b/azure-monitor-opentelemetry/tests/configuration/test_configure.py @@ -16,7 +16,7 @@ from unittest.mock import Mock, patch from azure.monitor.opentelemetry._configure import ( - _SUPPORTED_INSTRUMENTED_LIBRARIES_TO_INSTRUMENTS_MAP, + _SUPPORTED_INSTRUMENTED_LIBRARIES_DEPENDENCIES_MAP, _setup_instrumentations, _setup_logging, _setup_metrics, @@ -336,13 +336,13 @@ def test_setup_instrumentations_lib_not_supported( instr_class_mock.return_value = instrumentor_mock ep_mock.name = "test_instr" ep2_mock.name = list( - _SUPPORTED_INSTRUMENTED_LIBRARIES_TO_INSTRUMENTS_MAP.keys() + _SUPPORTED_INSTRUMENTED_LIBRARIES_DEPENDENCIES_MAP.keys() )[0] ep2_mock.load.return_value = instr_class_mock dep_mock.return_value = None _setup_instrumentations() dep_mock.assert_called_with( - _SUPPORTED_INSTRUMENTED_LIBRARIES_TO_INSTRUMENTS_MAP[ep2_mock.name] + _SUPPORTED_INSTRUMENTED_LIBRARIES_DEPENDENCIES_MAP[ep2_mock.name] ) ep_mock.load.assert_not_called() ep2_mock.load.assert_called_once() @@ -363,13 +363,13 @@ def test_setup_instrumentations_conflict( instr_class_mock = Mock() instr_class_mock.return_value = instrumentor_mock ep_mock.name = list( - _SUPPORTED_INSTRUMENTED_LIBRARIES_TO_INSTRUMENTS_MAP.keys() + _SUPPORTED_INSTRUMENTED_LIBRARIES_DEPENDENCIES_MAP.keys() )[0] ep_mock.load.return_value = instr_class_mock dep_mock.return_value = True _setup_instrumentations() dep_mock.assert_called_with( - _SUPPORTED_INSTRUMENTED_LIBRARIES_TO_INSTRUMENTS_MAP[ep_mock.name] + _SUPPORTED_INSTRUMENTED_LIBRARIES_DEPENDENCIES_MAP[ep_mock.name] ) ep_mock.load.assert_not_called() instrumentor_mock.instrument.assert_not_called() @@ -390,13 +390,13 @@ def test_setup_instrumentations_exception( instr_class_mock = Mock() instr_class_mock.return_value = instrumentor_mock ep_mock.name = list( - _SUPPORTED_INSTRUMENTED_LIBRARIES_TO_INSTRUMENTS_MAP.keys() + _SUPPORTED_INSTRUMENTED_LIBRARIES_DEPENDENCIES_MAP.keys() )[0] ep_mock.load.side_effect = Exception() dep_mock.return_value = None _setup_instrumentations() dep_mock.assert_called_with( - _SUPPORTED_INSTRUMENTED_LIBRARIES_TO_INSTRUMENTS_MAP[ep_mock.name] + _SUPPORTED_INSTRUMENTED_LIBRARIES_DEPENDENCIES_MAP[ep_mock.name] ) ep_mock.load.assert_called_once() instrumentor_mock.instrument.assert_not_called() diff --git a/azure-monitor-opentelemetry/tests/instrumentation/test_django.py b/azure-monitor-opentelemetry/tests/instrumentation/test_django.py index 8033853d..14c30749 100644 --- a/azure-monitor-opentelemetry/tests/instrumentation/test_django.py +++ b/azure-monitor-opentelemetry/tests/instrumentation/test_django.py @@ -6,7 +6,7 @@ import unittest -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.django import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.django import ( DjangoInstrumentor, ) diff --git a/azure-monitor-opentelemetry/tests/instrumentation/test_fastapi.py b/azure-monitor-opentelemetry/tests/instrumentation/test_fastapi.py index e2b015b5..c3d7f66b 100644 --- a/azure-monitor-opentelemetry/tests/instrumentation/test_fastapi.py +++ b/azure-monitor-opentelemetry/tests/instrumentation/test_fastapi.py @@ -6,7 +6,7 @@ import unittest -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.fastapi import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.fastapi import ( FastAPIInstrumentor, ) diff --git a/azure-monitor-opentelemetry/tests/instrumentation/test_flask.py b/azure-monitor-opentelemetry/tests/instrumentation/test_flask.py index c2e04220..a20a23c7 100644 --- a/azure-monitor-opentelemetry/tests/instrumentation/test_flask.py +++ b/azure-monitor-opentelemetry/tests/instrumentation/test_flask.py @@ -6,7 +6,7 @@ import unittest -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.flask import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.flask import ( FlaskInstrumentor, ) diff --git a/azure-monitor-opentelemetry/tests/instrumentation/test_psycopg2.py b/azure-monitor-opentelemetry/tests/instrumentation/test_psycopg2.py index 0d74879a..91a4968b 100644 --- a/azure-monitor-opentelemetry/tests/instrumentation/test_psycopg2.py +++ b/azure-monitor-opentelemetry/tests/instrumentation/test_psycopg2.py @@ -6,7 +6,7 @@ import unittest -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.psycopg2 import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.psycopg2 import ( Psycopg2Instrumentor, ) diff --git a/azure-monitor-opentelemetry/tests/instrumentation/test_requests.py b/azure-monitor-opentelemetry/tests/instrumentation/test_requests.py index 7aaab597..9db55df2 100644 --- a/azure-monitor-opentelemetry/tests/instrumentation/test_requests.py +++ b/azure-monitor-opentelemetry/tests/instrumentation/test_requests.py @@ -6,7 +6,7 @@ import unittest -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.requests import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.requests import ( RequestsInstrumentor, ) diff --git a/azure-monitor-opentelemetry/tests/instrumentation/test_urllib.py b/azure-monitor-opentelemetry/tests/instrumentation/test_urllib.py index 56c79ccf..e6419fed 100644 --- a/azure-monitor-opentelemetry/tests/instrumentation/test_urllib.py +++ b/azure-monitor-opentelemetry/tests/instrumentation/test_urllib.py @@ -6,7 +6,7 @@ import unittest -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.urllib import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.urllib import ( URLLibInstrumentor, ) diff --git a/azure-monitor-opentelemetry/tests/instrumentation/test_urllib3.py b/azure-monitor-opentelemetry/tests/instrumentation/test_urllib3.py index 0143ff2e..31b68006 100644 --- a/azure-monitor-opentelemetry/tests/instrumentation/test_urllib3.py +++ b/azure-monitor-opentelemetry/tests/instrumentation/test_urllib3.py @@ -6,7 +6,7 @@ import unittest -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.urllib3 import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.urllib3 import ( URLLib3Instrumentor, ) From b5c1ef9f8172da6445a219f608cb5243f3fa0149 Mon Sep 17 00:00:00 2001 From: jerevoss Date: Wed, 31 May 2023 14:36:57 -0700 Subject: [PATCH 05/10] Switched to _vendor, vendored instrumentation, removed comments+docstrings --- .../azure/monitor/opentelemetry/_configure.py | 17 +- .../{vendor => _vendor}/__init__.py | 0 .../opentelemetry/__init__.py | 0 .../opentelemetry/instrumentation/__init__.py | 0 .../instrumentation/asgi/__init__.py | 183 +-------- .../instrumentation/asgi/package.py | 0 .../instrumentation/asgi/version.py | 0 .../auto_instrumentation/__init__.py | 117 ++++++ .../auto_instrumentation/sitecustomize.py | 134 ++++++ .../instrumentation/bootstrap.py | 163 ++++++++ .../instrumentation/bootstrap_gen.py | 175 ++++++++ .../instrumentation/dbapi/__init__.py | 32 +- .../instrumentation/dbapi/package.py | 0 .../instrumentation/dbapi/version.py | 0 .../instrumentation/dependencies.py | 62 +++ .../opentelemetry/instrumentation/distro.py | 73 ++++ .../instrumentation/django/__init__.py | 167 ++++++++ .../django/environment_variables.py | 0 .../django/middleware/__init__.py | 0 .../django/middleware/otel_middleware.py | 26 +- .../middleware/sqlcommenter_middleware.py | 8 +- .../instrumentation/django/package.py | 0 .../instrumentation/django/version.py | 0 .../instrumentation/environment_variables.py | 18 + .../instrumentation/fastapi/__init__.py | 194 +++++++++ .../instrumentation/fastapi/package.py | 0 .../instrumentation/fastapi/version.py | 0 .../instrumentation/flask/__init__.py | 240 +---------- .../instrumentation/flask/package.py | 0 .../instrumentation/flask/version.py | 0 .../instrumentation/instrumentor.py | 131 ++++++ .../instrumentation/propagators.py | 124 ++++++ .../instrumentation/psycopg2/__init__.py | 98 +---- .../instrumentation/psycopg2/package.py | 0 .../instrumentation/psycopg2/version.py | 0 .../opentelemetry/instrumentation/py.typed | 0 .../instrumentation/requests/__init__.py | 49 +-- .../instrumentation/requests/package.py | 0 .../instrumentation/requests/version.py | 0 .../instrumentation/sqlcommenter_utils.py | 68 +++ .../instrumentation/urllib/__init__.py | 59 +-- .../instrumentation/urllib/package.py | 0 .../instrumentation/urllib/version.py | 0 .../instrumentation/urllib3/__init__.py | 63 +-- .../instrumentation/urllib3/package.py | 0 .../instrumentation/urllib3/version.py | 0 .../opentelemetry/instrumentation/utils.py | 154 +++++++ .../opentelemetry/instrumentation}/version.py | 0 .../instrumentation/wsgi/__init__.py | 196 +-------- .../instrumentation/wsgi/package.py | 0 .../instrumentation/wsgi}/version.py | 0 .../opentelemetry/util/__init__.py | 0 .../opentelemetry}/util/http/__init__.py | 0 .../opentelemetry/util/http/httplib.py | 8 +- .../opentelemetry}/util/http/version.py | 2 +- .../autoinstrumentation/_distro.py | 2 +- .../instrumentation/django/__init__.py | 387 ------------------ .../instrumentation/fastapi/__init__.py | 350 ---------------- .../instrumentation/util/http/httplib.py | 179 -------- .../opentelemetry/util/http/__init__.py | 213 ---------- .../samples/tracing/manual.py | 2 +- azure-monitor-opentelemetry/setup.py | 22 +- .../tests/configuration/test_configure.py | 14 +- .../tests/instrumentation/test_django.py | 2 +- .../tests/instrumentation/test_fastapi.py | 2 +- .../tests/instrumentation/test_flask.py | 2 +- .../tests/instrumentation/test_psycopg2.py | 2 +- .../tests/instrumentation/test_requests.py | 2 +- .../tests/instrumentation/test_urllib.py | 2 +- .../tests/instrumentation/test_urllib3.py | 2 +- 70 files changed, 1690 insertions(+), 2054 deletions(-) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/__init__.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/__init__.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/__init__.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/asgi/__init__.py (71%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/asgi/package.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/asgi/version.py (100%) create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/auto_instrumentation/__init__.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/auto_instrumentation/sitecustomize.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/bootstrap.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/bootstrap_gen.py rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/dbapi/__init__.py (95%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/dbapi/package.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/dbapi/version.py (100%) create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/dependencies.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/distro.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/__init__.py rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/django/environment_variables.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/django/middleware/__init__.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/django/middleware/otel_middleware.py (92%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/django/middleware/sqlcommenter_middleware.py (94%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/django/package.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/django/version.py (100%) create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/environment_variables.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/fastapi/__init__.py rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/fastapi/package.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/fastapi/version.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/flask/__init__.py (63%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/flask/package.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/flask/version.py (100%) create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/instrumentor.py create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/propagators.py rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/psycopg2/__init__.py (69%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/psycopg2/package.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/psycopg2/version.py (100%) create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/py.typed rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/requests/__init__.py (86%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/requests/package.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/requests/version.py (100%) create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/sqlcommenter_utils.py rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/urllib/__init__.py (83%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/urllib/package.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/urllib/version.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/urllib3/__init__.py (83%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/urllib3/package.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/urllib3/version.py (100%) create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/utils.py rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor/opentelemetry/instrumentation/wsgi => _vendor/opentelemetry/instrumentation}/version.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/wsgi/__init__.py (65%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/instrumentation/wsgi/package.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor/opentelemetry/util/http => _vendor/opentelemetry/instrumentation/wsgi}/version.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/util/__init__.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor/opentelemetry/instrumentation => _vendor/opentelemetry}/util/http/__init__.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor => _vendor}/opentelemetry/util/http/httplib.py (95%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/{vendor/opentelemetry/instrumentation => _vendor/opentelemetry}/util/http/version.py (95%) delete mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/__init__.py delete mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/fastapi/__init__.py delete mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/util/http/httplib.py delete mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/util/http/__init__.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py index e30261f4..979a8254 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py @@ -22,8 +22,12 @@ ) from azure.monitor.opentelemetry.util.configurations import _get_configurations from opentelemetry._logs import get_logger_provider, set_logger_provider -from opentelemetry.instrumentation.dependencies import get_dependency_conflicts -from opentelemetry.instrumentation.instrumentor import BaseInstrumentor +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.dependencies import ( + get_dependency_conflicts, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.instrumentor import ( + BaseInstrumentor, +) from opentelemetry.metrics import set_meter_provider from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler from opentelemetry.sdk._logs.export import BatchLogRecordProcessor @@ -37,7 +41,7 @@ _logger = getLogger(__name__) -_SUPPORTED_INSTRUMENTED_LIBRARIES_TO_INSTRUMENTS_MAP = { +_SUPPORTED_INSTRUMENTED_LIBRARIES_DEPENDENCIES_MAP = { "django": ("django >= 1.10",), "fastapi": ("fastapi ~= 0.58",), "flask": ("flask >= 1.0, < 3.0",), @@ -127,14 +131,11 @@ def _setup_instrumentations(): "azure_monitor_opentelemetry_instrumentor" ): lib_name = entry_point.name - if ( - lib_name - not in _SUPPORTED_INSTRUMENTED_LIBRARIES_TO_INSTRUMENTS_MAP - ): + if lib_name not in _SUPPORTED_INSTRUMENTED_LIBRARIES_DEPENDENCIES_MAP: continue try: # Check if dependent libraries/version are installed - instruments = _SUPPORTED_INSTRUMENTED_LIBRARIES_TO_INSTRUMENTS_MAP[ + instruments = _SUPPORTED_INSTRUMENTED_LIBRARIES_DEPENDENCIES_MAP[ lib_name ] conflict = get_dependency_conflicts(instruments) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/__init__.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/__init__.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/__init__.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/__init__.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/__init__.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/__init__.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/asgi/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/asgi/__init__.py similarity index 71% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/asgi/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/asgi/__init__.py index da78196d..02395303 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/asgi/__init__.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/asgi/__init__.py @@ -13,181 +13,6 @@ # limitations under the License. # pylint: disable=too-many-locals -""" -The opentelemetry-instrumentation-asgi package provides an ASGI middleware that can be used -on any ASGI framework (such as Django-channels / Quart) to track request timing through OpenTelemetry. - -Usage (Quart) -------------- - -.. code-block:: python - - from quart import Quart - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware - - app = Quart(__name__) - app.asgi_app = OpenTelemetryMiddleware(app.asgi_app) - - @app.route("/") - async def hello(): - return "Hello!" - - if __name__ == "__main__": - app.run(debug=True) - - -Usage (Django 3.0) ------------------- - -Modify the application's ``asgi.py`` file as shown below. - -.. code-block:: python - - import os - from django.core.asgi import get_asgi_application - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware - - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'asgi_example.settings') - - application = get_asgi_application() - application = OpenTelemetryMiddleware(application) - - -Usage (Raw ASGI) ----------------- - -.. code-block:: python - - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware - - app = ... # An ASGI application. - app = OpenTelemetryMiddleware(app) - - -Configuration -------------- - -Request/Response hooks -********************** - -This instrumentation supports request and response hooks. These are functions that get called -right after a span is created for a request and right before the span is finished for the response. - -- The server request hook is passed a server span and ASGI scope object for every incoming request. -- The client request hook is called with the internal span and an ASGI scope when the method ``receive`` is called. -- The client response hook is called with the internal span and an ASGI event when the method ``send`` is called. - -For example, - -.. code-block:: python - - def server_request_hook(span: Span, scope: dict): - if span and span.is_recording(): - span.set_attribute("custom_user_attribute_from_request_hook", "some-value") - - def client_request_hook(span: Span, scope: dict): - if span and span.is_recording(): - span.set_attribute("custom_user_attribute_from_client_request_hook", "some-value") - - def client_response_hook(span: Span, message: dict): - if span and span.is_recording(): - span.set_attribute("custom_user_attribute_from_response_hook", "some-value") - - OpenTelemetryMiddleware().(application, server_request_hook=server_request_hook, client_request_hook=client_request_hook, client_response_hook=client_response_hook) - -Capture HTTP request and response headers -***************************************** -You can configure the agent to capture specified HTTP headers as span attributes, according to the -`semantic convention `_. - -Request headers -*************** -To capture HTTP request headers as span attributes, set the environment variable -``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST`` to a comma delimited list of HTTP header names. - -For example, -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST="content-type,custom_request_header" - -will extract ``content-type`` and ``custom_request_header`` from the request headers and add them as span attributes. - -Request header names in ASGI are case-insensitive. So, giving the header name as ``CUStom-Header`` in the environment -variable will capture the header named ``custom-header``. - -Regular expressions may also be used to match multiple headers that correspond to the given pattern. For example: -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST="Accept.*,X-.*" - -Would match all request headers that start with ``Accept`` and ``X-``. - -To capture all request headers, set ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST`` to ``".*"``. -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST=".*" - -The name of the added span attribute will follow the format ``http.request.header.`` where ```` -is the normalized HTTP header name (lowercase, with ``-`` replaced by ``_``). The value of the attribute will be a -single item list containing all the header values. - -For example: -``http.request.header.custom_request_header = [","]`` - -Response headers -**************** -To capture HTTP response headers as span attributes, set the environment variable -``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE`` to a comma delimited list of HTTP header names. - -For example, -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE="content-type,custom_response_header" - -will extract ``content-type`` and ``custom_response_header`` from the response headers and add them as span attributes. - -Response header names in ASGI are case-insensitive. So, giving the header name as ``CUStom-Header`` in the environment -variable will capture the header named ``custom-header``. - -Regular expressions may also be used to match multiple headers that correspond to the given pattern. For example: -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE="Content.*,X-.*" - -Would match all response headers that start with ``Content`` and ``X-``. - -To capture all response headers, set ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE`` to ``".*"``. -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE=".*" - -The name of the added span attribute will follow the format ``http.response.header.`` where ```` -is the normalized HTTP header name (lowercase, with ``-`` replaced by ``_``). The value of the attribute will be a -single item list containing all the header values. - -For example: -``http.response.header.custom_response_header = [","]`` - -Sanitizing headers -****************** -In order to prevent storing sensitive data such as personally identifiable information (PII), session keys, passwords, -etc, set the environment variable ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS`` -to a comma delimited list of HTTP header names to be sanitized. Regexes may be used, and all header names will be -matched in a case-insensitive manner. - -For example, -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS=".*session.*,set-cookie" - -will replace the value of headers such as ``session-id`` and ``set-cookie`` with ``[REDACTED]`` in the span. - -Note: - The environment variable names used to capture HTTP headers are still experimental, and thus are subject to change. - -API ---- -""" import typing import urllib @@ -198,13 +23,13 @@ def client_response_hook(span: Span, message: dict): from asgiref.compatibility import guarantee_single_callable from opentelemetry import context, trace -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.asgi.version import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.asgi.version import ( __version__, ) # noqa -from opentelemetry.instrumentation.propagators import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.propagators import ( get_global_response_propagator, ) -from opentelemetry.instrumentation.utils import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.utils import ( _start_internal_or_server_span, http_status_to_status_code, ) @@ -214,7 +39,7 @@ def client_response_hook(span: Span, message: dict): from opentelemetry.semconv.trace import SpanAttributes from opentelemetry.trace import Span, set_span_in_context from opentelemetry.trace.status import Status, StatusCode -from azure.monitor.opentelemetry.vendor.opentelemetry.util.http import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.util.http import ( OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS, OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST, OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE, diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/asgi/package.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/asgi/package.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/asgi/package.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/asgi/package.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/asgi/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/asgi/version.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/asgi/version.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/asgi/version.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/auto_instrumentation/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/auto_instrumentation/__init__.py new file mode 100644 index 00000000..025d6588 --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/auto_instrumentation/__init__.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 + +# 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 argparse import REMAINDER, ArgumentParser +from logging import getLogger +from os import environ, execl, getcwd +from os.path import abspath, dirname, pathsep +from re import sub +from shutil import which + +from pkg_resources import iter_entry_points + +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.version import ( + __version__, +) + +_logger = getLogger(__name__) + + +def run() -> None: + parser = ArgumentParser( + description=""" + opentelemetry-instrument automatically instruments a Python + program and its dependencies and then runs the program. + """, + epilog=""" + Optional arguments (except for --help and --version) for opentelemetry-instrument + directly correspond with OpenTelemetry environment variables. The + corresponding optional argument is formed by removing the OTEL_ or + OTEL_PYTHON_ prefix from the environment variable and lower casing the + rest. For example, the optional argument --attribute_value_length_limit + corresponds with the environment variable + OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT. + + These optional arguments will override the current value of the + corresponding environment variable during the execution of the command. + """, + ) + + argument_otel_environment_variable = {} + + for entry_point in iter_entry_points( + "opentelemetry_environment_variables" + ): + environment_variable_module = entry_point.load() + + for attribute in dir(environment_variable_module): + if attribute.startswith("OTEL_"): + argument = sub(r"OTEL_(PYTHON_)?", "", attribute).lower() + + parser.add_argument( + f"--{argument}", + required=False, + ) + argument_otel_environment_variable[argument] = attribute + + parser.add_argument( + "--version", + help="print version information", + action="version", + version="%(prog)s " + __version__, + ) + parser.add_argument("command", help="Your Python application.") + parser.add_argument( + "command_args", + help="Arguments for your application.", + nargs=REMAINDER, + ) + + args = parser.parse_args() + + for argument, otel_environment_variable in ( + argument_otel_environment_variable + ).items(): + value = getattr(args, argument) + if value is not None: + environ[otel_environment_variable] = value + + python_path = environ.get("PYTHONPATH") + + if not python_path: + python_path = [] + + else: + python_path = python_path.split(pathsep) + + cwd_path = getcwd() + + # This is being added to support applications that are being run from their + # own executable, like Django. + # FIXME investigate if there is another way to achieve this + if cwd_path not in python_path: + python_path.insert(0, cwd_path) + + filedir_path = dirname(abspath(__file__)) + + python_path = [path for path in python_path if path != filedir_path] + + python_path.insert(0, filedir_path) + + environ["PYTHONPATH"] = pathsep.join(python_path) + + executable = which(args.command) + execl(executable, executable, *args.command_args) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/auto_instrumentation/sitecustomize.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/auto_instrumentation/sitecustomize.py new file mode 100644 index 00000000..3751a40c --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/auto_instrumentation/sitecustomize.py @@ -0,0 +1,134 @@ +# 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 os.path import abspath, dirname, pathsep + +from pkg_resources import iter_entry_points + +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.dependencies import ( + get_dist_dependency_conflicts, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.distro import ( + BaseDistro, + DefaultDistro, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.environment_variables import ( + OTEL_PYTHON_DISABLED_INSTRUMENTATIONS, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.utils import ( + _python_path_without_directory, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.version import ( + __version__, +) + +logger = getLogger(__name__) + + +def _load_distros() -> BaseDistro: + for entry_point in iter_entry_points("opentelemetry_distro"): + try: + distro = entry_point.load()() + if not isinstance(distro, BaseDistro): + logger.debug( + "%s is not an OpenTelemetry Distro. Skipping", + entry_point.name, + ) + continue + logger.debug( + "Distribution %s will be configured", entry_point.name + ) + return distro + except Exception as exc: # pylint: disable=broad-except + logger.exception( + "Distribution %s configuration failed", entry_point.name + ) + raise exc + return DefaultDistro() + + +def _load_instrumentors(distro): + package_to_exclude = environ.get(OTEL_PYTHON_DISABLED_INSTRUMENTATIONS, []) + if isinstance(package_to_exclude, str): + package_to_exclude = package_to_exclude.split(",") + # to handle users entering "requests , flask" or "requests, flask" with spaces + package_to_exclude = [x.strip() for x in package_to_exclude] + + for entry_point in iter_entry_points("opentelemetry_pre_instrument"): + entry_point.load()() + + for entry_point in iter_entry_points("opentelemetry_instrumentor"): + if entry_point.name in package_to_exclude: + logger.debug( + "Instrumentation skipped for library %s", entry_point.name + ) + continue + + try: + conflict = get_dist_dependency_conflicts(entry_point.dist) + if conflict: + logger.debug( + "Skipping instrumentation %s: %s", + entry_point.name, + conflict, + ) + continue + + # tell instrumentation to not run dep checks again as we already did it above + distro.load_instrumentor(entry_point, skip_dep_check=True) + logger.debug("Instrumented %s", entry_point.name) + except Exception as exc: # pylint: disable=broad-except + logger.exception("Instrumenting of %s failed", entry_point.name) + raise exc + + for entry_point in iter_entry_points("opentelemetry_post_instrument"): + entry_point.load()() + + +def _load_configurators(): + configured = None + for entry_point in iter_entry_points("opentelemetry_configurator"): + if configured is not None: + logger.warning( + "Configuration of %s not loaded, %s already loaded", + entry_point.name, + configured, + ) + continue + try: + entry_point.load()().configure(auto_instrumentation_version=__version__) # type: ignore + configured = entry_point.name + except Exception as exc: # pylint: disable=broad-except + logger.exception("Configuration of %s failed", entry_point.name) + raise exc + + +def initialize(): + # prevents auto-instrumentation of subprocesses if code execs another python process + environ["PYTHONPATH"] = _python_path_without_directory( + environ["PYTHONPATH"], dirname(abspath(__file__)), pathsep + ) + + try: + distro = _load_distros() + distro.configure() + _load_configurators() + _load_instrumentors(distro) + except Exception: # pylint: disable=broad-except + logger.exception("Failed to auto initialize opentelemetry") + + +initialize() diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/bootstrap.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/bootstrap.py new file mode 100644 index 00000000..afb6ed34 --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/bootstrap.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python3 + +# 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 argparse +import logging +import subprocess +import sys + +import pkg_resources + +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.bootstrap_gen import ( + default_instrumentations, + libraries, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.version import ( + __version__, +) + +logger = logging.getLogger(__name__) + + +def _syscall(func): + def wrapper(package=None): + try: + if package: + return func(package) + return func() + except subprocess.SubprocessError as exp: + cmd = getattr(exp, "cmd", None) + if cmd: + msg = f'Error calling system command "{" ".join(cmd)}"' + if package: + msg = f'{msg} for package "{package}"' + raise RuntimeError(msg) + + return wrapper + + +@_syscall +def _sys_pip_install(package): + # explicit upgrade strategy to override potential pip config + subprocess.check_call( + [ + sys.executable, + "-m", + "pip", + "install", + "-U", + "--upgrade-strategy", + "only-if-needed", + package, + ] + ) + + +def _pip_check(): + """Ensures none of the instrumentations have dependency conflicts. + Clean check reported as: + 'No broken requirements found.' + Dependency conflicts are reported as: + 'opentelemetry-instrumentation-flask 1.0.1 has requirement opentelemetry-sdk<2.0,>=1.0, but you have opentelemetry-sdk 0.5.' + To not be too restrictive, we'll only check for relevant packages. + """ + with subprocess.Popen( + [sys.executable, "-m", "pip", "check"], stdout=subprocess.PIPE + ) as check_pipe: + pip_check = check_pipe.communicate()[0].decode() + pip_check_lower = pip_check.lower() + for package_tup in libraries.values(): + for package in package_tup: + if package.lower() in pip_check_lower: + raise RuntimeError(f"Dependency conflict found: {pip_check}") + + +def _is_installed(req): + if req in sys.modules: + return True + + try: + pkg_resources.get_distribution(req) + except pkg_resources.DistributionNotFound: + return False + except pkg_resources.VersionConflict as exc: + logger.warning( + "instrumentation for package %s is available but version %s is installed. Skipping.", + exc.req, + exc.dist.as_requirement(), # pylint: disable=no-member + ) + return False + return True + + +def _find_installed_libraries(): + libs = default_instrumentations[:] + libs.extend( + [ + v["instrumentation"] + for _, v in libraries.items() + if _is_installed(v["library"]) + ] + ) + return libs + + +def _run_requirements(): + logger.setLevel(logging.ERROR) + print("\n".join(_find_installed_libraries()), end="") + + +def _run_install(): + for lib in _find_installed_libraries(): + _sys_pip_install(lib) + _pip_check() + + +def run() -> None: + action_install = "install" + action_requirements = "requirements" + + parser = argparse.ArgumentParser( + description=""" + opentelemetry-bootstrap detects installed libraries and automatically + installs the relevant instrumentation packages for them. + """ + ) + parser.add_argument( + "--version", + help="print version information", + action="version", + version="%(prog)s " + __version__, + ) + parser.add_argument( + "-a", + "--action", + choices=[action_install, action_requirements], + default=action_requirements, + help=""" + install - uses pip to install the new requirements using to the + currently active site-package. + requirements - prints out the new requirements to stdout. Action can + be piped and appended to a requirements.txt file. + """, + ) + args = parser.parse_args() + + cmd = { + action_install: _run_install, + action_requirements: _run_requirements, + }[args.action] + cmd() diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/bootstrap_gen.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/bootstrap_gen.py new file mode 100644 index 00000000..a2ee1dc1 --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/bootstrap_gen.py @@ -0,0 +1,175 @@ +# 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. + +# DO NOT EDIT. THIS FILE WAS AUTOGENERATED FROM INSTRUMENTATION PACKAGES. +# RUN `python scripts/generate_instrumentation_bootstrap.py` TO REGENERATE. + +libraries = { + "aio_pika": { + "library": "aio_pika >= 7.2.0, < 10.0.0", + "instrumentation": "opentelemetry-instrumentation-aio-pika==0.38b0", + }, + "aiohttp": { + "library": "aiohttp ~= 3.0", + "instrumentation": "opentelemetry-instrumentation-aiohttp-client==0.38b0", + }, + "aiopg": { + "library": "aiopg >= 0.13.0, < 2.0.0", + "instrumentation": "opentelemetry-instrumentation-aiopg==0.38b0", + }, + "asgiref": { + "library": "asgiref ~= 3.0", + "instrumentation": "opentelemetry-instrumentation-asgi==0.38b0", + }, + "asyncpg": { + "library": "asyncpg >= 0.12.0", + "instrumentation": "opentelemetry-instrumentation-asyncpg==0.38b0", + }, + "boto": { + "library": "boto~=2.0", + "instrumentation": "opentelemetry-instrumentation-boto==0.38b0", + }, + "boto3": { + "library": "boto3 ~= 1.0", + "instrumentation": "opentelemetry-instrumentation-boto3sqs==0.38b0", + }, + "botocore": { + "library": "botocore ~= 1.0", + "instrumentation": "opentelemetry-instrumentation-botocore==0.38b0", + }, + "celery": { + "library": "celery >= 4.0, < 6.0", + "instrumentation": "opentelemetry-instrumentation-celery==0.38b0", + }, + "confluent-kafka": { + "library": "confluent-kafka >= 1.8.2, < 2.0.0", + "instrumentation": "opentelemetry-instrumentation-confluent-kafka==0.38b0", + }, + "django": { + "library": "django >= 1.10", + "instrumentation": "opentelemetry-instrumentation-django==0.38b0", + }, + "elasticsearch": { + "library": "elasticsearch >= 2.0", + "instrumentation": "opentelemetry-instrumentation-elasticsearch==0.38b0", + }, + "falcon": { + "library": "falcon >= 1.4.1, < 4.0.0", + "instrumentation": "opentelemetry-instrumentation-falcon==0.38b0", + }, + "fastapi": { + "library": "fastapi ~= 0.58", + "instrumentation": "opentelemetry-instrumentation-fastapi==0.38b0", + }, + "flask": { + "library": "flask >= 1.0, < 3.0", + "instrumentation": "opentelemetry-instrumentation-flask==0.38b0", + }, + "grpcio": { + "library": "grpcio ~= 1.27", + "instrumentation": "opentelemetry-instrumentation-grpc==0.38b0", + }, + "httpx": { + "library": "httpx >= 0.18.0, <= 0.23.0", + "instrumentation": "opentelemetry-instrumentation-httpx==0.38b0", + }, + "jinja2": { + "library": "jinja2 >= 2.7, < 4.0", + "instrumentation": "opentelemetry-instrumentation-jinja2==0.38b0", + }, + "kafka-python": { + "library": "kafka-python >= 2.0", + "instrumentation": "opentelemetry-instrumentation-kafka-python==0.38b0", + }, + "mysql-connector-python": { + "library": "mysql-connector-python ~= 8.0", + "instrumentation": "opentelemetry-instrumentation-mysql==0.38b0", + }, + "pika": { + "library": "pika >= 0.12.0", + "instrumentation": "opentelemetry-instrumentation-pika==0.38b0", + }, + "psycopg2": { + "library": "psycopg2 >= 2.7.3.1", + "instrumentation": "opentelemetry-instrumentation-psycopg2==0.38b0", + }, + "pymemcache": { + "library": "pymemcache >= 1.3.5, < 4", + "instrumentation": "opentelemetry-instrumentation-pymemcache==0.38b0", + }, + "pymongo": { + "library": "pymongo >= 3.1, < 5.0", + "instrumentation": "opentelemetry-instrumentation-pymongo==0.38b0", + }, + "PyMySQL": { + "library": "PyMySQL < 2", + "instrumentation": "opentelemetry-instrumentation-pymysql==0.38b0", + }, + "pyramid": { + "library": "pyramid >= 1.7", + "instrumentation": "opentelemetry-instrumentation-pyramid==0.38b0", + }, + "redis": { + "library": "redis >= 2.6", + "instrumentation": "opentelemetry-instrumentation-redis==0.38b0", + }, + "remoulade": { + "library": "remoulade >= 0.50", + "instrumentation": "opentelemetry-instrumentation-remoulade==0.38b0", + }, + "requests": { + "library": "requests ~= 2.0", + "instrumentation": "opentelemetry-instrumentation-requests==0.38b0", + }, + "scikit-learn": { + "library": "scikit-learn ~= 0.24.0", + "instrumentation": "opentelemetry-instrumentation-sklearn==0.38b0", + }, + "sqlalchemy": { + "library": "sqlalchemy", + "instrumentation": "opentelemetry-instrumentation-sqlalchemy==0.38b0", + }, + "starlette": { + "library": "starlette ~= 0.13.0", + "instrumentation": "opentelemetry-instrumentation-starlette==0.38b0", + }, + "psutil": { + "library": "psutil >= 5", + "instrumentation": "opentelemetry-instrumentation-system-metrics==0.38b0", + }, + "tornado": { + "library": "tornado >= 5.1.1", + "instrumentation": "opentelemetry-instrumentation-tornado==0.38b0", + }, + "tortoise-orm": { + "library": "tortoise-orm >= 0.17.0", + "instrumentation": "opentelemetry-instrumentation-tortoiseorm==0.38b0", + }, + "pydantic": { + "library": "pydantic >= 1.10.2", + "instrumentation": "opentelemetry-instrumentation-tortoiseorm==0.38b0", + }, + "urllib3": { + "library": "urllib3 >= 1.0.0, < 2.0.0", + "instrumentation": "opentelemetry-instrumentation-urllib3==0.38b0", + }, +} +default_instrumentations = [ + "opentelemetry-instrumentation-aws-lambda==0.38b0", + "opentelemetry-instrumentation-dbapi==0.38b0", + "opentelemetry-instrumentation-logging==0.38b0", + "opentelemetry-instrumentation-sqlite3==0.38b0", + "opentelemetry-instrumentation-urllib==0.38b0", + "opentelemetry-instrumentation-wsgi==0.38b0", +] diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/dbapi/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/dbapi/__init__.py similarity index 95% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/dbapi/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/dbapi/__init__.py index e6382aad..09b2d673 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/dbapi/__init__.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/dbapi/__init__.py @@ -12,30 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" -The trace integration with Database API supports libraries that follow the -Python Database API Specification v2.0. -``_ - -Usage ------ - -.. code-block:: python - - import mysql.connector - import pyodbc - - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.dbapi import trace_integration - - - # Ex: mysql.connector - trace_integration(mysql.connector, "connect", "mysql") - # Ex: pyodbc - trace_integration(pyodbc, "Connection", "odbc") - -API ---- -""" import functools import logging @@ -45,11 +21,13 @@ import wrapt from opentelemetry import trace as trace_api -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.dbapi.version import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.dbapi.version import ( __version__, ) -from opentelemetry.instrumentation.sqlcommenter_utils import _add_sql_comment -from opentelemetry.instrumentation.utils import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.sqlcommenter_utils import ( + _add_sql_comment, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.utils import ( _get_opentelemetry_values, unwrap, ) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/dbapi/package.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/dbapi/package.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/dbapi/package.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/dbapi/package.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/dbapi/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/dbapi/version.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/dbapi/version.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/dbapi/version.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/dependencies.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/dependencies.py new file mode 100644 index 00000000..2da0a3d1 --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/dependencies.py @@ -0,0 +1,62 @@ +from logging import getLogger +from typing import Collection, Optional + +from pkg_resources import ( + Distribution, + DistributionNotFound, + RequirementParseError, + VersionConflict, + get_distribution, +) + +logger = getLogger(__name__) + + +class DependencyConflict: + required: str = None + found: Optional[str] = None + + def __init__(self, required, found=None): + self.required = required + self.found = found + + def __str__(self): + return f'DependencyConflict: requested: "{self.required}" but found: "{self.found}"' + + +def get_dist_dependency_conflicts( + dist: Distribution, +) -> Optional[DependencyConflict]: + main_deps = dist.requires() + instrumentation_deps = [] + for dep in dist.requires(("instruments",)): + if dep not in main_deps: + # we set marker to none so string representation of the dependency looks like + # requests ~= 1.0 + # instead of + # requests ~= 1.0; extra = "instruments" + # which does not work with `get_distribution()` + dep.marker = None + instrumentation_deps.append(str(dep)) + + return get_dependency_conflicts(instrumentation_deps) + + +def get_dependency_conflicts( + deps: Collection[str], +) -> Optional[DependencyConflict]: + for dep in deps: + try: + get_distribution(dep) + except VersionConflict as exc: + return DependencyConflict(dep, exc.dist) + except DistributionNotFound: + return DependencyConflict(dep) + except RequirementParseError as exc: + logger.warning( + 'error parsing dependency, reporting as a conflict: "%s" - %s', + dep, + exc, + ) + return DependencyConflict(dep) + return None diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/distro.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/distro.py new file mode 100644 index 00000000..e7403bfb --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/distro.py @@ -0,0 +1,73 @@ +# 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. +# type: ignore + +""" +OpenTelemetry Base Distribution (Distro) +""" + +from abc import ABC, abstractmethod +from logging import getLogger + +from pkg_resources import EntryPoint + +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.instrumentor import ( + BaseInstrumentor, +) + +_LOG = getLogger(__name__) + + +class BaseDistro(ABC): + """An ABC for distro""" + + _instance = None + + def __new__(cls, *args, **kwargs): + + if cls._instance is None: + cls._instance = object.__new__(cls, *args, **kwargs) + + return cls._instance + + @abstractmethod + def _configure(self, **kwargs): + """Configure the distribution""" + + def configure(self, **kwargs): + """Configure the distribution""" + self._configure(**kwargs) + + def load_instrumentor( # pylint: disable=no-self-use + self, entry_point: EntryPoint, **kwargs + ): + """Takes a collection of instrumentation entry points + and activates them by instantiating and calling instrument() + on each one. + + Distros can override this method to customize the behavior by + inspecting each entry point and configuring them in special ways, + passing additional arguments, load a replacement/fork instead, + skip loading entirely, etc. + """ + instrumentor: BaseInstrumentor = entry_point.load() + instrumentor().instrument(**kwargs) + + +class DefaultDistro(BaseDistro): + def _configure(self, **kwargs): + pass + + +__all__ = ["BaseDistro", "DefaultDistro"] diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/__init__.py new file mode 100644 index 00000000..f47af874 --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/__init__.py @@ -0,0 +1,167 @@ +# 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 Collection + +from django import VERSION as django_version +from django.conf import settings +from django.core.exceptions import ImproperlyConfigured + +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.django.environment_variables import ( + OTEL_PYTHON_DJANGO_INSTRUMENT, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.django.middleware.otel_middleware import ( + _DjangoMiddleware, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.django.package import ( + _instruments, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.django.version import ( + __version__, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.instrumentor import ( + BaseInstrumentor, +) +from opentelemetry.metrics import get_meter +from opentelemetry.semconv.metrics import MetricInstruments +from opentelemetry.trace import get_tracer +from azure.monitor.opentelemetry._vendor.opentelemetry.util.http import ( + get_excluded_urls, + parse_excluded_urls, +) + +DJANGO_2_0 = django_version >= (2, 0) + +_excluded_urls_from_env = get_excluded_urls("DJANGO") +_logger = getLogger(__name__) + + +def _get_django_middleware_setting() -> str: + # In Django versions 1.x, setting MIDDLEWARE_CLASSES can be used as a legacy + # alternative to MIDDLEWARE. This is the case when `settings.MIDDLEWARE` has + # its default value (`None`). + if not DJANGO_2_0 and getattr(settings, "MIDDLEWARE", None) is None: + return "MIDDLEWARE_CLASSES" + return "MIDDLEWARE" + + +class DjangoInstrumentor(BaseInstrumentor): + """An instrumentor for Django + + See `BaseInstrumentor` + """ + + _opentelemetry_middleware = ".".join( + [_DjangoMiddleware.__module__, _DjangoMiddleware.__qualname__] + ) + + _sql_commenter_middleware = "azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.django.middleware.sqlcommenter_middleware.SqlCommenter" + + def instrumentation_dependencies(self) -> Collection[str]: + return _instruments + + def _instrument(self, **kwargs): + # FIXME this is probably a pattern that will show up in the rest of the + # ext. Find a better way of implementing this. + if environ.get(OTEL_PYTHON_DJANGO_INSTRUMENT) == "False": + return + + tracer_provider = kwargs.get("tracer_provider") + meter_provider = kwargs.get("meter_provider") + _excluded_urls = kwargs.get("excluded_urls") + tracer = get_tracer( + __name__, + __version__, + tracer_provider=tracer_provider, + ) + meter = get_meter(__name__, __version__, meter_provider=meter_provider) + _DjangoMiddleware._tracer = tracer + _DjangoMiddleware._meter = meter + _DjangoMiddleware._excluded_urls = ( + _excluded_urls_from_env + if _excluded_urls is None + else parse_excluded_urls(_excluded_urls) + ) + _DjangoMiddleware._otel_request_hook = kwargs.pop("request_hook", None) + _DjangoMiddleware._otel_response_hook = kwargs.pop( + "response_hook", None + ) + _DjangoMiddleware._duration_histogram = meter.create_histogram( + name=MetricInstruments.HTTP_SERVER_DURATION, + unit="ms", + description="measures the duration of the inbound http request", + ) + _DjangoMiddleware._active_request_counter = meter.create_up_down_counter( + name=MetricInstruments.HTTP_SERVER_ACTIVE_REQUESTS, + unit="requests", + description="measures the number of concurrent HTTP requests those are currently in flight", + ) + # This can not be solved, but is an inherent problem of this approach: + # the order of middleware entries matters, and here you have no control + # on that: + # https://docs.djangoproject.com/en/3.0/topics/http/middleware/#activating-middleware + # https://docs.djangoproject.com/en/3.0/ref/middleware/#middleware-ordering + + _middleware_setting = _get_django_middleware_setting() + settings_middleware = [] + try: + settings_middleware = getattr(settings, _middleware_setting, []) + except ImproperlyConfigured as exception: + _logger.debug( + "DJANGO_SETTINGS_MODULE environment variable not configured. Defaulting to empty settings: %s", + exception, + ) + settings.configure() + settings_middleware = getattr(settings, _middleware_setting, []) + except ModuleNotFoundError as exception: + _logger.debug( + "DJANGO_SETTINGS_MODULE points to a non-existent module. Defaulting to empty settings: %s", + exception, + ) + settings.configure() + settings_middleware = getattr(settings, _middleware_setting, []) + + # Django allows to specify middlewares as a tuple, so we convert this tuple to a + # list, otherwise we wouldn't be able to call append/remove + if isinstance(settings_middleware, tuple): + settings_middleware = list(settings_middleware) + + is_sql_commentor_enabled = kwargs.pop("is_sql_commentor_enabled", None) + + if is_sql_commentor_enabled: + settings_middleware.insert(0, self._sql_commenter_middleware) + + settings_middleware.insert(0, self._opentelemetry_middleware) + + setattr(settings, _middleware_setting, settings_middleware) + + def _uninstrument(self, **kwargs): + _middleware_setting = _get_django_middleware_setting() + settings_middleware = getattr(settings, _middleware_setting, None) + + # FIXME This is starting to smell like trouble. We have 2 mechanisms + # that may make this condition be True, one implemented in + # BaseInstrumentor and another one implemented in _instrument. Both + # stop _instrument from running and thus, settings_middleware not being + # set. + if settings_middleware is None or ( + self._opentelemetry_middleware not in settings_middleware + ): + return + + settings_middleware.remove(self._opentelemetry_middleware) + setattr(settings, _middleware_setting, settings_middleware) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/environment_variables.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/environment_variables.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/environment_variables.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/environment_variables.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/middleware/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/middleware/__init__.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/middleware/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/middleware/__init__.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/middleware/otel_middleware.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/middleware/otel_middleware.py similarity index 92% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/middleware/otel_middleware.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/middleware/otel_middleware.py index 5ac16097..f7e34c3c 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/middleware/otel_middleware.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/middleware/otel_middleware.py @@ -22,31 +22,31 @@ from django.http import HttpRequest, HttpResponse from opentelemetry.context import detach -from opentelemetry.instrumentation.propagators import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.propagators import ( get_global_response_propagator, ) -from opentelemetry.instrumentation.utils import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.utils import ( _start_internal_or_server_span, extract_attributes_from_object, ) -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.wsgi import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.wsgi import ( add_response_attributes, ) -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.wsgi import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.wsgi import ( collect_custom_request_headers_attributes as wsgi_collect_custom_request_headers_attributes, ) -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.wsgi import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.wsgi import ( collect_custom_response_headers_attributes as wsgi_collect_custom_response_headers_attributes, ) -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.wsgi import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.wsgi import ( collect_request_attributes as wsgi_collect_request_attributes, ) -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.wsgi import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.wsgi import ( wsgi_getter, ) from opentelemetry.semconv.trace import SpanAttributes from opentelemetry.trace import Span, SpanKind, use_span -from azure.monitor.opentelemetry.vendor.opentelemetry.util.http import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.util.http import ( _parse_active_request_count_attrs, _parse_duration_attrs, get_excluded_urls, @@ -93,20 +93,20 @@ def __call__(self, request): # try/except block exclusive for optional ASGI imports. try: - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.asgi import ( + from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.asgi import ( asgi_getter, asgi_setter, ) - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.asgi import ( + from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.asgi import ( collect_custom_request_headers_attributes as asgi_collect_custom_request_attributes, ) - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.asgi import ( + from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.asgi import ( collect_custom_response_headers_attributes as asgi_collect_custom_response_attributes, ) - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.asgi import ( + from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.asgi import ( collect_request_attributes as asgi_collect_request_attributes, ) - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.asgi import ( + from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.asgi import ( set_status_code, ) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/middleware/sqlcommenter_middleware.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/middleware/sqlcommenter_middleware.py similarity index 94% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/middleware/sqlcommenter_middleware.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/middleware/sqlcommenter_middleware.py index 89d8a9b7..5a4a0149 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/middleware/sqlcommenter_middleware.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/middleware/sqlcommenter_middleware.py @@ -22,8 +22,12 @@ from django.db import connections from django.db.backends.utils import CursorDebugWrapper -from opentelemetry.instrumentation.sqlcommenter_utils import _add_sql_comment -from opentelemetry.instrumentation.utils import _get_opentelemetry_values +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.sqlcommenter_utils import ( + _add_sql_comment, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.utils import ( + _get_opentelemetry_values, +) from opentelemetry.trace.propagation.tracecontext import ( TraceContextTextMapPropagator, ) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/package.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/package.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/package.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/package.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/version.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/version.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/version.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/environment_variables.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/environment_variables.py new file mode 100644 index 00000000..ad28f068 --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/environment_variables.py @@ -0,0 +1,18 @@ +# 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. + +OTEL_PYTHON_DISABLED_INSTRUMENTATIONS = "OTEL_PYTHON_DISABLED_INSTRUMENTATIONS" +""" +.. envvar:: OTEL_PYTHON_DISABLED_INSTRUMENTATIONS +""" diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/fastapi/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/fastapi/__init__.py new file mode 100644 index 00000000..42b86cf9 --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/fastapi/__init__.py @@ -0,0 +1,194 @@ +# 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 logging +import typing +from typing import Collection + +import fastapi +from starlette.routing import Match + +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.asgi import ( + OpenTelemetryMiddleware, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.fastapi.package import ( + _instruments, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.fastapi.version import ( + __version__, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.instrumentor import ( + BaseInstrumentor, +) +from opentelemetry.metrics import get_meter +from opentelemetry.semconv.trace import SpanAttributes +from opentelemetry.trace import Span +from azure.monitor.opentelemetry._vendor.opentelemetry.util.http import ( + get_excluded_urls, + parse_excluded_urls, +) + +_excluded_urls_from_env = get_excluded_urls("FASTAPI") +_logger = logging.getLogger(__name__) + +_ServerRequestHookT = typing.Optional[typing.Callable[[Span, dict], None]] +_ClientRequestHookT = typing.Optional[typing.Callable[[Span, dict], None]] +_ClientResponseHookT = typing.Optional[typing.Callable[[Span, dict], None]] + + +class FastAPIInstrumentor(BaseInstrumentor): + """An instrumentor for FastAPI + + See `BaseInstrumentor` + """ + + _original_fastapi = None + + @staticmethod + def instrument_app( + app: fastapi.FastAPI, + server_request_hook: _ServerRequestHookT = None, + client_request_hook: _ClientRequestHookT = None, + client_response_hook: _ClientResponseHookT = None, + tracer_provider=None, + meter_provider=None, + excluded_urls=None, + ): + """Instrument an uninstrumented FastAPI application.""" + if not hasattr(app, "_is_instrumented_by_opentelemetry"): + app._is_instrumented_by_opentelemetry = False + + if not getattr(app, "_is_instrumented_by_opentelemetry", False): + if excluded_urls is None: + excluded_urls = _excluded_urls_from_env + else: + excluded_urls = parse_excluded_urls(excluded_urls) + meter = get_meter(__name__, __version__, meter_provider) + + app.add_middleware( + OpenTelemetryMiddleware, + excluded_urls=excluded_urls, + default_span_details=_get_route_details, + server_request_hook=server_request_hook, + client_request_hook=client_request_hook, + client_response_hook=client_response_hook, + tracer_provider=tracer_provider, + meter=meter, + ) + app._is_instrumented_by_opentelemetry = True + if app not in _InstrumentedFastAPI._instrumented_fastapi_apps: + _InstrumentedFastAPI._instrumented_fastapi_apps.add(app) + else: + _logger.warning( + "Attempting to instrument FastAPI app while already instrumented" + ) + + @staticmethod + def uninstrument_app(app: fastapi.FastAPI): + app.user_middleware = [ + x + for x in app.user_middleware + if x.cls is not OpenTelemetryMiddleware + ] + app.middleware_stack = app.build_middleware_stack() + app._is_instrumented_by_opentelemetry = False + + def instrumentation_dependencies(self) -> Collection[str]: + return _instruments + + def _instrument(self, **kwargs): + self._original_fastapi = fastapi.FastAPI + _InstrumentedFastAPI._tracer_provider = kwargs.get("tracer_provider") + _InstrumentedFastAPI._server_request_hook = kwargs.get( + "server_request_hook" + ) + _InstrumentedFastAPI._client_request_hook = kwargs.get( + "client_request_hook" + ) + _InstrumentedFastAPI._client_response_hook = kwargs.get( + "client_response_hook" + ) + _excluded_urls = kwargs.get("excluded_urls") + _InstrumentedFastAPI._excluded_urls = ( + _excluded_urls_from_env + if _excluded_urls is None + else parse_excluded_urls(_excluded_urls) + ) + _InstrumentedFastAPI._meter_provider = kwargs.get("meter_provider") + fastapi.FastAPI = _InstrumentedFastAPI + + def _uninstrument(self, **kwargs): + for instance in _InstrumentedFastAPI._instrumented_fastapi_apps: + self.uninstrument_app(instance) + _InstrumentedFastAPI._instrumented_fastapi_apps.clear() + fastapi.FastAPI = self._original_fastapi + + +class _InstrumentedFastAPI(fastapi.FastAPI): + _tracer_provider = None + _meter_provider = None + _excluded_urls = None + _server_request_hook: _ServerRequestHookT = None + _client_request_hook: _ClientRequestHookT = None + _client_response_hook: _ClientResponseHookT = None + _instrumented_fastapi_apps = set() + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + meter = get_meter( + __name__, __version__, _InstrumentedFastAPI._meter_provider + ) + self.add_middleware( + OpenTelemetryMiddleware, + excluded_urls=_InstrumentedFastAPI._excluded_urls, + default_span_details=_get_route_details, + server_request_hook=_InstrumentedFastAPI._server_request_hook, + client_request_hook=_InstrumentedFastAPI._client_request_hook, + client_response_hook=_InstrumentedFastAPI._client_response_hook, + tracer_provider=_InstrumentedFastAPI._tracer_provider, + meter=meter, + ) + self._is_instrumented_by_opentelemetry = True + _InstrumentedFastAPI._instrumented_fastapi_apps.add(self) + + def __del__(self): + if self in _InstrumentedFastAPI._instrumented_fastapi_apps: + _InstrumentedFastAPI._instrumented_fastapi_apps.remove(self) + + +def _get_route_details(scope): + """Callback to retrieve the fastapi route being served. + + TODO: there is currently no way to retrieve http.route from + a starlette application from scope. + + See: https://github.com/encode/starlette/pull/804 + """ + app = scope["app"] + route = None + for starlette_route in app.routes: + match, _ = starlette_route.matches(scope) + if match == Match.FULL: + route = starlette_route.path + break + if match == Match.PARTIAL: + route = starlette_route.path + # method only exists for http, if websocket + # leave it blank. + span_name = route or scope.get("method", "") + attributes = {} + if route: + attributes[SpanAttributes.HTTP_ROUTE] = route + return span_name, attributes diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/fastapi/package.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/fastapi/package.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/fastapi/package.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/fastapi/package.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/fastapi/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/fastapi/version.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/fastapi/version.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/fastapi/version.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/flask/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/flask/__init__.py similarity index 63% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/flask/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/flask/__init__.py index 062570d1..c1681da7 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/flask/__init__.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/flask/__init__.py @@ -15,229 +15,7 @@ # Note: This package is not named "flask" because of # https://github.com/PyCQA/pylint/issues/2648 -""" -This library builds on the OpenTelemetry WSGI middleware to track web requests -in Flask applications. In addition to opentelemetry-util-http, it -supports Flask-specific features such as: -* The Flask url rule pattern is used as the Span name. -* The ``http.route`` Span attribute is set so that one can see which URL rule - matched a request. - -SQLCOMMENTER -***************************************** -You can optionally configure Flask instrumentation to enable sqlcommenter which enriches -the query with contextual information. - -Usage ------ - -.. code:: python - - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.flask import FlaskInstrumentor - - FlaskInstrumentor().instrument(enable_commenter=True, commenter_options={}) - -For example, FlaskInstrumentor when used with SQLAlchemyInstrumentor or Psycopg2Instrumentor, -invoking ``cursor.execute("select * from auth_users")`` will lead to sql query -``select * from auth_users`` but when SQLCommenter is enabled the query will get appended with -some configurable tags like: - -.. code:: - - select * from auth_users /*metrics=value*/;" - -Inorder for the commenter to append flask related tags to sql queries, the commenter needs -to enabled on the respective SQLAlchemyInstrumentor or Psycopg2Instrumentor framework too. - -SQLCommenter Configurations -*************************** -We can configure the tags to be appended to the sqlquery log by adding configuration -inside ``commenter_options={}`` dict. - -For example, enabling this flag will add flask and it's version which -is ``/*flask%%3A2.9.3*/`` to the SQL query as a comment (default is True): - -.. code:: python - - framework = True - -For example, enabling this flag will add route uri ``/*route='/home'*/`` -to the SQL query as a comment (default is True): - -.. code:: python - - route = True - -For example, enabling this flag will add controller name ``/*controller='home_view'*/`` -to the SQL query as a comment (default is True): - -.. code:: python - - controller = True - -Usage ------ - -.. code-block:: python - - from flask import Flask - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.flask import FlaskInstrumentor - - app = Flask(__name__) - - FlaskInstrumentor().instrument_app(app) - - @app.route("/") - def hello(): - return "Hello!" - - if __name__ == "__main__": - app.run(debug=True) - -Configuration -------------- - -Exclude lists -************* -To exclude certain URLs from tracking, set the environment variable ``OTEL_PYTHON_FLASK_EXCLUDED_URLS`` -(or ``OTEL_PYTHON_EXCLUDED_URLS`` to cover all instrumentations) to a string of comma delimited regexes that match the -URLs. - -For example, - -:: - - export OTEL_PYTHON_FLASK_EXCLUDED_URLS="client/.*/info,healthcheck" - -will exclude requests such as ``https://site/client/123/info`` and ``https://site/xyz/healthcheck``. - -You can also pass comma delimited regexes directly to the ``instrument_app`` method: - -.. code-block:: python - - FlaskInstrumentor().instrument_app(app, excluded_urls="client/.*/info,healthcheck") - -Request/Response hooks -********************** - -This instrumentation supports request and response hooks. These are functions that get called -right after a span is created for a request and right before the span is finished for the response. - -- The client request hook is called with the internal span and an instance of WSGIEnvironment (flask.request.environ) - when the method ``receive`` is called. -- The client response hook is called with the internal span, the status of the response and a list of key-value (tuples) - representing the response headers returned from the response when the method ``send`` is called. - -For example, - -.. code-block:: python - - def request_hook(span: Span, environ: WSGIEnvironment): - if span and span.is_recording(): - span.set_attribute("custom_user_attribute_from_request_hook", "some-value") - - def response_hook(span: Span, status: str, response_headers: List): - if span and span.is_recording(): - span.set_attribute("custom_user_attribute_from_response_hook", "some-value") - - FlaskInstrumentation().instrument(request_hook=request_hook, response_hook=response_hook) - -Flask Request object reference: https://flask.palletsprojects.com/en/2.1.x/api/#flask.Request - -Capture HTTP request and response headers -***************************************** -You can configure the agent to capture specified HTTP headers as span attributes, according to the -`semantic convention `_. - -Request headers -*************** -To capture HTTP request headers as span attributes, set the environment variable -``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST`` to a comma delimited list of HTTP header names. - -For example, -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST="content-type,custom_request_header" - -will extract ``content-type`` and ``custom_request_header`` from the request headers and add them as span attributes. - -Request header names in Flask are case-insensitive and ``-`` characters are replaced by ``_``. So, giving the header -name as ``CUStom_Header`` in the environment variable will capture the header named ``custom-header``. - -Regular expressions may also be used to match multiple headers that correspond to the given pattern. For example: -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST="Accept.*,X-.*" - -Would match all request headers that start with ``Accept`` and ``X-``. - -To capture all request headers, set ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST`` to ``".*"``. -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST=".*" - -The name of the added span attribute will follow the format ``http.request.header.`` where ```` -is the normalized HTTP header name (lowercase, with ``-`` replaced by ``_``). The value of the attribute will be a -single item list containing all the header values. - -For example: -``http.request.header.custom_request_header = [","]`` - -Response headers -**************** -To capture HTTP response headers as span attributes, set the environment variable -``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE`` to a comma delimited list of HTTP header names. - -For example, -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE="content-type,custom_response_header" - -will extract ``content-type`` and ``custom_response_header`` from the response headers and add them as span attributes. - -Response header names in Flask are case-insensitive. So, giving the header name as ``CUStom-Header`` in the environment -variable will capture the header named ``custom-header``. - -Regular expressions may also be used to match multiple headers that correspond to the given pattern. For example: -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE="Content.*,X-.*" - -Would match all response headers that start with ``Content`` and ``X-``. - -To capture all response headers, set ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE`` to ``".*"``. -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE=".*" - -The name of the added span attribute will follow the format ``http.response.header.`` where ```` -is the normalized HTTP header name (lowercase, with ``-`` replaced by ``_``). The value of the attribute will be a -single item list containing all the header values. - -For example: -``http.response.header.custom_response_header = [","]`` - -Sanitizing headers -****************** -In order to prevent storing sensitive data such as personally identifiable information (PII), session keys, passwords, -etc, set the environment variable ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS`` -to a comma delimited list of HTTP header names to be sanitized. Regexes may be used, and all header names will be -matched in a case-insensitive manner. - -For example, -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS=".*session.*,set-cookie" - -will replace the value of headers such as ``session-id`` and ``set-cookie`` with ``[REDACTED]`` in the span. - -Note: - The environment variable names used to capture HTTP headers are still experimental, and thus are subject to change. - -API ---- -""" from logging import getLogger from threading import get_ident from time import time_ns @@ -246,23 +24,27 @@ def response_hook(span: Span, status: str, response_headers: List): import flask -import azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.wsgi as otel_wsgi +import azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.wsgi as otel_wsgi from opentelemetry import context, trace -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.flask.package import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.flask.package import ( _instruments, ) -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.flask.version import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.flask.version import ( __version__, ) -from opentelemetry.instrumentation.instrumentor import BaseInstrumentor -from opentelemetry.instrumentation.propagators import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.instrumentor import ( + BaseInstrumentor, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.propagators import ( get_global_response_propagator, ) -from opentelemetry.instrumentation.utils import _start_internal_or_server_span +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.utils import ( + _start_internal_or_server_span, +) from opentelemetry.metrics import get_meter from opentelemetry.semconv.metrics import MetricInstruments from opentelemetry.semconv.trace import SpanAttributes -from azure.monitor.opentelemetry.vendor.opentelemetry.util.http import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.util.http import ( get_excluded_urls, parse_excluded_urls, ) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/flask/package.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/flask/package.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/flask/package.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/flask/package.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/flask/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/flask/version.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/flask/version.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/flask/version.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/instrumentor.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/instrumentor.py new file mode 100644 index 00000000..520f1dc8 --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/instrumentor.py @@ -0,0 +1,131 @@ +# 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. +# type: ignore + +""" +OpenTelemetry Base Instrumentor +""" + +from abc import ABC, abstractmethod +from logging import getLogger +from typing import Collection, Optional + +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.dependencies import ( + DependencyConflict, + get_dependency_conflicts, +) + +_LOG = getLogger(__name__) + + +class BaseInstrumentor(ABC): + """An ABC for instrumentors + + Child classes of this ABC should instrument specific third + party libraries or frameworks either by using the + ``opentelemetry-instrument`` command or by calling their methods + directly. + + Since every third party library or framework is different and has different + instrumentation needs, more methods can be added to the child classes as + needed to provide practical instrumentation to the end user. + """ + + _instance = None + _is_instrumented_by_opentelemetry = False + + def __new__(cls, *args, **kwargs): + if cls._instance is None: + cls._instance = object.__new__(cls) + + return cls._instance + + @property + def is_instrumented_by_opentelemetry(self): + return self._is_instrumented_by_opentelemetry + + @abstractmethod + def instrumentation_dependencies(self) -> Collection[str]: + """Return a list of python packages with versions that the will be instrumented. + + The format should be the same as used in requirements.txt or pyproject.toml. + + For example, if an instrumentation instruments requests 1.x, this method should look + like: + + def instrumentation_dependencies(self) -> Collection[str]: + return ['requests ~= 1.0'] + + This will ensure that the instrumentation will only be used when the specified library + is present in the environment. + """ + + def _instrument(self, **kwargs): + """Instrument the library""" + + @abstractmethod + def _uninstrument(self, **kwargs): + """Uninstrument the library""" + + def _check_dependency_conflicts(self) -> Optional[DependencyConflict]: + dependencies = self.instrumentation_dependencies() + return get_dependency_conflicts(dependencies) + + def instrument(self, **kwargs): + """Instrument the library + + This method will be called without any optional arguments by the + ``opentelemetry-instrument`` command. + + This means that calling this method directly without passing any + optional values should do the very same thing that the + ``opentelemetry-instrument`` command does. + """ + + if self._is_instrumented_by_opentelemetry: + _LOG.warning("Attempting to instrument while already instrumented") + return None + + # check if instrumentor has any missing or conflicting dependencies + skip_dep_check = kwargs.pop("skip_dep_check", False) + if not skip_dep_check: + conflict = self._check_dependency_conflicts() + if conflict: + _LOG.error(conflict) + return None + + result = self._instrument( # pylint: disable=assignment-from-no-return + **kwargs + ) + self._is_instrumented_by_opentelemetry = True + return result + + def uninstrument(self, **kwargs): + """Uninstrument the library + + See ``BaseInstrumentor.instrument`` for more information regarding the + usage of ``kwargs``. + """ + + if self._is_instrumented_by_opentelemetry: + result = self._uninstrument(**kwargs) + self._is_instrumented_by_opentelemetry = False + return result + + _LOG.warning("Attempting to uninstrument while already uninstrumented") + + return None + + +__all__ = ["BaseInstrumentor"] diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/propagators.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/propagators.py new file mode 100644 index 00000000..bc40f774 --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/propagators.py @@ -0,0 +1,124 @@ +# 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 module implements experimental propagators to inject trace context +into response carriers. This is useful for server side frameworks that start traces +when server requests and want to share the trace context with the client so the +client can add its spans to the same trace. + +This is part of an upcoming W3C spec and will eventually make it to the Otel spec. + +https://w3c.github.io/trace-context/#trace-context-http-response-headers-format +""" + +import typing +from abc import ABC, abstractmethod + +from opentelemetry import trace +from opentelemetry.context.context import Context +from opentelemetry.propagators import textmap +from opentelemetry.trace import format_span_id, format_trace_id + +_HTTP_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers" +_RESPONSE_PROPAGATOR = None + + +def get_global_response_propagator(): + return _RESPONSE_PROPAGATOR + + +def set_global_response_propagator(propagator): + global _RESPONSE_PROPAGATOR # pylint:disable=global-statement + _RESPONSE_PROPAGATOR = propagator + + +class Setter(ABC): + @abstractmethod + def set(self, carrier, key, value): + """Inject the provided key value pair in carrier.""" + + +class DictHeaderSetter(Setter): + def set(self, carrier, key, value): # pylint: disable=no-self-use + old_value = carrier.get(key, "") + if old_value: + value = f"{old_value}, {value}" + carrier[key] = value + + +class FuncSetter(Setter): + """FuncSetter coverts a function into a valid Setter. Any function that can + set values in a carrier can be converted into a Setter by using FuncSetter. + This is useful when injecting trace context into non-dict objects such + HTTP Response objects for different framework. + + For example, it can be used to create a setter for Falcon response object as: + + setter = FuncSetter(falcon.api.Response.append_header) + + and then used with the propagator as: + + propagator.inject(falcon_response, setter=setter) + + This would essentially make the propagator call `falcon_response.append_header(key, value)` + """ + + def __init__(self, func): + self._func = func + + def set(self, carrier, key, value): + self._func(carrier, key, value) + + +default_setter = DictHeaderSetter() + + +class ResponsePropagator(ABC): + @abstractmethod + def inject( + self, + carrier: textmap.CarrierT, + context: typing.Optional[Context] = None, + setter: textmap.Setter = default_setter, + ) -> None: + """Injects SpanContext into the HTTP response carrier.""" + + +class TraceResponsePropagator(ResponsePropagator): + """Experimental propagator that injects tracecontext into HTTP responses.""" + + def inject( + self, + carrier: textmap.CarrierT, + context: typing.Optional[Context] = None, + setter: textmap.Setter = default_setter, + ) -> None: + """Injects SpanContext into the HTTP response carrier.""" + span = trace.get_current_span(context) + span_context = span.get_span_context() + if span_context == trace.INVALID_SPAN_CONTEXT: + return + + header_name = "traceresponse" + setter.set( + carrier, + header_name, + f"00-{format_trace_id(span_context.trace_id)}-{format_span_id(span_context.span_id)}-{span_context.trace_flags:02x}", + ) + setter.set( + carrier, + _HTTP_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS, + header_name, + ) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/psycopg2/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/psycopg2/__init__.py similarity index 69% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/psycopg2/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/psycopg2/__init__.py index 0ff18571..820496bf 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/psycopg2/__init__.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/psycopg2/__init__.py @@ -12,94 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" -The integration with PostgreSQL supports the `Psycopg`_ library, it can be enabled by -using ``Psycopg2Instrumentor``. - -.. _Psycopg: http://initd.org/psycopg/ - -SQLCOMMENTER -***************************************** -You can optionally configure Psycopg2 instrumentation to enable sqlcommenter which enriches -the query with contextual information. - -Usage ------ - -.. code:: python - - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.psycopg2 import Psycopg2Instrumentor - - Psycopg2Instrumentor().instrument(enable_commenter=True, commenter_options={}) - - -For example, -:: - - Invoking cursor.execute("select * from auth_users") will lead to sql query "select * from auth_users" but when SQLCommenter is enabled - the query will get appended with some configurable tags like "select * from auth_users /*tag=value*/;" - - -SQLCommenter Configurations -*************************** -We can configure the tags to be appended to the sqlquery log by adding configuration inside commenter_options(default:{}) keyword - -db_driver = True(Default) or False - -For example, -:: -Enabling this flag will add psycopg2 and it's version which is /*psycopg2%%3A2.9.3*/ - -dbapi_threadsafety = True(Default) or False - -For example, -:: -Enabling this flag will add threadsafety /*dbapi_threadsafety=2*/ - -dbapi_level = True(Default) or False - -For example, -:: -Enabling this flag will add dbapi_level /*dbapi_level='2.0'*/ - -libpq_version = True(Default) or False - -For example, -:: -Enabling this flag will add libpq_version /*libpq_version=140001*/ - -driver_paramstyle = True(Default) or False - -For example, -:: -Enabling this flag will add driver_paramstyle /*driver_paramstyle='pyformat'*/ - -opentelemetry_values = True(Default) or False - -For example, -:: -Enabling this flag will add traceparent values /*traceparent='00-03afa25236b8cd948fa853d67038ac79-405ff022e8247c46-01'*/ - -Usage ------ - -.. code-block:: python - - import psycopg2 - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.psycopg2 import Psycopg2Instrumentor - - - Psycopg2Instrumentor().instrument() - - cnx = psycopg2.connect(database='Database') - cursor = cnx.cursor() - cursor.execute("INSERT INTO test (testField) VALUES (123)") - cursor.close() - cnx.close() - -API ---- -""" import logging import typing @@ -111,14 +23,16 @@ ) from psycopg2.sql import Composed # pylint: disable=no-name-in-module -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation import ( dbapi, ) -from opentelemetry.instrumentation.instrumentor import BaseInstrumentor -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.psycopg2.package import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.instrumentor import ( + BaseInstrumentor, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.psycopg2.package import ( _instruments, ) -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.psycopg2.version import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.psycopg2.version import ( __version__, ) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/psycopg2/package.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/psycopg2/package.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/psycopg2/package.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/psycopg2/package.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/psycopg2/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/psycopg2/version.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/psycopg2/version.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/psycopg2/version.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/py.typed b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/requests/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/requests/__init__.py similarity index 86% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/requests/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/requests/__init__.py index 6ff4ea83..af7a348e 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/requests/__init__.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/requests/__init__.py @@ -12,41 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" -This library allows tracing HTTP requests made by the -`requests `_ library. - -Usage ------ - -.. code-block:: python - - import requests - from azure.monitor.opentelemetry.vendor.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 -------------- - -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 ---- -""" import functools import types @@ -62,14 +27,16 @@ # FIXME: fix the importing of this private attribute when the location of the _SUPPRESS_HTTP_INSTRUMENTATION_KEY is defined. from opentelemetry.context import _SUPPRESS_HTTP_INSTRUMENTATION_KEY -from opentelemetry.instrumentation.instrumentor import BaseInstrumentor -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.requests.package import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.instrumentor import ( + BaseInstrumentor, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.requests.package import ( _instruments, ) -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.requests.version import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.requests.version import ( __version__, ) -from opentelemetry.instrumentation.utils import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.utils import ( _SUPPRESS_INSTRUMENTATION_KEY, http_status_to_status_code, ) @@ -80,12 +47,12 @@ from opentelemetry.trace import SpanKind, Tracer, get_tracer from opentelemetry.trace.span import Span from opentelemetry.trace.status import Status -from azure.monitor.opentelemetry.vendor.opentelemetry.util.http import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.util.http import ( get_excluded_urls, parse_excluded_urls, remove_url_credentials, ) -from azure.monitor.opentelemetry.vendor.opentelemetry.util.http.httplib import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.util.http.httplib import ( set_ip_on_next_http_connection, ) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/requests/package.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/requests/package.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/requests/package.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/requests/package.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/requests/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/requests/version.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/requests/version.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/requests/version.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/sqlcommenter_utils.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/sqlcommenter_utils.py new file mode 100644 index 00000000..3616ea3c --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/sqlcommenter_utils.py @@ -0,0 +1,68 @@ +# 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 opentelemetry import context +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.utils import ( + _url_quote, +) + + +def _add_sql_comment(sql, **meta) -> str: + """ + Appends comments to the sql statement and returns it + """ + meta.update(**_add_framework_tags()) + comment = _generate_sql_comment(**meta) + sql = sql.rstrip() + if sql[-1] == ";": + sql = sql[:-1] + comment + ";" + else: + sql = sql + comment + return sql + + +def _generate_sql_comment(**meta) -> str: + """ + Return a SQL comment with comma delimited key=value pairs created from + **meta kwargs. + """ + key_value_delimiter = "," + + if not meta: # No entries added. + return "" + + # Sort the keywords to ensure that caching works and that testing is + # deterministic. It eases visual inspection as well. + return ( + " /*" + + key_value_delimiter.join( + f"{_url_quote(key)}={_url_quote(value)!r}" + for key, value in sorted(meta.items()) + if value is not None + ) + + "*/" + ) + + +def _add_framework_tags() -> dict: + """ + Returns orm related tags if any set by the context + """ + + sqlcommenter_framework_values = ( + context.get_value("SQLCOMMENTER_ORM_TAGS_AND_VALUES") + if context.get_value("SQLCOMMENTER_ORM_TAGS_AND_VALUES") + else {} + ) + return sqlcommenter_framework_values diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib/__init__.py similarity index 83% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib/__init__.py index f13bbcb7..50c4ee06 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib/__init__.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib/__init__.py @@ -12,53 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" -This library allows tracing HTTP requests made by the -`urllib `_ library. - -Usage ------ -.. code-block:: python - - from urllib import request - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.urllib import URLLibInstrumentor - - # You can optionally pass a custom TracerProvider to - # URLLibInstrumentor().instrument() - - URLLibInstrumentor().instrument() - req = request.Request('https://postman-echo.com/post', method="POST") - r = request.urlopen(req) - -Configuration -------------- - -Request/Response hooks -********************** - -The urllib 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 - - # `request_obj` is an instance of urllib.request.Request - def request_hook(span, request_obj): - pass - - # `request_obj` is an instance of urllib.request.Request - # `response` is an instance of http.client.HTTPResponse - def response_hook(span, request_obj, response) - pass - - URLLibInstrumentor.instrument( - request_hook=request_hook, response_hook=response_hook) - ) - -API ---- -""" import functools import types @@ -75,14 +28,16 @@ def response_hook(span, request_obj, response) # FIXME: fix the importing of this private attribute when the location of the _SUPPRESS_HTTP_INSTRUMENTATION_KEY is defined. from opentelemetry.context import _SUPPRESS_HTTP_INSTRUMENTATION_KEY -from opentelemetry.instrumentation.instrumentor import BaseInstrumentor -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.urllib.package import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.instrumentor import ( + BaseInstrumentor, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.urllib.package import ( _instruments, ) -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.urllib.version import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.urllib.version import ( __version__, ) -from opentelemetry.instrumentation.utils import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.utils import ( _SUPPRESS_INSTRUMENTATION_KEY, http_status_to_status_code, ) @@ -92,7 +47,7 @@ def response_hook(span, request_obj, response) from opentelemetry.semconv.trace import SpanAttributes from opentelemetry.trace import Span, SpanKind, get_tracer from opentelemetry.trace.status import Status -from azure.monitor.opentelemetry.vendor.opentelemetry.util.http import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.util.http import ( remove_url_credentials, ) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib/package.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib/package.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib/package.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib/package.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib/version.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib/version.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib/version.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib3/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib3/__init__.py similarity index 83% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib3/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib3/__init__.py index 2ec1d57d..7291d0f9 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib3/__init__.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib3/__init__.py @@ -12,57 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" -This library allows tracing HTTP requests made by the -`urllib3 `_ library. - -Usage ------ -.. code-block:: python - - import urllib3 - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.urllib3 import URLLib3Instrumentor - - def strip_query_params(url: str) -> str: - return url.split("?")[0] - - URLLib3Instrumentor().instrument( - # Remove all query params from the URL attribute on the span. - url_filter=strip_query_params, - ) - - http = urllib3.PoolManager() - response = http.request("GET", "https://www.example.org/") - -Configuration -------------- - -Request/Response hooks -********************** - -The urllib3 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 - - # `request` is an instance of urllib3.connectionpool.HTTPConnectionPool - def request_hook(span, request): - pass - - # `request` is an instance of urllib3.connectionpool.HTTPConnectionPool - # `response` is an instance of urllib3.response.HTTPResponse - def response_hook(span, request, response): - pass - - URLLib3Instrumentor.instrument( - request_hook=request_hook, response_hook=response_hook) - ) - -API ---- -""" import collections.abc import contextlib @@ -78,14 +27,16 @@ def response_hook(span, request, response): # FIXME: fix the importing of this private attribute when the location of the _SUPPRESS_HTTP_INSTRUMENTATION_KEY is defined. from opentelemetry.context import _SUPPRESS_HTTP_INSTRUMENTATION_KEY -from opentelemetry.instrumentation.instrumentor import BaseInstrumentor -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.urllib3.package import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.instrumentor import ( + BaseInstrumentor, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.urllib3.package import ( _instruments, ) -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.urllib3.version import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.urllib3.version import ( __version__, ) -from opentelemetry.instrumentation.utils import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.utils import ( _SUPPRESS_INSTRUMENTATION_KEY, http_status_to_status_code, unwrap, @@ -96,7 +47,7 @@ def response_hook(span, request, response): from opentelemetry.semconv.trace import SpanAttributes from opentelemetry.trace import Span, SpanKind, Tracer, get_tracer from opentelemetry.trace.status import Status -from azure.monitor.opentelemetry.vendor.opentelemetry.util.http.httplib import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.util.http.httplib import ( set_ip_on_next_http_connection, ) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib3/package.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib3/package.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib3/package.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib3/package.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib3/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib3/version.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/urllib3/version.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib3/version.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/utils.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/utils.py new file mode 100644 index 00000000..3022e6dd --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/utils.py @@ -0,0 +1,154 @@ +# 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 urllib.parse +from re import escape, sub +from typing import Dict, Sequence + +from wrapt import ObjectProxy + +from opentelemetry import context, trace + +# pylint: disable=unused-import +# pylint: disable=E0611 +from opentelemetry.context import _SUPPRESS_INSTRUMENTATION_KEY # noqa: F401 +from opentelemetry.propagate import extract +from opentelemetry.trace import StatusCode +from opentelemetry.trace.propagation.tracecontext import ( + TraceContextTextMapPropagator, +) + +propagator = TraceContextTextMapPropagator() + + +def extract_attributes_from_object( + obj: any, attributes: Sequence[str], existing: Dict[str, str] = None +) -> Dict[str, str]: + extracted = {} + if existing: + extracted.update(existing) + for attr in attributes: + value = getattr(obj, attr, None) + if value is not None: + extracted[attr] = str(value) + return extracted + + +def http_status_to_status_code( + status: int, + allow_redirect: bool = True, + server_span: bool = False, +) -> StatusCode: + """Converts an HTTP status code to an OpenTelemetry canonical status code + + Args: + status (int): HTTP status code + """ + # See: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#status + if not isinstance(status, int): + return StatusCode.UNSET + + if status < 100: + return StatusCode.ERROR + if status <= 299: + return StatusCode.UNSET + if status <= 399 and allow_redirect: + return StatusCode.UNSET + if status <= 499 and server_span: + return StatusCode.UNSET + return StatusCode.ERROR + + +def unwrap(obj, attr: str): + """Given a function that was wrapped by wrapt.wrap_function_wrapper, unwrap it + + Args: + obj: Object that holds a reference to the wrapped function + attr (str): Name of the wrapped function + """ + func = getattr(obj, attr, None) + if func and isinstance(func, ObjectProxy) and hasattr(func, "__wrapped__"): + setattr(obj, attr, func.__wrapped__) + + +def _start_internal_or_server_span( + tracer, + span_name, + start_time, + context_carrier, + context_getter, + attributes=None, +): + """Returns internal or server span along with the token which can be used by caller to reset context + + + Args: + tracer : tracer in use by given instrumentation library + name (string): name of the span + start_time : start time of the span + context_carrier : object which contains values that are + used to construct a Context. This object + must be paired with an appropriate getter + which understands how to extract a value from it. + context_getter : an object which contains a get function that can retrieve zero + or more values from the carrier and a keys function that can get all the keys + from carrier. + """ + + token = ctx = span_kind = None + if trace.get_current_span() is trace.INVALID_SPAN: + ctx = extract(context_carrier, getter=context_getter) + token = context.attach(ctx) + span_kind = trace.SpanKind.SERVER + else: + ctx = context.get_current() + span_kind = trace.SpanKind.INTERNAL + span = tracer.start_span( + name=span_name, + context=ctx, + kind=span_kind, + start_time=start_time, + attributes=attributes, + ) + return span, token + + +def _url_quote(s) -> str: # pylint: disable=invalid-name + if not isinstance(s, (str, bytes)): + return s + quoted = urllib.parse.quote(s) + # Since SQL uses '%' as a keyword, '%' is a by-product of url quoting + # e.g. foo,bar --> foo%2Cbar + # thus in our quoting, we need to escape it too to finally give + # foo,bar --> foo%%2Cbar + return quoted.replace("%", "%%") + + +def _get_opentelemetry_values() -> dict: + """ + Return the OpenTelemetry Trace and Span IDs if Span ID is set in the + OpenTelemetry execution context. + """ + # Insert the W3C TraceContext generated + _headers = {} + propagator.inject(_headers) + return _headers + + +def _python_path_without_directory(python_path, directory, path_separator): + return sub( + rf"{escape(directory)}{path_separator}(?!$)", + "", + python_path, + ) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/wsgi/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/version.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/wsgi/version.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/version.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/wsgi/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/wsgi/__init__.py similarity index 65% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/wsgi/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/wsgi/__init__.py index 0f7409fe..bba8211d 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/wsgi/__init__.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/wsgi/__init__.py @@ -11,195 +11,7 @@ # 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 a WSGI middleware that can be used on any WSGI framework -(such as Django / Flask / Web.py) to track requests timing through OpenTelemetry. -Usage (Flask) -------------- - -.. code-block:: python - - from flask import Flask - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.wsgi import OpenTelemetryMiddleware - - app = Flask(__name__) - app.wsgi_app = OpenTelemetryMiddleware(app.wsgi_app) - - @app.route("/") - def hello(): - return "Hello!" - - if __name__ == "__main__": - app.run(debug=True) - - -Usage (Django) --------------- - -Modify the application's ``wsgi.py`` file as shown below. - -.. code-block:: python - - import os - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.wsgi import OpenTelemetryMiddleware - from django.core.wsgi import get_wsgi_application - - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings') - - application = get_wsgi_application() - application = OpenTelemetryMiddleware(application) - -Usage (Web.py) --------------- - -.. code-block:: python - - import web - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.wsgi import OpenTelemetryMiddleware - from cheroot import wsgi - - urls = ('/', 'index') - - - class index: - - def GET(self): - return "Hello, world!" - - - if __name__ == "__main__": - app = web.application(urls, globals()) - func = app.wsgifunc() - - func = OpenTelemetryMiddleware(func) - - server = wsgi.WSGIServer( - ("localhost", 5100), func, server_name="localhost" - ) - server.start() - -Configuration -------------- - -Request/Response hooks -********************** - -This instrumentation supports request and response hooks. These are functions that get called -right after a span is created for a request and right before the span is finished for the response. - -- The client request hook is called with the internal span and an instance of WSGIEnvironment when the method - ``receive`` is called. -- The client response hook is called with the internal span, the status of the response and a list of key-value (tuples) - representing the response headers returned from the response when the method ``send`` is called. - -For example, - -.. code-block:: python - - def request_hook(span: Span, environ: WSGIEnvironment): - if span and span.is_recording(): - span.set_attribute("custom_user_attribute_from_request_hook", "some-value") - - def response_hook(span: Span, environ: WSGIEnvironment, status: str, response_headers: List): - if span and span.is_recording(): - span.set_attribute("custom_user_attribute_from_response_hook", "some-value") - - OpenTelemetryMiddleware(request_hook=request_hook, response_hook=response_hook) - -Capture HTTP request and response headers -***************************************** -You can configure the agent to capture specified HTTP headers as span attributes, according to the -`semantic convention `_. - -Request headers -*************** -To capture HTTP request headers as span attributes, set the environment variable -``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST`` to a comma delimited list of HTTP header names. - -For example, -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST="content-type,custom_request_header" - -will extract ``content-type`` and ``custom_request_header`` from the request headers and add them as span attributes. - -Request header names in WSGI are case-insensitive and ``-`` characters are replaced by ``_``. So, giving the header -name as ``CUStom_Header`` in the environment variable will capture the header named ``custom-header``. - -Regular expressions may also be used to match multiple headers that correspond to the given pattern. For example: -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST="Accept.*,X-.*" - -Would match all request headers that start with ``Accept`` and ``X-``. - -To capture all request headers, set ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST`` to ``".*"``. -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST=".*" - -The name of the added span attribute will follow the format ``http.request.header.`` where ```` -is the normalized HTTP header name (lowercase, with ``-`` replaced by ``_``). The value of the attribute will be a -single item list containing all the header values. - -For example: -``http.request.header.custom_request_header = [","]`` - -Response headers -**************** -To capture HTTP response headers as span attributes, set the environment variable -``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE`` to a comma delimited list of HTTP header names. - -For example, -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE="content-type,custom_response_header" - -will extract ``content-type`` and ``custom_response_header`` from the response headers and add them as span attributes. - -Response header names in WSGI are case-insensitive. So, giving the header name as ``CUStom-Header`` in the environment -variable will capture the header named ``custom-header``. - -Regular expressions may also be used to match multiple headers that correspond to the given pattern. For example: -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE="Content.*,X-.*" - -Would match all response headers that start with ``Content`` and ``X-``. - -To capture all response headers, set ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE`` to ``".*"``. -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE=".*" - -The name of the added span attribute will follow the format ``http.response.header.`` where ```` -is the normalized HTTP header name (lowercase, with ``-`` replaced by ``_``). The value of the attribute will be a -single item list containing all the header values. - -For example: -``http.response.header.custom_response_header = [","]`` - -Sanitizing headers -****************** -In order to prevent storing sensitive data such as personally identifiable information (PII), session keys, passwords, -etc, set the environment variable ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS`` -to a comma delimited list of HTTP header names to be sanitized. Regexes may be used, and all header names will be -matched in a case-insensitive manner. - -For example, -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS=".*session.*,set-cookie" - -will replace the value of headers such as ``session-id`` and ``set-cookie`` with ``[REDACTED]`` in the span. - -Note: - The environment variable names used to capture HTTP headers are still experimental, and thus are subject to change. - -API ---- -""" import functools import typing @@ -207,11 +19,11 @@ def response_hook(span: Span, environ: WSGIEnvironment, status: str, response_he from timeit import default_timer from opentelemetry import context, trace -from opentelemetry.instrumentation.utils import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.utils import ( _start_internal_or_server_span, http_status_to_status_code, ) -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.wsgi.version import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.wsgi.version import ( __version__, ) from opentelemetry.metrics import get_meter @@ -219,7 +31,7 @@ def response_hook(span: Span, environ: WSGIEnvironment, status: str, response_he from opentelemetry.semconv.metrics import MetricInstruments from opentelemetry.semconv.trace import SpanAttributes from opentelemetry.trace.status import Status, StatusCode -from azure.monitor.opentelemetry.vendor.opentelemetry.util.http import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.util.http import ( OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS, OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST, OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE, @@ -583,7 +395,7 @@ def _end_span_after_iterating(iterable, span, token): context.detach(token) -# TODO: inherit from opentelemetry.instrumentation.propagators.Setter +# TODO: inherit from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.propagators.Setter class ResponsePropagationSetter: diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/wsgi/package.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/wsgi/package.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/wsgi/package.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/wsgi/package.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/util/http/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/wsgi/version.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/util/http/version.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/wsgi/version.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/util/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/util/__init__.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/util/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/util/__init__.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/util/http/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/util/http/__init__.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/util/http/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/util/http/__init__.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/util/http/httplib.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/util/http/httplib.py similarity index 95% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/util/http/httplib.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/util/http/httplib.py index de95a0aa..0ccec5f9 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/util/http/httplib.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/util/http/httplib.py @@ -27,8 +27,12 @@ import wrapt from opentelemetry import context -from opentelemetry.instrumentation.instrumentor import BaseInstrumentor -from opentelemetry.instrumentation.utils import unwrap +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.instrumentor import ( + BaseInstrumentor, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.utils import ( + unwrap, +) from opentelemetry.semconv.trace import SpanAttributes from opentelemetry.trace.span import Span diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/util/http/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/util/http/version.py similarity index 95% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/util/http/version.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/util/http/version.py index eb62a67e..88e9292a 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/util/http/version.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/util/http/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.39b0.dev" +__version__ = "0.38b0" diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/autoinstrumentation/_distro.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/autoinstrumentation/_distro.py index 1ca3adf5..0a7ff16f 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/autoinstrumentation/_distro.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/autoinstrumentation/_distro.py @@ -17,7 +17,7 @@ OTEL_METRICS_EXPORTER, OTEL_TRACES_EXPORTER, ) -from opentelemetry.instrumentation.distro import BaseDistro +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.distro import BaseDistro from opentelemetry.sdk.environment_variables import ( _OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED, ) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/__init__.py deleted file mode 100644 index b5f052c7..00000000 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/django/__init__.py +++ /dev/null @@ -1,387 +0,0 @@ -# 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. -""" - -Instrument `django`_ to trace Django applications. - -.. _django: https://pypi.org/project/django/ - -SQLCOMMENTER -***************************************** -You can optionally configure Django instrumentation to enable sqlcommenter which enriches -the query with contextual information. - -Usage ------ - -.. code:: python - - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.django import DjangoInstrumentor - - DjangoInstrumentor().instrument(is_sql_commentor_enabled=True) - - -For example, -:: - - Invoking Users().objects.all() will lead to sql query "select * from auth_users" but when SQLCommenter is enabled - the query will get appended with some configurable tags like "select * from auth_users /*metrics=value*/;" - - -SQLCommenter Configurations -*************************** -We can configure the tags to be appended to the sqlquery log by adding below variables to the settings.py - -SQLCOMMENTER_WITH_FRAMEWORK = True(Default) or False - -For example, -:: -Enabling this flag will add django framework and it's version which is /*framework='django%3A2.2.3*/ - -SQLCOMMENTER_WITH_CONTROLLER = True(Default) or False - -For example, -:: -Enabling this flag will add controller name that handles the request /*controller='index'*/ - -SQLCOMMENTER_WITH_ROUTE = True(Default) or False - -For example, -:: -Enabling this flag will add url path that handles the request /*route='polls/'*/ - -SQLCOMMENTER_WITH_APP_NAME = True(Default) or False - -For example, -:: -Enabling this flag will add app name that handles the request /*app_name='polls'*/ - -SQLCOMMENTER_WITH_OPENTELEMETRY = True(Default) or False - -For example, -:: -Enabling this flag will add opentelemetry traceparent /*traceparent='00-fd720cffceba94bbf75940ff3caaf3cc-4fd1a2bdacf56388-01'*/ - -SQLCOMMENTER_WITH_DB_DRIVER = True(Default) or False - -For example, -:: -Enabling this flag will add name of the db driver /*db_driver='django.db.backends.postgresql'*/ - -Usage ------ - -.. code:: python - - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.django import DjangoInstrumentor - - DjangoInstrumentor().instrument() - - -Configuration -------------- - -Exclude lists -************* -To exclude certain URLs from tracking, set the environment variable ``OTEL_PYTHON_DJANGO_EXCLUDED_URLS`` -(or ``OTEL_PYTHON_EXCLUDED_URLS`` to cover all instrumentations) to a string of comma delimited regexes that match the -URLs. - -For example, - -:: - - export OTEL_PYTHON_DJANGO_EXCLUDED_URLS="client/.*/info,healthcheck" - -will exclude requests such as ``https://site/client/123/info`` and ``https://site/xyz/healthcheck``. - -Request attributes -******************** -To extract attributes from Django's request object and use them as span attributes, set the environment variable -``OTEL_PYTHON_DJANGO_TRACED_REQUEST_ATTRS`` to a comma delimited list of request attribute names. - -For example, - -:: - - export OTEL_PYTHON_DJANGO_TRACED_REQUEST_ATTRS='path_info,content_type' - -will extract the ``path_info`` and ``content_type`` attributes from every traced request and add them as span attributes. - -Django Request object reference: https://docs.djangoproject.com/en/3.1/ref/request-response/#attributes - -Request and Response hooks -*************************** -This instrumentation supports request and response hooks. These are functions that get called -right after a span is created for a request and right before the span is finished for the response. -The hooks can be configured as follows: - -.. code:: python - - def request_hook(span, request): - pass - - def response_hook(span, request, response): - pass - - DjangoInstrumentor().instrument(request_hook=request_hook, response_hook=response_hook) - -Django Request object: https://docs.djangoproject.com/en/3.1/ref/request-response/#httprequest-objects -Django Response object: https://docs.djangoproject.com/en/3.1/ref/request-response/#httpresponse-objects - -Capture HTTP request and response headers -***************************************** -You can configure the agent to capture specified HTTP headers as span attributes, according to the -`semantic convention `_. - -Request headers -*************** -To capture HTTP request headers as span attributes, set the environment variable -``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST`` to a comma delimited list of HTTP header names. - -For example, -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST="content-type,custom_request_header" - -will extract ``content-type`` and ``custom_request_header`` from the request headers and add them as span attributes. - -Request header names in Django are case-insensitive. So, giving the header name as ``CUStom-Header`` in the environment -variable will capture the header named ``custom-header``. - -Regular expressions may also be used to match multiple headers that correspond to the given pattern. For example: -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST="Accept.*,X-.*" - -Would match all request headers that start with ``Accept`` and ``X-``. - -To capture all request headers, set ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST`` to ``".*"``. -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST=".*" - -The name of the added span attribute will follow the format ``http.request.header.`` where ```` -is the normalized HTTP header name (lowercase, with ``-`` replaced by ``_``). The value of the attribute will be a -single item list containing all the header values. - -For example: -``http.request.header.custom_request_header = [","]`` - -Response headers -**************** -To capture HTTP response headers as span attributes, set the environment variable -``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE`` to a comma delimited list of HTTP header names. - -For example, -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE="content-type,custom_response_header" - -will extract ``content-type`` and ``custom_response_header`` from the response headers and add them as span attributes. - -Response header names in Django are case-insensitive. So, giving the header name as ``CUStom-Header`` in the environment -variable will capture the header named ``custom-header``. - -Regular expressions may also be used to match multiple headers that correspond to the given pattern. For example: -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE="Content.*,X-.*" - -Would match all response headers that start with ``Content`` and ``X-``. - -To capture all response headers, set ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE`` to ``".*"``. -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE=".*" - -The name of the added span attribute will follow the format ``http.response.header.`` where ```` -is the normalized HTTP header name (lowercase, with ``-`` replaced by ``_``). The value of the attribute will be a -single item list containing all the header values. - -For example: -``http.response.header.custom_response_header = [","]`` - -Sanitizing headers -****************** -In order to prevent storing sensitive data such as personally identifiable information (PII), session keys, passwords, -etc, set the environment variable ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS`` -to a comma delimited list of HTTP header names to be sanitized. Regexes may be used, and all header names will be -matched in a case-insensitive manner. - -For example, -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS=".*session.*,set-cookie" - -will replace the value of headers such as ``session-id`` and ``set-cookie`` with ``[REDACTED]`` in the span. - -Note: - The environment variable names used to capture HTTP headers are still experimental, and thus are subject to change. - -API ---- - -""" - -from logging import getLogger -from os import environ -from typing import Collection - -from django import VERSION as django_version -from django.conf import settings -from django.core.exceptions import ImproperlyConfigured - -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.django.environment_variables import ( - OTEL_PYTHON_DJANGO_INSTRUMENT, -) -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.django.middleware.otel_middleware import ( - _DjangoMiddleware, -) -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.django.package import ( - _instruments, -) -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.django.version import ( - __version__, -) -from opentelemetry.instrumentation.instrumentor import BaseInstrumentor -from opentelemetry.metrics import get_meter -from opentelemetry.semconv.metrics import MetricInstruments -from opentelemetry.trace import get_tracer -from azure.monitor.opentelemetry.vendor.opentelemetry.util.http import ( - get_excluded_urls, - parse_excluded_urls, -) - -DJANGO_2_0 = django_version >= (2, 0) - -_excluded_urls_from_env = get_excluded_urls("DJANGO") -_logger = getLogger(__name__) - - -def _get_django_middleware_setting() -> str: - # In Django versions 1.x, setting MIDDLEWARE_CLASSES can be used as a legacy - # alternative to MIDDLEWARE. This is the case when `settings.MIDDLEWARE` has - # its default value (`None`). - if not DJANGO_2_0 and getattr(settings, "MIDDLEWARE", None) is None: - return "MIDDLEWARE_CLASSES" - return "MIDDLEWARE" - - -class DjangoInstrumentor(BaseInstrumentor): - """An instrumentor for Django - - See `BaseInstrumentor` - """ - - _opentelemetry_middleware = ".".join( - [_DjangoMiddleware.__module__, _DjangoMiddleware.__qualname__] - ) - - _sql_commenter_middleware = "azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.django.middleware.sqlcommenter_middleware.SqlCommenter" - - def instrumentation_dependencies(self) -> Collection[str]: - return _instruments - - def _instrument(self, **kwargs): - # FIXME this is probably a pattern that will show up in the rest of the - # ext. Find a better way of implementing this. - if environ.get(OTEL_PYTHON_DJANGO_INSTRUMENT) == "False": - return - - tracer_provider = kwargs.get("tracer_provider") - meter_provider = kwargs.get("meter_provider") - _excluded_urls = kwargs.get("excluded_urls") - tracer = get_tracer( - __name__, - __version__, - tracer_provider=tracer_provider, - ) - meter = get_meter(__name__, __version__, meter_provider=meter_provider) - _DjangoMiddleware._tracer = tracer - _DjangoMiddleware._meter = meter - _DjangoMiddleware._excluded_urls = ( - _excluded_urls_from_env - if _excluded_urls is None - else parse_excluded_urls(_excluded_urls) - ) - _DjangoMiddleware._otel_request_hook = kwargs.pop("request_hook", None) - _DjangoMiddleware._otel_response_hook = kwargs.pop( - "response_hook", None - ) - _DjangoMiddleware._duration_histogram = meter.create_histogram( - name=MetricInstruments.HTTP_SERVER_DURATION, - unit="ms", - description="measures the duration of the inbound http request", - ) - _DjangoMiddleware._active_request_counter = meter.create_up_down_counter( - name=MetricInstruments.HTTP_SERVER_ACTIVE_REQUESTS, - unit="requests", - description="measures the number of concurrent HTTP requests those are currently in flight", - ) - # This can not be solved, but is an inherent problem of this approach: - # the order of middleware entries matters, and here you have no control - # on that: - # https://docs.djangoproject.com/en/3.0/topics/http/middleware/#activating-middleware - # https://docs.djangoproject.com/en/3.0/ref/middleware/#middleware-ordering - - _middleware_setting = _get_django_middleware_setting() - settings_middleware = [] - try: - settings_middleware = getattr(settings, _middleware_setting, []) - except ImproperlyConfigured as exception: - _logger.debug( - "DJANGO_SETTINGS_MODULE environment variable not configured. Defaulting to empty settings: %s", - exception, - ) - settings.configure() - settings_middleware = getattr(settings, _middleware_setting, []) - except ModuleNotFoundError as exception: - _logger.debug( - "DJANGO_SETTINGS_MODULE points to a non-existent module. Defaulting to empty settings: %s", - exception, - ) - settings.configure() - settings_middleware = getattr(settings, _middleware_setting, []) - - # Django allows to specify middlewares as a tuple, so we convert this tuple to a - # list, otherwise we wouldn't be able to call append/remove - if isinstance(settings_middleware, tuple): - settings_middleware = list(settings_middleware) - - is_sql_commentor_enabled = kwargs.pop("is_sql_commentor_enabled", None) - - if is_sql_commentor_enabled: - settings_middleware.insert(0, self._sql_commenter_middleware) - - settings_middleware.insert(0, self._opentelemetry_middleware) - - setattr(settings, _middleware_setting, settings_middleware) - - def _uninstrument(self, **kwargs): - _middleware_setting = _get_django_middleware_setting() - settings_middleware = getattr(settings, _middleware_setting, None) - - # FIXME This is starting to smell like trouble. We have 2 mechanisms - # that may make this condition be True, one implemented in - # BaseInstrumentor and another one implemented in _instrument. Both - # stop _instrument from running and thus, settings_middleware not being - # set. - if settings_middleware is None or ( - self._opentelemetry_middleware not in settings_middleware - ): - return - - settings_middleware.remove(self._opentelemetry_middleware) - setattr(settings, _middleware_setting, settings_middleware) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/fastapi/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/fastapi/__init__.py deleted file mode 100644 index 3406bab7..00000000 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/fastapi/__init__.py +++ /dev/null @@ -1,350 +0,0 @@ -# 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. - -""" -Usage ------ - -.. code-block:: python - - import fastapi - from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.fastapi import FastAPIInstrumentor - - app = fastapi.FastAPI() - - @app.get("/foobar") - async def foobar(): - return {"message": "hello world"} - - FastAPIInstrumentor.instrument_app(app) - -Configuration -------------- - -Exclude lists -************* -To exclude certain URLs from tracking, set the environment variable ``OTEL_PYTHON_FASTAPI_EXCLUDED_URLS`` -(or ``OTEL_PYTHON_EXCLUDED_URLS`` to cover all instrumentations) to a string of comma delimited regexes that match the -URLs. - -For example, - -:: - - export OTEL_PYTHON_FASTAPI_EXCLUDED_URLS="client/.*/info,healthcheck" - -will exclude requests such as ``https://site/client/123/info`` and ``https://site/xyz/healthcheck``. - -You can also pass comma delimited regexes directly to the ``instrument_app`` method: - -.. code-block:: python - - FastAPIInstrumentor.instrument_app(app, excluded_urls="client/.*/info,healthcheck") - -Request/Response hooks -********************** - -This instrumentation supports request and response hooks. These are functions that get called -right after a span is created for a request and right before the span is finished for the response. - -- The server request hook is passed a server span and ASGI scope object for every incoming request. -- The client request hook is called with the internal span and an ASGI scope when the method ``receive`` is called. -- The client response hook is called with the internal span and an ASGI event when the method ``send`` is called. - -.. code-block:: python - - def server_request_hook(span: Span, scope: dict): - if span and span.is_recording(): - span.set_attribute("custom_user_attribute_from_request_hook", "some-value") - - def client_request_hook(span: Span, scope: dict): - if span and span.is_recording(): - span.set_attribute("custom_user_attribute_from_client_request_hook", "some-value") - - def client_response_hook(span: Span, message: dict): - if span and span.is_recording(): - span.set_attribute("custom_user_attribute_from_response_hook", "some-value") - - FastAPIInstrumentor().instrument(server_request_hook=server_request_hook, client_request_hook=client_request_hook, client_response_hook=client_response_hook) - -Capture HTTP request and response headers -***************************************** -You can configure the agent to capture specified HTTP headers as span attributes, according to the -`semantic convention `_. - -Request headers -*************** -To capture HTTP request headers as span attributes, set the environment variable -``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST`` to a comma delimited list of HTTP header names. - -For example, -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST="content-type,custom_request_header" - -will extract ``content-type`` and ``custom_request_header`` from the request headers and add them as span attributes. - -Request header names in FastAPI are case-insensitive. So, giving the header name as ``CUStom-Header`` in the environment -variable will capture the header named ``custom-header``. - -Regular expressions may also be used to match multiple headers that correspond to the given pattern. For example: -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST="Accept.*,X-.*" - -Would match all request headers that start with ``Accept`` and ``X-``. - -To capture all request headers, set ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST`` to ``".*"``. -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST=".*" - -The name of the added span attribute will follow the format ``http.request.header.`` where ```` -is the normalized HTTP header name (lowercase, with ``-`` replaced by ``_``). The value of the attribute will be a -single item list containing all the header values. - -For example: -``http.request.header.custom_request_header = [","]`` - -Response headers -**************** -To capture HTTP response headers as span attributes, set the environment variable -``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE`` to a comma delimited list of HTTP header names. - -For example, -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE="content-type,custom_response_header" - -will extract ``content-type`` and ``custom_response_header`` from the response headers and add them as span attributes. - -Response header names in FastAPI are case-insensitive. So, giving the header name as ``CUStom-Header`` in the environment -variable will capture the header named ``custom-header``. - -Regular expressions may also be used to match multiple headers that correspond to the given pattern. For example: -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE="Content.*,X-.*" - -Would match all response headers that start with ``Content`` and ``X-``. - -To capture all response headers, set ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE`` to ``".*"``. -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE=".*" - -The name of the added span attribute will follow the format ``http.response.header.`` where ```` -is the normalized HTTP header name (lowercase, with ``-`` replaced by ``_``). The value of the attribute will be a -single item list containing all the header values. - -For example: -``http.response.header.custom_response_header = [","]`` - -Sanitizing headers -****************** -In order to prevent storing sensitive data such as personally identifiable information (PII), session keys, passwords, -etc, set the environment variable ``OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS`` -to a comma delimited list of HTTP header names to be sanitized. Regexes may be used, and all header names will be -matched in a case-insensitive manner. - -For example, -:: - - export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS=".*session.*,set-cookie" - -will replace the value of headers such as ``session-id`` and ``set-cookie`` with ``[REDACTED]`` in the span. - -Note: - The environment variable names used to capture HTTP headers are still experimental, and thus are subject to change. - -API ---- -""" -import logging -import typing -from typing import Collection - -import fastapi -from starlette.routing import Match - -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.asgi import ( - OpenTelemetryMiddleware, -) -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.fastapi.package import ( - _instruments, -) -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.fastapi.version import ( - __version__, -) -from opentelemetry.instrumentation.instrumentor import BaseInstrumentor -from opentelemetry.metrics import get_meter -from opentelemetry.semconv.trace import SpanAttributes -from opentelemetry.trace import Span -from azure.monitor.opentelemetry.vendor.opentelemetry.util.http import ( - get_excluded_urls, - parse_excluded_urls, -) - -_excluded_urls_from_env = get_excluded_urls("FASTAPI") -_logger = logging.getLogger(__name__) - -_ServerRequestHookT = typing.Optional[typing.Callable[[Span, dict], None]] -_ClientRequestHookT = typing.Optional[typing.Callable[[Span, dict], None]] -_ClientResponseHookT = typing.Optional[typing.Callable[[Span, dict], None]] - - -class FastAPIInstrumentor(BaseInstrumentor): - """An instrumentor for FastAPI - - See `BaseInstrumentor` - """ - - _original_fastapi = None - - @staticmethod - def instrument_app( - app: fastapi.FastAPI, - server_request_hook: _ServerRequestHookT = None, - client_request_hook: _ClientRequestHookT = None, - client_response_hook: _ClientResponseHookT = None, - tracer_provider=None, - meter_provider=None, - excluded_urls=None, - ): - """Instrument an uninstrumented FastAPI application.""" - if not hasattr(app, "_is_instrumented_by_opentelemetry"): - app._is_instrumented_by_opentelemetry = False - - if not getattr(app, "_is_instrumented_by_opentelemetry", False): - if excluded_urls is None: - excluded_urls = _excluded_urls_from_env - else: - excluded_urls = parse_excluded_urls(excluded_urls) - meter = get_meter(__name__, __version__, meter_provider) - - app.add_middleware( - OpenTelemetryMiddleware, - excluded_urls=excluded_urls, - default_span_details=_get_route_details, - server_request_hook=server_request_hook, - client_request_hook=client_request_hook, - client_response_hook=client_response_hook, - tracer_provider=tracer_provider, - meter=meter, - ) - app._is_instrumented_by_opentelemetry = True - if app not in _InstrumentedFastAPI._instrumented_fastapi_apps: - _InstrumentedFastAPI._instrumented_fastapi_apps.add(app) - else: - _logger.warning( - "Attempting to instrument FastAPI app while already instrumented" - ) - - @staticmethod - def uninstrument_app(app: fastapi.FastAPI): - app.user_middleware = [ - x - for x in app.user_middleware - if x.cls is not OpenTelemetryMiddleware - ] - app.middleware_stack = app.build_middleware_stack() - app._is_instrumented_by_opentelemetry = False - - def instrumentation_dependencies(self) -> Collection[str]: - return _instruments - - def _instrument(self, **kwargs): - self._original_fastapi = fastapi.FastAPI - _InstrumentedFastAPI._tracer_provider = kwargs.get("tracer_provider") - _InstrumentedFastAPI._server_request_hook = kwargs.get( - "server_request_hook" - ) - _InstrumentedFastAPI._client_request_hook = kwargs.get( - "client_request_hook" - ) - _InstrumentedFastAPI._client_response_hook = kwargs.get( - "client_response_hook" - ) - _excluded_urls = kwargs.get("excluded_urls") - _InstrumentedFastAPI._excluded_urls = ( - _excluded_urls_from_env - if _excluded_urls is None - else parse_excluded_urls(_excluded_urls) - ) - _InstrumentedFastAPI._meter_provider = kwargs.get("meter_provider") - fastapi.FastAPI = _InstrumentedFastAPI - - def _uninstrument(self, **kwargs): - for instance in _InstrumentedFastAPI._instrumented_fastapi_apps: - self.uninstrument_app(instance) - _InstrumentedFastAPI._instrumented_fastapi_apps.clear() - fastapi.FastAPI = self._original_fastapi - - -class _InstrumentedFastAPI(fastapi.FastAPI): - _tracer_provider = None - _meter_provider = None - _excluded_urls = None - _server_request_hook: _ServerRequestHookT = None - _client_request_hook: _ClientRequestHookT = None - _client_response_hook: _ClientResponseHookT = None - _instrumented_fastapi_apps = set() - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - meter = get_meter( - __name__, __version__, _InstrumentedFastAPI._meter_provider - ) - self.add_middleware( - OpenTelemetryMiddleware, - excluded_urls=_InstrumentedFastAPI._excluded_urls, - default_span_details=_get_route_details, - server_request_hook=_InstrumentedFastAPI._server_request_hook, - client_request_hook=_InstrumentedFastAPI._client_request_hook, - client_response_hook=_InstrumentedFastAPI._client_response_hook, - tracer_provider=_InstrumentedFastAPI._tracer_provider, - meter=meter, - ) - self._is_instrumented_by_opentelemetry = True - _InstrumentedFastAPI._instrumented_fastapi_apps.add(self) - - def __del__(self): - if self in _InstrumentedFastAPI._instrumented_fastapi_apps: - _InstrumentedFastAPI._instrumented_fastapi_apps.remove(self) - - -def _get_route_details(scope): - """Callback to retrieve the fastapi route being served. - - TODO: there is currently no way to retrieve http.route from - a starlette application from scope. - - See: https://github.com/encode/starlette/pull/804 - """ - app = scope["app"] - route = None - for starlette_route in app.routes: - match, _ = starlette_route.matches(scope) - if match == Match.FULL: - route = starlette_route.path - break - if match == Match.PARTIAL: - route = starlette_route.path - # method only exists for http, if websocket - # leave it blank. - span_name = route or scope.get("method", "") - attributes = {} - if route: - attributes[SpanAttributes.HTTP_ROUTE] = route - return span_name, attributes diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/util/http/httplib.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/util/http/httplib.py deleted file mode 100644 index de95a0aa..00000000 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/instrumentation/util/http/httplib.py +++ /dev/null @@ -1,179 +0,0 @@ -# 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. -""" - -import contextlib -import http.client -import logging -import socket # pylint:disable=unused-import # Used for typing -import typing -from typing import Collection - -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__) - - -class HttpClientInstrumentor(BaseInstrumentor): - def instrumentation_dependencies(self) -> Collection[str]: - return () # This instruments http.client from stdlib; no extra deps. - - def _instrument(self, **kwargs): - """Instruments the http.client module (not creating spans on its own)""" - _instrument() - - def _uninstrument(self, **kwargs): - _uninstrument() - - -def _remove_nonrecording(spanlist: typing.List[Span]): - 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=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 = state.get("need_ip") # type: typing.List[Span] - if not spanlist: - return True - - # Remove all non-recording spans from the list. - if not _remove_nonrecording(spanlist): - return True - - sock = "" - try: - sock = conn.sock # type: typing.Optional[socket.socket] - 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, instance: http.client.HTTPConnection, args, kwargs -): - result = wrapped(*args, **kwargs) - trysetip(instance, loglevel=logging.WARNING) - return result - - -def instrument_connect(module, name="connect"): - """Instrument additional connect() methods, e.g. for derived classes.""" - - wrapt.wrap_function_wrapper( - module, - name, - _instrumented_connect, - ) - - -def _instrument(): - def instrumented_send( - wrapped, instance: http.client.HTTPConnection, args, kwargs - ): - 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() - - -def _getstate() -> typing.Optional[dict]: - return 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"] # type: typing.List[Span] - 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/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/util/http/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/util/http/__init__.py deleted file mode 100644 index f3d39ab0..00000000 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/opentelemetry/util/http/__init__.py +++ /dev/null @@ -1,213 +0,0 @@ -# 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 os import environ -from re import IGNORECASE as RE_IGNORECASE -from re import compile as re_compile -from re import search -from typing import Iterable, List -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" -) - -# 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: dict, header_regexes: list, normalize_function: callable - ) -> dict: - values = {} - - if header_regexes: - header_regexes_compiled = re_compile( - "|".join("^" + i + "$" for i in header_regexes), - RE_IGNORECASE, - ) - - for header_name in list( - filter( - header_regexes_compiled.match, - headers.keys(), - ) - ): - header_values = headers.get(header_name) - if header_values: - key = normalize_function(header_name.lower()) - values[key] = [ - self.sanitize_header_value( - header=header_name, value=header_values - ) - ] - - return values - - -_root = r"OTEL_PYTHON_{}" - - -def get_traced_request_attrs(instrumentation): - traced_request_attrs = environ.get( - _root.format(f"{instrumentation}_TRACED_REQUEST_ATTRS"), [] - ) - - if traced_request_attrs: - traced_request_attrs = [ - traced_request_attr.strip() - for traced_request_attr in traced_request_attrs.split(",") - ] - - return traced_request_attrs - - -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 = ( - (":".join(((parsed_url.hostname or ""), str(parsed_url.port)))) - if parsed_url.port - else (parsed_url.hostname or "") - ) - 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}" - - -def get_custom_headers(env_var: str) -> List[str]: - custom_headers = environ.get(env_var, []) - if custom_headers: - custom_headers = [ - custom_headers.strip() - for custom_headers in custom_headers.split(",") - ] - return custom_headers - - -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 diff --git a/azure-monitor-opentelemetry/samples/tracing/manual.py b/azure-monitor-opentelemetry/samples/tracing/manual.py index 7bbf1e50..9466f9cf 100644 --- a/azure-monitor-opentelemetry/samples/tracing/manual.py +++ b/azure-monitor-opentelemetry/samples/tracing/manual.py @@ -4,7 +4,7 @@ # license information. # -------------------------------------------------------------------------- from azure.monitor.opentelemetry import configure_azure_monitor -from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor from sqlalchemy import create_engine, text configure_azure_monitor() diff --git a/azure-monitor-opentelemetry/setup.py b/azure-monitor-opentelemetry/setup.py index 9844da4f..1f76d22c 100644 --- a/azure-monitor-opentelemetry/setup.py +++ b/azure-monitor-opentelemetry/setup.py @@ -85,14 +85,6 @@ python_requires=">=3.7", install_requires=[ "azure-monitor-opentelemetry-exporter>=1.0.0b13", - "opentelemetry-instrumentation~=0.38b0", - # "opentelemetry-instrumentation-django~=0.38b0", - # "opentelemetry-instrumentation-fastapi~=0.38b0", - # "opentelemetry-instrumentation-flask~=0.38b0", - # "opentelemetry-instrumentation-psycopg2~=0.38b0", - # "opentelemetry-instrumentation-requests~=0.38b0", - # "opentelemetry-instrumentation-urllib~=0.38b0", - # "opentelemetry-instrumentation-urllib3~=0.38b0", "opentelemetry-api==1.17.0", "opentelemetry-sdk==1.17.0", "wrapt >= 1.0.0, < 2.0.0", @@ -105,13 +97,13 @@ "azure_monitor_opentelemetry_configurator = azure.monitor.opentelemetry.autoinstrumentation._configurator:AzureMonitorConfigurator" ], "azure_monitor_opentelemetry_instrumentor": [ - "django = azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.django:DjangoInstrumentor", - "fastapi = azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.fastapi:FastAPIInstrumentor", - "flask = azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.flask:FlaskInstrumentor", - "psycopg2 = azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.psycopg2:Psycopg2Instrumentor", - "requests = azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.requests:RequestsInstrumentor", - "urllib = azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.urllib:URLLibInstrumentor", - "urllib3 = azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.urllib3:URLLib3Instrumentor", + "django = azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.django:DjangoInstrumentor", + "fastapi = azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.fastapi:FastAPIInstrumentor", + "flask = azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.flask:FlaskInstrumentor", + "psycopg2 = azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.psycopg2:Psycopg2Instrumentor", + "requests = azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.requests:RequestsInstrumentor", + "urllib = azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.urllib:URLLibInstrumentor", + "urllib3 = azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.urllib3:URLLib3Instrumentor", ], }, ) diff --git a/azure-monitor-opentelemetry/tests/configuration/test_configure.py b/azure-monitor-opentelemetry/tests/configuration/test_configure.py index b2057098..50c7731e 100644 --- a/azure-monitor-opentelemetry/tests/configuration/test_configure.py +++ b/azure-monitor-opentelemetry/tests/configuration/test_configure.py @@ -16,7 +16,7 @@ from unittest.mock import Mock, patch from azure.monitor.opentelemetry._configure import ( - _SUPPORTED_INSTRUMENTED_LIBRARIES_TO_INSTRUMENTS_MAP, + _SUPPORTED_INSTRUMENTED_LIBRARIES_DEPENDENCIES_MAP, _setup_instrumentations, _setup_logging, _setup_metrics, @@ -336,13 +336,13 @@ def test_setup_instrumentations_lib_not_supported( instr_class_mock.return_value = instrumentor_mock ep_mock.name = "test_instr" ep2_mock.name = list( - _SUPPORTED_INSTRUMENTED_LIBRARIES_TO_INSTRUMENTS_MAP.keys() + _SUPPORTED_INSTRUMENTED_LIBRARIES_DEPENDENCIES_MAP.keys() )[0] ep2_mock.load.return_value = instr_class_mock dep_mock.return_value = None _setup_instrumentations() dep_mock.assert_called_with( - _SUPPORTED_INSTRUMENTED_LIBRARIES_TO_INSTRUMENTS_MAP[ep2_mock.name] + _SUPPORTED_INSTRUMENTED_LIBRARIES_DEPENDENCIES_MAP[ep2_mock.name] ) ep_mock.load.assert_not_called() ep2_mock.load.assert_called_once() @@ -363,13 +363,13 @@ def test_setup_instrumentations_conflict( instr_class_mock = Mock() instr_class_mock.return_value = instrumentor_mock ep_mock.name = list( - _SUPPORTED_INSTRUMENTED_LIBRARIES_TO_INSTRUMENTS_MAP.keys() + _SUPPORTED_INSTRUMENTED_LIBRARIES_DEPENDENCIES_MAP.keys() )[0] ep_mock.load.return_value = instr_class_mock dep_mock.return_value = True _setup_instrumentations() dep_mock.assert_called_with( - _SUPPORTED_INSTRUMENTED_LIBRARIES_TO_INSTRUMENTS_MAP[ep_mock.name] + _SUPPORTED_INSTRUMENTED_LIBRARIES_DEPENDENCIES_MAP[ep_mock.name] ) ep_mock.load.assert_not_called() instrumentor_mock.instrument.assert_not_called() @@ -390,13 +390,13 @@ def test_setup_instrumentations_exception( instr_class_mock = Mock() instr_class_mock.return_value = instrumentor_mock ep_mock.name = list( - _SUPPORTED_INSTRUMENTED_LIBRARIES_TO_INSTRUMENTS_MAP.keys() + _SUPPORTED_INSTRUMENTED_LIBRARIES_DEPENDENCIES_MAP.keys() )[0] ep_mock.load.side_effect = Exception() dep_mock.return_value = None _setup_instrumentations() dep_mock.assert_called_with( - _SUPPORTED_INSTRUMENTED_LIBRARIES_TO_INSTRUMENTS_MAP[ep_mock.name] + _SUPPORTED_INSTRUMENTED_LIBRARIES_DEPENDENCIES_MAP[ep_mock.name] ) ep_mock.load.assert_called_once() instrumentor_mock.instrument.assert_not_called() diff --git a/azure-monitor-opentelemetry/tests/instrumentation/test_django.py b/azure-monitor-opentelemetry/tests/instrumentation/test_django.py index 8033853d..14c30749 100644 --- a/azure-monitor-opentelemetry/tests/instrumentation/test_django.py +++ b/azure-monitor-opentelemetry/tests/instrumentation/test_django.py @@ -6,7 +6,7 @@ import unittest -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.django import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.django import ( DjangoInstrumentor, ) diff --git a/azure-monitor-opentelemetry/tests/instrumentation/test_fastapi.py b/azure-monitor-opentelemetry/tests/instrumentation/test_fastapi.py index e2b015b5..c3d7f66b 100644 --- a/azure-monitor-opentelemetry/tests/instrumentation/test_fastapi.py +++ b/azure-monitor-opentelemetry/tests/instrumentation/test_fastapi.py @@ -6,7 +6,7 @@ import unittest -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.fastapi import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.fastapi import ( FastAPIInstrumentor, ) diff --git a/azure-monitor-opentelemetry/tests/instrumentation/test_flask.py b/azure-monitor-opentelemetry/tests/instrumentation/test_flask.py index c2e04220..a20a23c7 100644 --- a/azure-monitor-opentelemetry/tests/instrumentation/test_flask.py +++ b/azure-monitor-opentelemetry/tests/instrumentation/test_flask.py @@ -6,7 +6,7 @@ import unittest -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.flask import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.flask import ( FlaskInstrumentor, ) diff --git a/azure-monitor-opentelemetry/tests/instrumentation/test_psycopg2.py b/azure-monitor-opentelemetry/tests/instrumentation/test_psycopg2.py index 0d74879a..91a4968b 100644 --- a/azure-monitor-opentelemetry/tests/instrumentation/test_psycopg2.py +++ b/azure-monitor-opentelemetry/tests/instrumentation/test_psycopg2.py @@ -6,7 +6,7 @@ import unittest -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.psycopg2 import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.psycopg2 import ( Psycopg2Instrumentor, ) diff --git a/azure-monitor-opentelemetry/tests/instrumentation/test_requests.py b/azure-monitor-opentelemetry/tests/instrumentation/test_requests.py index 7aaab597..9db55df2 100644 --- a/azure-monitor-opentelemetry/tests/instrumentation/test_requests.py +++ b/azure-monitor-opentelemetry/tests/instrumentation/test_requests.py @@ -6,7 +6,7 @@ import unittest -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.requests import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.requests import ( RequestsInstrumentor, ) diff --git a/azure-monitor-opentelemetry/tests/instrumentation/test_urllib.py b/azure-monitor-opentelemetry/tests/instrumentation/test_urllib.py index 56c79ccf..e6419fed 100644 --- a/azure-monitor-opentelemetry/tests/instrumentation/test_urllib.py +++ b/azure-monitor-opentelemetry/tests/instrumentation/test_urllib.py @@ -6,7 +6,7 @@ import unittest -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.urllib import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.urllib import ( URLLibInstrumentor, ) diff --git a/azure-monitor-opentelemetry/tests/instrumentation/test_urllib3.py b/azure-monitor-opentelemetry/tests/instrumentation/test_urllib3.py index 0143ff2e..31b68006 100644 --- a/azure-monitor-opentelemetry/tests/instrumentation/test_urllib3.py +++ b/azure-monitor-opentelemetry/tests/instrumentation/test_urllib3.py @@ -6,7 +6,7 @@ import unittest -from azure.monitor.opentelemetry.vendor.opentelemetry.instrumentation.urllib3 import ( +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.urllib3 import ( URLLib3Instrumentor, ) From 7d3f090ea05eee444dab14b8af5b98b3421c4090 Mon Sep 17 00:00:00 2001 From: jerevoss Date: Wed, 31 May 2023 15:40:29 -0700 Subject: [PATCH 06/10] lint --- .../_vendor/opentelemetry/instrumentation/distro.py | 1 - .../monitor/opentelemetry/autoinstrumentation/_distro.py | 4 +++- azure-monitor-opentelemetry/samples/tracing/manual.py | 4 +++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/distro.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/distro.py index e7403bfb..1e01eb06 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/distro.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/distro.py @@ -35,7 +35,6 @@ class BaseDistro(ABC): _instance = None def __new__(cls, *args, **kwargs): - if cls._instance is None: cls._instance = object.__new__(cls, *args, **kwargs) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/autoinstrumentation/_distro.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/autoinstrumentation/_distro.py index 0a7ff16f..852a3297 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/autoinstrumentation/_distro.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/autoinstrumentation/_distro.py @@ -17,7 +17,9 @@ OTEL_METRICS_EXPORTER, OTEL_TRACES_EXPORTER, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.distro import BaseDistro +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.distro import ( + BaseDistro, +) from opentelemetry.sdk.environment_variables import ( _OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED, ) diff --git a/azure-monitor-opentelemetry/samples/tracing/manual.py b/azure-monitor-opentelemetry/samples/tracing/manual.py index 9466f9cf..c20ba214 100644 --- a/azure-monitor-opentelemetry/samples/tracing/manual.py +++ b/azure-monitor-opentelemetry/samples/tracing/manual.py @@ -4,7 +4,9 @@ # license information. # -------------------------------------------------------------------------- from azure.monitor.opentelemetry import configure_azure_monitor -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.sqlalchemy import ( + SQLAlchemyInstrumentor, +) from sqlalchemy import create_engine, text configure_azure_monitor() From ffb4fb36921b21ada61fb93ee4100dd179cf98c9 Mon Sep 17 00:00:00 2001 From: jerevoss Date: Wed, 31 May 2023 16:04:46 -0700 Subject: [PATCH 07/10] switched lint to ignore _vendor --- .flake8 | 2 +- .isort.cfg | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.flake8 b/.flake8 index c725e893..0cbf13c9 100644 --- a/.flake8 +++ b/.flake8 @@ -25,4 +25,4 @@ exclude = target __pycache__ */build/lib/* - azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/* + azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/* diff --git a/.isort.cfg b/.isort.cfg index 1cebe04e..ba49faf4 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -14,5 +14,5 @@ profile=black ; docs: https://github.com/timothycrosley/isort#multi-line-output-modes multi_line_output=3 skip=target -skip_glob=**/gen/*,.venv*/*,venv*/*,**/proto/*,.tox/*, azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor/* +skip_glob=**/gen/*,.venv*/*,venv*/*,**/proto/*,.tox/*, azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/* known_third_party=opentelemetry,psutil,pytest,redis,redis_opentracing diff --git a/pyproject.toml b/pyproject.toml index 7e62d134..94dff567 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ exclude = ''' venv| .*/build/lib/.*| scripts| - azure-monitor-opentelemetry/azure/monitor/opentelemetry/vendor| + azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor| )/ ) ''' From b810aed7630fe19478fc80e8a190953f1df8c2c0 Mon Sep 17 00:00:00 2001 From: jerevoss Date: Wed, 31 May 2023 16:27:55 -0700 Subject: [PATCH 08/10] nt --- .../azure/monitor/opentelemetry/_configure.py | 12 ++++++------ .../opentelemetry/autoinstrumentation/_distro.py | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py index 979a8254..5b928e5a 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py @@ -14,6 +14,12 @@ SAMPLING_RATIO_ARG, ) from azure.monitor.opentelemetry._types import ConfigurationValue +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.dependencies import ( + get_dependency_conflicts, +) +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.instrumentor import ( + BaseInstrumentor, +) from azure.monitor.opentelemetry.exporter import ( ApplicationInsightsSampler, AzureMonitorLogExporter, @@ -22,12 +28,6 @@ ) from azure.monitor.opentelemetry.util.configurations import _get_configurations from opentelemetry._logs import get_logger_provider, set_logger_provider -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.dependencies import ( - get_dependency_conflicts, -) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.instrumentor import ( - BaseInstrumentor, -) from opentelemetry.metrics import set_meter_provider from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler from opentelemetry.sdk._logs.export import BatchLogRecordProcessor diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/autoinstrumentation/_distro.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/autoinstrumentation/_distro.py index 852a3297..3603415a 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/autoinstrumentation/_distro.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/autoinstrumentation/_distro.py @@ -6,6 +6,9 @@ import logging from os import environ +from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.distro import ( + BaseDistro, +) from azure.monitor.opentelemetry.diagnostics._diagnostic_logging import ( AzureDiagnosticLogging, ) @@ -17,9 +20,6 @@ OTEL_METRICS_EXPORTER, OTEL_TRACES_EXPORTER, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.distro import ( - BaseDistro, -) from opentelemetry.sdk.environment_variables import ( _OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED, ) From 58f20ec1396643782ed28b70d7b1c52181bb8b63 Mon Sep 17 00:00:00 2001 From: jerevoss Date: Wed, 31 May 2023 17:00:17 -0700 Subject: [PATCH 09/10] added v0_38b0 subfolder --- .../azure/monitor/opentelemetry/_configure.py | 4 +-- .../{opentelemetry => v0_38b0}/__init__.py | 0 .../opentelemetry}/__init__.py | 0 .../instrumentation}/__init__.py | 0 .../instrumentation/asgi/__init__.py | 8 +++--- .../instrumentation/asgi/package.py | 0 .../instrumentation/asgi/version.py | 0 .../auto_instrumentation/__init__.py | 2 +- .../auto_instrumentation/sitecustomize.py | 10 +++---- .../instrumentation/bootstrap.py | 4 +-- .../instrumentation/bootstrap_gen.py | 0 .../instrumentation/dbapi/__init__.py | 6 ++--- .../instrumentation/dbapi/package.py | 0 .../instrumentation/dbapi/version.py | 0 .../instrumentation/dependencies.py | 0 .../opentelemetry/instrumentation/distro.py | 2 +- .../instrumentation/django/__init__.py | 14 +++++----- .../django/environment_variables.py | 0 .../django/middleware/__init__.py | 0 .../django/middleware/otel_middleware.py | 26 +++++++++---------- .../middleware/sqlcommenter_middleware.py | 4 +-- .../instrumentation/django/package.py | 0 .../instrumentation/django/version.py | 0 .../instrumentation/environment_variables.py | 0 .../instrumentation/fastapi/__init__.py | 10 +++---- .../instrumentation/fastapi/package.py | 0 .../instrumentation/fastapi/version.py | 0 .../instrumentation/flask/__init__.py | 14 +++++----- .../instrumentation/flask/package.py | 0 .../instrumentation/flask/version.py | 0 .../instrumentation/instrumentor.py | 2 +- .../instrumentation/propagators.py | 0 .../instrumentation/psycopg2/__init__.py | 8 +++--- .../instrumentation/psycopg2/package.py | 0 .../instrumentation/psycopg2/version.py | 0 .../opentelemetry/instrumentation/py.typed | 0 .../instrumentation/requests/__init__.py | 12 ++++----- .../instrumentation/requests/package.py | 0 .../instrumentation/requests/version.py | 0 .../instrumentation/sqlcommenter_utils.py | 2 +- .../instrumentation/urllib/__init__.py | 10 +++---- .../instrumentation/urllib/package.py | 0 .../instrumentation/urllib/version.py | 0 .../instrumentation/urllib3/__init__.py | 10 +++---- .../instrumentation/urllib3/package.py | 0 .../instrumentation/urllib3/version.py | 0 .../opentelemetry/instrumentation/utils.py | 0 .../opentelemetry/instrumentation/version.py | 0 .../instrumentation/wsgi/__init__.py | 8 +++--- .../instrumentation/wsgi/package.py | 0 .../instrumentation/wsgi/version.py | 0 .../v0_38b0/opentelemetry/util/__init__.py | 5 ++++ .../opentelemetry/util/http/__init__.py | 0 .../opentelemetry/util/http/httplib.py | 4 +-- .../opentelemetry/util/http/version.py | 0 .../autoinstrumentation/_distro.py | 2 +- .../samples/tracing/manual.py | 2 +- azure-monitor-opentelemetry/setup.py | 14 +++++----- .../tests/instrumentation/test_django.py | 2 +- .../tests/instrumentation/test_fastapi.py | 2 +- .../tests/instrumentation/test_flask.py | 2 +- .../tests/instrumentation/test_psycopg2.py | 2 +- .../tests/instrumentation/test_requests.py | 2 +- .../tests/instrumentation/test_urllib.py | 2 +- .../tests/instrumentation/test_urllib3.py | 2 +- 65 files changed, 101 insertions(+), 96 deletions(-) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{opentelemetry => v0_38b0}/__init__.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{opentelemetry/instrumentation => v0_38b0/opentelemetry}/__init__.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{opentelemetry/util => v0_38b0/opentelemetry/instrumentation}/__init__.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/asgi/__init__.py (97%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/asgi/package.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/asgi/version.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/auto_instrumentation/__init__.py (97%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/auto_instrumentation/sitecustomize.py (89%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/bootstrap.py (95%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/bootstrap_gen.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/dbapi/__init__.py (98%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/dbapi/package.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/dbapi/version.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/dependencies.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/distro.py (95%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/django/__init__.py (89%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/django/environment_variables.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/django/middleware/__init__.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/django/middleware/otel_middleware.py (92%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/django/middleware/sqlcommenter_middleware.py (95%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/django/package.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/django/version.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/environment_variables.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/fastapi/__init__.py (93%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/fastapi/package.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/fastapi/version.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/flask/__init__.py (95%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/flask/package.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/flask/version.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/instrumentor.py (97%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/propagators.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/psycopg2/__init__.py (93%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/psycopg2/package.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/psycopg2/version.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/py.typed (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/requests/__init__.py (94%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/requests/package.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/requests/version.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/sqlcommenter_utils.py (95%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/urllib/__init__.py (94%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/urllib/package.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/urllib/version.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/urllib3/__init__.py (95%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/urllib3/package.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/urllib3/version.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/utils.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/version.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/wsgi/__init__.py (97%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/wsgi/package.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/instrumentation/wsgi/version.py (100%) create mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/util/__init__.py rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/util/http/__init__.py (100%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/util/http/httplib.py (96%) rename azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/{ => v0_38b0}/opentelemetry/util/http/version.py (100%) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py index 5b928e5a..e1b2aa32 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py @@ -14,10 +14,10 @@ SAMPLING_RATIO_ARG, ) from azure.monitor.opentelemetry._types import ConfigurationValue -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.dependencies import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.dependencies import ( get_dependency_conflicts, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.instrumentor import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.instrumentor import ( BaseInstrumentor, ) from azure.monitor.opentelemetry.exporter import ( diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/__init__.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/__init__.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/__init__.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/__init__.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/util/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/__init__.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/util/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/__init__.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/asgi/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/asgi/__init__.py similarity index 97% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/asgi/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/asgi/__init__.py index 02395303..8f09415c 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/asgi/__init__.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/asgi/__init__.py @@ -23,13 +23,13 @@ from asgiref.compatibility import guarantee_single_callable from opentelemetry import context, trace -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.asgi.version import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.asgi.version import ( __version__, ) # noqa -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.propagators import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.propagators import ( get_global_response_propagator, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.utils import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.utils import ( _start_internal_or_server_span, http_status_to_status_code, ) @@ -39,7 +39,7 @@ from opentelemetry.semconv.trace import SpanAttributes from opentelemetry.trace import Span, set_span_in_context from opentelemetry.trace.status import Status, StatusCode -from azure.monitor.opentelemetry._vendor.opentelemetry.util.http import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.util.http import ( OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS, OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST, OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE, diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/asgi/package.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/asgi/package.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/asgi/package.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/asgi/package.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/asgi/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/asgi/version.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/asgi/version.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/asgi/version.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/auto_instrumentation/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/auto_instrumentation/__init__.py similarity index 97% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/auto_instrumentation/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/auto_instrumentation/__init__.py index 025d6588..7f08a062 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/auto_instrumentation/__init__.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/auto_instrumentation/__init__.py @@ -23,7 +23,7 @@ from pkg_resources import iter_entry_points -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.version import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.version import ( __version__, ) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/auto_instrumentation/sitecustomize.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/auto_instrumentation/sitecustomize.py similarity index 89% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/auto_instrumentation/sitecustomize.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/auto_instrumentation/sitecustomize.py index 3751a40c..f66c2ae4 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/auto_instrumentation/sitecustomize.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/auto_instrumentation/sitecustomize.py @@ -18,20 +18,20 @@ from pkg_resources import iter_entry_points -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.dependencies import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.dependencies import ( get_dist_dependency_conflicts, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.distro import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.distro import ( BaseDistro, DefaultDistro, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.environment_variables import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.environment_variables import ( OTEL_PYTHON_DISABLED_INSTRUMENTATIONS, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.utils import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.utils import ( _python_path_without_directory, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.version import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.version import ( __version__, ) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/bootstrap.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/bootstrap.py similarity index 95% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/bootstrap.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/bootstrap.py index afb6ed34..f80eccc9 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/bootstrap.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/bootstrap.py @@ -21,11 +21,11 @@ import pkg_resources -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.bootstrap_gen import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.bootstrap_gen import ( default_instrumentations, libraries, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.version import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.version import ( __version__, ) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/bootstrap_gen.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/bootstrap_gen.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/bootstrap_gen.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/bootstrap_gen.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/dbapi/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/dbapi/__init__.py similarity index 98% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/dbapi/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/dbapi/__init__.py index 09b2d673..60016ae1 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/dbapi/__init__.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/dbapi/__init__.py @@ -21,13 +21,13 @@ import wrapt from opentelemetry import trace as trace_api -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.dbapi.version import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.dbapi.version import ( __version__, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.sqlcommenter_utils import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.sqlcommenter_utils import ( _add_sql_comment, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.utils import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.utils import ( _get_opentelemetry_values, unwrap, ) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/dbapi/package.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/dbapi/package.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/dbapi/package.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/dbapi/package.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/dbapi/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/dbapi/version.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/dbapi/version.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/dbapi/version.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/dependencies.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/dependencies.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/dependencies.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/dependencies.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/distro.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/distro.py similarity index 95% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/distro.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/distro.py index 1e01eb06..decc6ab3 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/distro.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/distro.py @@ -22,7 +22,7 @@ from pkg_resources import EntryPoint -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.instrumentor import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.instrumentor import ( BaseInstrumentor, ) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/django/__init__.py similarity index 89% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/django/__init__.py index f47af874..d1a1509f 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/__init__.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/django/__init__.py @@ -21,25 +21,25 @@ from django.conf import settings from django.core.exceptions import ImproperlyConfigured -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.django.environment_variables import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.django.environment_variables import ( OTEL_PYTHON_DJANGO_INSTRUMENT, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.django.middleware.otel_middleware import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.django.middleware.otel_middleware import ( _DjangoMiddleware, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.django.package import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.django.package import ( _instruments, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.django.version import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.django.version import ( __version__, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.instrumentor import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.instrumentor import ( BaseInstrumentor, ) from opentelemetry.metrics import get_meter from opentelemetry.semconv.metrics import MetricInstruments from opentelemetry.trace import get_tracer -from azure.monitor.opentelemetry._vendor.opentelemetry.util.http import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.util.http import ( get_excluded_urls, parse_excluded_urls, ) @@ -69,7 +69,7 @@ class DjangoInstrumentor(BaseInstrumentor): [_DjangoMiddleware.__module__, _DjangoMiddleware.__qualname__] ) - _sql_commenter_middleware = "azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.django.middleware.sqlcommenter_middleware.SqlCommenter" + _sql_commenter_middleware = "azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.django.middleware.sqlcommenter_middleware.SqlCommenter" def instrumentation_dependencies(self) -> Collection[str]: return _instruments diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/environment_variables.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/django/environment_variables.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/environment_variables.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/django/environment_variables.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/middleware/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/django/middleware/__init__.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/middleware/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/django/middleware/__init__.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/middleware/otel_middleware.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/django/middleware/otel_middleware.py similarity index 92% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/middleware/otel_middleware.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/django/middleware/otel_middleware.py index f7e34c3c..8c3231eb 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/middleware/otel_middleware.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/django/middleware/otel_middleware.py @@ -22,31 +22,31 @@ from django.http import HttpRequest, HttpResponse from opentelemetry.context import detach -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.propagators import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.propagators import ( get_global_response_propagator, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.utils import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.utils import ( _start_internal_or_server_span, extract_attributes_from_object, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.wsgi import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.wsgi import ( add_response_attributes, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.wsgi import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.wsgi import ( collect_custom_request_headers_attributes as wsgi_collect_custom_request_headers_attributes, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.wsgi import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.wsgi import ( collect_custom_response_headers_attributes as wsgi_collect_custom_response_headers_attributes, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.wsgi import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.wsgi import ( collect_request_attributes as wsgi_collect_request_attributes, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.wsgi import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.wsgi import ( wsgi_getter, ) from opentelemetry.semconv.trace import SpanAttributes from opentelemetry.trace import Span, SpanKind, use_span -from azure.monitor.opentelemetry._vendor.opentelemetry.util.http import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.util.http import ( _parse_active_request_count_attrs, _parse_duration_attrs, get_excluded_urls, @@ -93,20 +93,20 @@ def __call__(self, request): # try/except block exclusive for optional ASGI imports. try: - from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.asgi import ( + from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.asgi import ( asgi_getter, asgi_setter, ) - from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.asgi import ( + from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.asgi import ( collect_custom_request_headers_attributes as asgi_collect_custom_request_attributes, ) - from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.asgi import ( + from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.asgi import ( collect_custom_response_headers_attributes as asgi_collect_custom_response_attributes, ) - from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.asgi import ( + from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.asgi import ( collect_request_attributes as asgi_collect_request_attributes, ) - from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.asgi import ( + from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.asgi import ( set_status_code, ) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/middleware/sqlcommenter_middleware.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/django/middleware/sqlcommenter_middleware.py similarity index 95% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/middleware/sqlcommenter_middleware.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/django/middleware/sqlcommenter_middleware.py index 5a4a0149..d8e653bb 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/middleware/sqlcommenter_middleware.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/django/middleware/sqlcommenter_middleware.py @@ -22,10 +22,10 @@ from django.db import connections from django.db.backends.utils import CursorDebugWrapper -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.sqlcommenter_utils import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.sqlcommenter_utils import ( _add_sql_comment, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.utils import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.utils import ( _get_opentelemetry_values, ) from opentelemetry.trace.propagation.tracecontext import ( diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/package.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/django/package.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/package.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/django/package.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/django/version.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/django/version.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/django/version.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/environment_variables.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/environment_variables.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/environment_variables.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/environment_variables.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/fastapi/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/fastapi/__init__.py similarity index 93% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/fastapi/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/fastapi/__init__.py index 42b86cf9..669f1ef3 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/fastapi/__init__.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/fastapi/__init__.py @@ -20,22 +20,22 @@ import fastapi from starlette.routing import Match -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.asgi import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.asgi import ( OpenTelemetryMiddleware, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.fastapi.package import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.fastapi.package import ( _instruments, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.fastapi.version import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.fastapi.version import ( __version__, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.instrumentor import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.instrumentor import ( BaseInstrumentor, ) from opentelemetry.metrics import get_meter from opentelemetry.semconv.trace import SpanAttributes from opentelemetry.trace import Span -from azure.monitor.opentelemetry._vendor.opentelemetry.util.http import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.util.http import ( get_excluded_urls, parse_excluded_urls, ) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/fastapi/package.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/fastapi/package.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/fastapi/package.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/fastapi/package.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/fastapi/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/fastapi/version.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/fastapi/version.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/fastapi/version.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/flask/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/flask/__init__.py similarity index 95% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/flask/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/flask/__init__.py index c1681da7..815be63b 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/flask/__init__.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/flask/__init__.py @@ -24,27 +24,27 @@ import flask -import azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.wsgi as otel_wsgi +import azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.wsgi as otel_wsgi from opentelemetry import context, trace -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.flask.package import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.flask.package import ( _instruments, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.flask.version import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.flask.version import ( __version__, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.instrumentor import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.instrumentor import ( BaseInstrumentor, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.propagators import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.propagators import ( get_global_response_propagator, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.utils import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.utils import ( _start_internal_or_server_span, ) from opentelemetry.metrics import get_meter from opentelemetry.semconv.metrics import MetricInstruments from opentelemetry.semconv.trace import SpanAttributes -from azure.monitor.opentelemetry._vendor.opentelemetry.util.http import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.util.http import ( get_excluded_urls, parse_excluded_urls, ) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/flask/package.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/flask/package.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/flask/package.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/flask/package.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/flask/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/flask/version.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/flask/version.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/flask/version.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/instrumentor.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/instrumentor.py similarity index 97% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/instrumentor.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/instrumentor.py index 520f1dc8..8403edb8 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/instrumentor.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/instrumentor.py @@ -21,7 +21,7 @@ from logging import getLogger from typing import Collection, Optional -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.dependencies import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.dependencies import ( DependencyConflict, get_dependency_conflicts, ) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/propagators.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/propagators.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/propagators.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/propagators.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/psycopg2/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/psycopg2/__init__.py similarity index 93% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/psycopg2/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/psycopg2/__init__.py index 820496bf..5d7fde0e 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/psycopg2/__init__.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/psycopg2/__init__.py @@ -23,16 +23,16 @@ ) from psycopg2.sql import Composed # pylint: disable=no-name-in-module -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation import ( dbapi, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.instrumentor import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.instrumentor import ( BaseInstrumentor, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.psycopg2.package import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.psycopg2.package import ( _instruments, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.psycopg2.version import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.psycopg2.version import ( __version__, ) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/psycopg2/package.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/psycopg2/package.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/psycopg2/package.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/psycopg2/package.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/psycopg2/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/psycopg2/version.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/psycopg2/version.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/psycopg2/version.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/py.typed b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/py.typed similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/py.typed rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/py.typed diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/requests/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/requests/__init__.py similarity index 94% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/requests/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/requests/__init__.py index af7a348e..cb606dd0 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/requests/__init__.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/requests/__init__.py @@ -27,16 +27,16 @@ # FIXME: fix the importing of this private attribute when the location of the _SUPPRESS_HTTP_INSTRUMENTATION_KEY is defined. from opentelemetry.context import _SUPPRESS_HTTP_INSTRUMENTATION_KEY -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.instrumentor import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.instrumentor import ( BaseInstrumentor, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.requests.package import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.requests.package import ( _instruments, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.requests.version import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.requests.version import ( __version__, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.utils import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.utils import ( _SUPPRESS_INSTRUMENTATION_KEY, http_status_to_status_code, ) @@ -47,12 +47,12 @@ from opentelemetry.trace import SpanKind, Tracer, get_tracer from opentelemetry.trace.span import Span from opentelemetry.trace.status import Status -from azure.monitor.opentelemetry._vendor.opentelemetry.util.http import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.util.http import ( get_excluded_urls, parse_excluded_urls, remove_url_credentials, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.util.http.httplib import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.util.http.httplib import ( set_ip_on_next_http_connection, ) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/requests/package.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/requests/package.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/requests/package.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/requests/package.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/requests/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/requests/version.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/requests/version.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/requests/version.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/sqlcommenter_utils.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/sqlcommenter_utils.py similarity index 95% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/sqlcommenter_utils.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/sqlcommenter_utils.py index 3616ea3c..404409c7 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/sqlcommenter_utils.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/sqlcommenter_utils.py @@ -13,7 +13,7 @@ # limitations under the License. from opentelemetry import context -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.utils import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.utils import ( _url_quote, ) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/urllib/__init__.py similarity index 94% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/urllib/__init__.py index 50c4ee06..33d96fed 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib/__init__.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/urllib/__init__.py @@ -28,16 +28,16 @@ # FIXME: fix the importing of this private attribute when the location of the _SUPPRESS_HTTP_INSTRUMENTATION_KEY is defined. from opentelemetry.context import _SUPPRESS_HTTP_INSTRUMENTATION_KEY -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.instrumentor import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.instrumentor import ( BaseInstrumentor, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.urllib.package import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.urllib.package import ( _instruments, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.urllib.version import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.urllib.version import ( __version__, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.utils import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.utils import ( _SUPPRESS_INSTRUMENTATION_KEY, http_status_to_status_code, ) @@ -47,7 +47,7 @@ from opentelemetry.semconv.trace import SpanAttributes from opentelemetry.trace import Span, SpanKind, get_tracer from opentelemetry.trace.status import Status -from azure.monitor.opentelemetry._vendor.opentelemetry.util.http import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.util.http import ( remove_url_credentials, ) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib/package.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/urllib/package.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib/package.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/urllib/package.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/urllib/version.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib/version.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/urllib/version.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib3/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/urllib3/__init__.py similarity index 95% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib3/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/urllib3/__init__.py index 7291d0f9..78956fd1 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib3/__init__.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/urllib3/__init__.py @@ -27,16 +27,16 @@ # FIXME: fix the importing of this private attribute when the location of the _SUPPRESS_HTTP_INSTRUMENTATION_KEY is defined. from opentelemetry.context import _SUPPRESS_HTTP_INSTRUMENTATION_KEY -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.instrumentor import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.instrumentor import ( BaseInstrumentor, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.urllib3.package import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.urllib3.package import ( _instruments, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.urllib3.version import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.urllib3.version import ( __version__, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.utils import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.utils import ( _SUPPRESS_INSTRUMENTATION_KEY, http_status_to_status_code, unwrap, @@ -47,7 +47,7 @@ from opentelemetry.semconv.trace import SpanAttributes from opentelemetry.trace import Span, SpanKind, Tracer, get_tracer from opentelemetry.trace.status import Status -from azure.monitor.opentelemetry._vendor.opentelemetry.util.http.httplib import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.util.http.httplib import ( set_ip_on_next_http_connection, ) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib3/package.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/urllib3/package.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib3/package.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/urllib3/package.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib3/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/urllib3/version.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/urllib3/version.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/urllib3/version.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/utils.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/utils.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/utils.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/utils.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/version.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/version.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/version.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/wsgi/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/wsgi/__init__.py similarity index 97% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/wsgi/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/wsgi/__init__.py index bba8211d..6db7b900 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/wsgi/__init__.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/wsgi/__init__.py @@ -19,11 +19,11 @@ from timeit import default_timer from opentelemetry import context, trace -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.utils import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.utils import ( _start_internal_or_server_span, http_status_to_status_code, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.wsgi.version import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.wsgi.version import ( __version__, ) from opentelemetry.metrics import get_meter @@ -31,7 +31,7 @@ from opentelemetry.semconv.metrics import MetricInstruments from opentelemetry.semconv.trace import SpanAttributes from opentelemetry.trace.status import Status, StatusCode -from azure.monitor.opentelemetry._vendor.opentelemetry.util.http import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.util.http import ( OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS, OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST, OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE, @@ -395,7 +395,7 @@ def _end_span_after_iterating(iterable, span, token): context.detach(token) -# TODO: inherit from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.propagators.Setter +# TODO: inherit from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.propagators.Setter class ResponsePropagationSetter: diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/wsgi/package.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/wsgi/package.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/wsgi/package.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/wsgi/package.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/wsgi/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/wsgi/version.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/instrumentation/wsgi/version.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/instrumentation/wsgi/version.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/util/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/util/__init__.py new file mode 100644 index 00000000..0bdee620 --- /dev/null +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/util/__init__.py @@ -0,0 +1,5 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License in the project root for +# license information. +# -------------------------------------------------------------------------- diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/util/http/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/util/http/__init__.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/util/http/__init__.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/util/http/__init__.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/util/http/httplib.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/util/http/httplib.py similarity index 96% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/util/http/httplib.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/util/http/httplib.py index 0ccec5f9..736b332f 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/util/http/httplib.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/util/http/httplib.py @@ -27,10 +27,10 @@ import wrapt from opentelemetry import context -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.instrumentor import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.instrumentor import ( BaseInstrumentor, ) -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.utils import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.utils import ( unwrap, ) from opentelemetry.semconv.trace import SpanAttributes diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/util/http/version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/util/http/version.py similarity index 100% rename from azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/opentelemetry/util/http/version.py rename to azure-monitor-opentelemetry/azure/monitor/opentelemetry/_vendor/v0_38b0/opentelemetry/util/http/version.py diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/autoinstrumentation/_distro.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/autoinstrumentation/_distro.py index 3603415a..1a375d3a 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/autoinstrumentation/_distro.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/autoinstrumentation/_distro.py @@ -6,7 +6,7 @@ import logging from os import environ -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.distro import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.distro import ( BaseDistro, ) from azure.monitor.opentelemetry.diagnostics._diagnostic_logging import ( diff --git a/azure-monitor-opentelemetry/samples/tracing/manual.py b/azure-monitor-opentelemetry/samples/tracing/manual.py index c20ba214..e25907a7 100644 --- a/azure-monitor-opentelemetry/samples/tracing/manual.py +++ b/azure-monitor-opentelemetry/samples/tracing/manual.py @@ -4,7 +4,7 @@ # license information. # -------------------------------------------------------------------------- from azure.monitor.opentelemetry import configure_azure_monitor -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.sqlalchemy import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.sqlalchemy import ( SQLAlchemyInstrumentor, ) from sqlalchemy import create_engine, text diff --git a/azure-monitor-opentelemetry/setup.py b/azure-monitor-opentelemetry/setup.py index 1f76d22c..32b9e1d3 100644 --- a/azure-monitor-opentelemetry/setup.py +++ b/azure-monitor-opentelemetry/setup.py @@ -97,13 +97,13 @@ "azure_monitor_opentelemetry_configurator = azure.monitor.opentelemetry.autoinstrumentation._configurator:AzureMonitorConfigurator" ], "azure_monitor_opentelemetry_instrumentor": [ - "django = azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.django:DjangoInstrumentor", - "fastapi = azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.fastapi:FastAPIInstrumentor", - "flask = azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.flask:FlaskInstrumentor", - "psycopg2 = azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.psycopg2:Psycopg2Instrumentor", - "requests = azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.requests:RequestsInstrumentor", - "urllib = azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.urllib:URLLibInstrumentor", - "urllib3 = azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.urllib3:URLLib3Instrumentor", + "django = azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.django:DjangoInstrumentor", + "fastapi = azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.fastapi:FastAPIInstrumentor", + "flask = azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.flask:FlaskInstrumentor", + "psycopg2 = azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.psycopg2:Psycopg2Instrumentor", + "requests = azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.requests:RequestsInstrumentor", + "urllib = azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.urllib:URLLibInstrumentor", + "urllib3 = azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.urllib3:URLLib3Instrumentor", ], }, ) diff --git a/azure-monitor-opentelemetry/tests/instrumentation/test_django.py b/azure-monitor-opentelemetry/tests/instrumentation/test_django.py index 14c30749..829b3b3c 100644 --- a/azure-monitor-opentelemetry/tests/instrumentation/test_django.py +++ b/azure-monitor-opentelemetry/tests/instrumentation/test_django.py @@ -6,7 +6,7 @@ import unittest -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.django import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.django import ( DjangoInstrumentor, ) diff --git a/azure-monitor-opentelemetry/tests/instrumentation/test_fastapi.py b/azure-monitor-opentelemetry/tests/instrumentation/test_fastapi.py index c3d7f66b..2e165876 100644 --- a/azure-monitor-opentelemetry/tests/instrumentation/test_fastapi.py +++ b/azure-monitor-opentelemetry/tests/instrumentation/test_fastapi.py @@ -6,7 +6,7 @@ import unittest -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.fastapi import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.fastapi import ( FastAPIInstrumentor, ) diff --git a/azure-monitor-opentelemetry/tests/instrumentation/test_flask.py b/azure-monitor-opentelemetry/tests/instrumentation/test_flask.py index a20a23c7..d26071b5 100644 --- a/azure-monitor-opentelemetry/tests/instrumentation/test_flask.py +++ b/azure-monitor-opentelemetry/tests/instrumentation/test_flask.py @@ -6,7 +6,7 @@ import unittest -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.flask import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.flask import ( FlaskInstrumentor, ) diff --git a/azure-monitor-opentelemetry/tests/instrumentation/test_psycopg2.py b/azure-monitor-opentelemetry/tests/instrumentation/test_psycopg2.py index 91a4968b..e9a106d3 100644 --- a/azure-monitor-opentelemetry/tests/instrumentation/test_psycopg2.py +++ b/azure-monitor-opentelemetry/tests/instrumentation/test_psycopg2.py @@ -6,7 +6,7 @@ import unittest -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.psycopg2 import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.psycopg2 import ( Psycopg2Instrumentor, ) diff --git a/azure-monitor-opentelemetry/tests/instrumentation/test_requests.py b/azure-monitor-opentelemetry/tests/instrumentation/test_requests.py index 9db55df2..19798cf7 100644 --- a/azure-monitor-opentelemetry/tests/instrumentation/test_requests.py +++ b/azure-monitor-opentelemetry/tests/instrumentation/test_requests.py @@ -6,7 +6,7 @@ import unittest -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.requests import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.requests import ( RequestsInstrumentor, ) diff --git a/azure-monitor-opentelemetry/tests/instrumentation/test_urllib.py b/azure-monitor-opentelemetry/tests/instrumentation/test_urllib.py index e6419fed..5807625c 100644 --- a/azure-monitor-opentelemetry/tests/instrumentation/test_urllib.py +++ b/azure-monitor-opentelemetry/tests/instrumentation/test_urllib.py @@ -6,7 +6,7 @@ import unittest -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.urllib import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.urllib import ( URLLibInstrumentor, ) diff --git a/azure-monitor-opentelemetry/tests/instrumentation/test_urllib3.py b/azure-monitor-opentelemetry/tests/instrumentation/test_urllib3.py index 31b68006..f3ebaf01 100644 --- a/azure-monitor-opentelemetry/tests/instrumentation/test_urllib3.py +++ b/azure-monitor-opentelemetry/tests/instrumentation/test_urllib3.py @@ -6,7 +6,7 @@ import unittest -from azure.monitor.opentelemetry._vendor.opentelemetry.instrumentation.urllib3 import ( +from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.urllib3 import ( URLLib3Instrumentor, ) From d7ceb65711c646ab6f7ba42a86f2ddc5afd27f16 Mon Sep 17 00:00:00 2001 From: jerevoss Date: Fri, 2 Jun 2023 14:21:55 -0700 Subject: [PATCH 10/10] Removed accidental sample change --- azure-monitor-opentelemetry/samples/tracing/manual.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/azure-monitor-opentelemetry/samples/tracing/manual.py b/azure-monitor-opentelemetry/samples/tracing/manual.py index e25907a7..7bbf1e50 100644 --- a/azure-monitor-opentelemetry/samples/tracing/manual.py +++ b/azure-monitor-opentelemetry/samples/tracing/manual.py @@ -4,9 +4,7 @@ # license information. # -------------------------------------------------------------------------- from azure.monitor.opentelemetry import configure_azure_monitor -from azure.monitor.opentelemetry._vendor.v0_38b0.opentelemetry.instrumentation.sqlalchemy import ( - SQLAlchemyInstrumentor, -) +from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor from sqlalchemy import create_engine, text configure_azure_monitor()