From d9cd6bc1c98b525523b56aafed16fe3e37271f2d Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Thu, 9 Apr 2026 11:52:10 -0700 Subject: [PATCH 1/2] Remove the duplicate azure monitor folder --- CHANGELOG.md | 2 + azure-monitor-opentelemetry/README.md | 11 - azure-monitor-opentelemetry/azure/__init__.py | 1 - .../azure/monitor/__init__.py | 1 - .../azure/monitor/opentelemetry/__init__.py | 18 - .../_autoinstrumentation/__init__.py | 5 - .../_autoinstrumentation/configurator.py | 75 --- .../_autoinstrumentation/distro.py | 68 --- .../_browser_sdk_loader/__init__.py | 122 ----- .../_browser_sdk_loader/_config.py | 48 -- .../_browser_sdk_loader/django_middleware.py | 180 ------- .../_browser_sdk_loader/snippet_injector.py | 497 ------------------ .../azure/monitor/opentelemetry/_configure.py | 470 ----------------- .../azure/monitor/opentelemetry/_constants.py | 98 ---- .../opentelemetry/_diagnostics/__init__.py | 5 - .../_diagnostics/diagnostic_logging.py | 140 ----- .../_diagnostics/status_logger.py | 64 --- .../azure/monitor/opentelemetry/_types.py | 25 - .../monitor/opentelemetry/_utils/__init__.py | 78 --- .../opentelemetry/_utils/configurations.py | 428 --------------- .../opentelemetry/_utils/instrumentation.py | 160 ------ .../azure/monitor/opentelemetry/_version.py | 7 - .../azure/monitor/opentelemetry/py.typed | 7 - azure-monitor-opentelemetry/pyproject.toml | 8 - azure-monitor-opentelemetry/setup.py | 107 ---- 25 files changed, 2 insertions(+), 2623 deletions(-) delete mode 100644 azure-monitor-opentelemetry/README.md delete mode 100644 azure-monitor-opentelemetry/azure/__init__.py delete mode 100644 azure-monitor-opentelemetry/azure/monitor/__init__.py delete mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/__init__.py delete mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_autoinstrumentation/__init__.py delete mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_autoinstrumentation/configurator.py delete mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_autoinstrumentation/distro.py delete mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_browser_sdk_loader/__init__.py delete mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_browser_sdk_loader/_config.py delete mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_browser_sdk_loader/django_middleware.py delete mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_browser_sdk_loader/snippet_injector.py delete mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py delete mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_constants.py delete mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_diagnostics/__init__.py delete mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_diagnostics/diagnostic_logging.py delete mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_diagnostics/status_logger.py delete mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_types.py delete mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_utils/__init__.py delete mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_utils/configurations.py delete mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_utils/instrumentation.py delete mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/_version.py delete mode 100644 azure-monitor-opentelemetry/azure/monitor/opentelemetry/py.typed delete mode 100644 azure-monitor-opentelemetry/pyproject.toml delete mode 100644 azure-monitor-opentelemetry/setup.py diff --git a/CHANGELOG.md b/CHANGELOG.md index a357f80d..77943dbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ ### Other Changes +- Remove the duplicate azure monitor folder + - Add support for local mypy, pylint, black checks ([#14](https://github.com/microsoft/opentelemetry-distro-python/pull/14)) - Add mypy and pyright checks diff --git a/azure-monitor-opentelemetry/README.md b/azure-monitor-opentelemetry/README.md deleted file mode 100644 index 2b05000b..00000000 --- a/azure-monitor-opentelemetry/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# azure-monitor-opentelemetry (Temporary) - -> **This is a temporary copy of the `azure-monitor-opentelemetry` package source.** - -This directory exists solely to validate the integration between the Microsoft OpenTelemetry distro and the Azure Monitor OpenTelemetry pipeline. It allows us to iterate on the distro configuration surface and understand the required changes before they are finalized upstream. - -## Important - -- **This is not the source of truth.** The canonical source for `azure-monitor-opentelemetry` lives in the [Azure SDK for Python](https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/monitor/azure-monitor-opentelemetry) repository. -- **Do not make long-lived changes here.** Any changes required in `azure-monitor-opentelemetry` should be developed in parallel in the actual Azure SDK repository. -- **This copy will be removed** once the upstream package exposes the necessary APIs and the distro can depend on a published release. diff --git a/azure-monitor-opentelemetry/azure/__init__.py b/azure-monitor-opentelemetry/azure/__init__.py deleted file mode 100644 index 8db66d3d..00000000 --- a/azure-monitor-opentelemetry/azure/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__path__ = __import__("pkgutil").extend_path(__path__, __name__) diff --git a/azure-monitor-opentelemetry/azure/monitor/__init__.py b/azure-monitor-opentelemetry/azure/monitor/__init__.py deleted file mode 100644 index 8db66d3d..00000000 --- a/azure-monitor-opentelemetry/azure/monitor/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__path__ = __import__("pkgutil").extend_path(__path__, __name__) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/__init__.py deleted file mode 100644 index c1dbbe51..00000000 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License in the project root for -# license information. -# ------------------------------------------------------------------------- - -# Extend path so the exporter subpackage (azure-monitor-opentelemetry-exporter) -# is discoverable when this package is installed in editable/development mode. -__path__ = __import__("pkgutil").extend_path(__path__, __name__) - -from azure.monitor.opentelemetry._configure import configure_azure_monitor - -from ._version import VERSION - -__all__ = [ - "configure_azure_monitor", -] -__version__ = VERSION diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_autoinstrumentation/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_autoinstrumentation/__init__.py deleted file mode 100644 index dcc72b25..00000000 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_autoinstrumentation/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# ------------------------------------------------------------------------- -# 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/_autoinstrumentation/configurator.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_autoinstrumentation/configurator.py deleted file mode 100644 index c6dbb86a..00000000 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_autoinstrumentation/configurator.py +++ /dev/null @@ -1,75 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License in the project root for -# license information. -# -------------------------------------------------------------------------- - - -from os import environ -from warnings import warn - -from opentelemetry.environment_variables import ( - OTEL_LOGS_EXPORTER, - OTEL_METRICS_EXPORTER, - OTEL_TRACES_EXPORTER, -) -from opentelemetry.sdk._configuration import _OTelSDKConfigurator - -from azure.monitor.opentelemetry.exporter import ( # pylint: disable=import-error,no-name-in-module - ApplicationInsightsSampler, -) -from azure.monitor.opentelemetry.exporter._utils import ( # pylint: disable=import-error,no-name-in-module - _is_attach_enabled, -) -from azure.monitor.opentelemetry._constants import ( - _PREVIEW_ENTRY_POINT_WARNING, - LOG_EXPORTER_NAMES_ARG, - METRIC_EXPORTER_NAMES_ARG, - SAMPLER_ARG, - TRACE_EXPORTER_NAMES_ARG, -) -from azure.monitor.opentelemetry._diagnostics.diagnostic_logging import ( - AzureDiagnosticLogging, - _ATTACH_FAILURE_CONFIGURATOR, - _ATTACH_SUCCESS_CONFIGURATOR, -) -from azure.monitor.opentelemetry._diagnostics.status_logger import ( - AzureStatusLogger, -) - - -class AzureMonitorConfigurator(_OTelSDKConfigurator): - def _configure(self, **kwargs): - if not _is_attach_enabled(): - warn(_PREVIEW_ENTRY_POINT_WARNING) - try: - if environ.get(OTEL_TRACES_EXPORTER, "").lower().strip() != "none": - kwargs.setdefault( - TRACE_EXPORTER_NAMES_ARG, ["azure_monitor_opentelemetry_exporter"] - ) - try: - sample_rate = float(environ.get("OTEL_TRACES_SAMPLER_ARG", 1.0)) - except ValueError: - sample_rate = 1.0 - kwargs.setdefault(SAMPLER_ARG, ApplicationInsightsSampler(sample_rate)) - if environ.get(OTEL_METRICS_EXPORTER, "").lower().strip() != "none": - kwargs.setdefault( - METRIC_EXPORTER_NAMES_ARG, ["azure_monitor_opentelemetry_exporter"] - ) - if environ.get(OTEL_LOGS_EXPORTER, "").lower().strip() != "none": - kwargs.setdefault( - LOG_EXPORTER_NAMES_ARG, ["azure_monitor_opentelemetry_exporter"] - ) - # As of OTel SDK 1.25.0, exporters passed as kwargs will be added to those specified in env vars. - super()._configure(**kwargs) - AzureStatusLogger.log_status(True) - AzureDiagnosticLogging.info( - "Azure Monitor Configurator configured successfully.", - _ATTACH_SUCCESS_CONFIGURATOR, - ) - except Exception as e: - AzureDiagnosticLogging.error( # pylint: disable=C - "Azure Monitor Configurator failed during configuration: %s" % str(e), - _ATTACH_FAILURE_CONFIGURATOR, - ) - raise e diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_autoinstrumentation/distro.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_autoinstrumentation/distro.py deleted file mode 100644 index 64168267..00000000 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_autoinstrumentation/distro.py +++ /dev/null @@ -1,68 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License in the project root for -# license information. -# -------------------------------------------------------------------------- -from os import environ -from warnings import warn - -from opentelemetry.instrumentation.distro import ( # type: ignore - BaseDistro, -) -from opentelemetry.sdk.environment_variables import ( - _OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED, - OTEL_EXPERIMENTAL_RESOURCE_DETECTORS, -) - -from azure.core.settings import settings -from azure.core.tracing.ext.opentelemetry_span import OpenTelemetrySpan -from azure.monitor.opentelemetry.exporter._utils import ( # pylint: disable=import-error,no-name-in-module - _is_attach_enabled, -) -from azure.monitor.opentelemetry._constants import ( - _AZURE_APP_SERVICE_RESOURCE_DETECTOR_NAME, - _AZURE_SDK_INSTRUMENTATION_NAME, - _PREVIEW_ENTRY_POINT_WARNING, -) -from azure.monitor.opentelemetry._diagnostics.diagnostic_logging import ( - AzureDiagnosticLogging, - _ATTACH_FAILURE_DISTRO, - _ATTACH_SUCCESS_DISTRO, -) -from azure.monitor.opentelemetry._diagnostics.status_logger import ( - AzureStatusLogger, -) -from azure.monitor.opentelemetry._utils.configurations import ( - _get_otel_disabled_instrumentations, -) - - -class AzureMonitorDistro(BaseDistro): - def _configure(self, **kwargs) -> None: - if not _is_attach_enabled(): - warn(_PREVIEW_ENTRY_POINT_WARNING) - try: - _configure_auto_instrumentation() - AzureStatusLogger.log_status(True) - AzureDiagnosticLogging.info( - "Azure Monitor OpenTelemetry Distro configured successfully.", - _ATTACH_SUCCESS_DISTRO, - ) - except Exception as e: - AzureStatusLogger.log_status(False, reason=str(e)) - AzureDiagnosticLogging.error( # pylint: disable=C - "Azure Monitor OpenTelemetry Distro failed during configuration: %s" - % str(e), - _ATTACH_FAILURE_DISTRO, - ) - raise e - - -def _configure_auto_instrumentation() -> None: - environ.setdefault(_OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED, "true") - environ.setdefault( - OTEL_EXPERIMENTAL_RESOURCE_DETECTORS, _AZURE_APP_SERVICE_RESOURCE_DETECTOR_NAME - ) - otel_disabled_instrumentations = _get_otel_disabled_instrumentations() - if _AZURE_SDK_INSTRUMENTATION_NAME not in otel_disabled_instrumentations: - settings.tracing_implementation = OpenTelemetrySpan diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_browser_sdk_loader/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_browser_sdk_loader/__init__.py deleted file mode 100644 index bee0fffc..00000000 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_browser_sdk_loader/__init__.py +++ /dev/null @@ -1,122 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License in the project root for -# license information. -# ------------------------------------------------------------------------- - -from logging import getLogger - -from azure.monitor.opentelemetry._browser_sdk_loader._config import BrowserSDKConfig - -_logger = getLogger(__name__) - - -def setup_snippet_injection(config: BrowserSDKConfig) -> None: - """Setup snippet injection for supported frameworks. - - :param config: BrowserSDK configuration. - :type config: BrowserSDKConfig - :rtype: None - """ - try: - # Try to setup Django middleware if Django is available - _setup_django_injection(config) - # Future: Add support for other frameworks like Flask, FastAPI, etc. - except Exception as ex: # pylint: disable=broad-exception-caught - _logger.debug("Failed to setup snippet injection: %s", ex, exc_info=True) - - -def _setup_django_injection(config: BrowserSDKConfig) -> None: - """Setup Django middleware injection if Django is available and configured. - - This function attempts to automatically register the Application Insights web snippet - middleware with Django if Django is available and properly configured. - - :param config: BrowserSDK configuration. - :type config: BrowserSDKConfig - :rtype: None - """ - try: - # Check if Django is available - try: - from django.conf import ( - settings, - ) # noqa: F401 # pylint: disable=import-error - except ImportError: - _logger.debug("Django not available - skipping Django middleware setup") - return - # Check if Django is configured - try: - from django.conf import settings # pylint: disable=import-error - - if not settings.configured: - _logger.debug( - "Django not configured - skipping middleware registration" - ) - return - except Exception: # pylint: disable=broad-exception-caught - _logger.debug( - "Cannot access Django settings - skipping middleware registration" - ) - return - # Try to dynamically register our middleware - _register_django_middleware(config) - except Exception as ex: # pylint: disable=broad-exception-caught - _logger.debug("Failed to setup Django middleware: %s", ex, exc_info=True) - - -def _register_django_middleware(config: BrowserSDKConfig) -> None: - """Register the Application Insights middleware with Django. - - :param config: BrowserSDK configuration. - :type config: BrowserSDKConfig - :rtype: None - """ - try: - from django.conf import settings # pylint: disable=import-error - - # Check if our middleware is already in the middleware list - middleware_path = ( - f"{__name__}.django_middleware.ApplicationInsightsWebSnippetMiddleware" - ) - if hasattr(settings, "MIDDLEWARE"): - middleware_list = list(settings.MIDDLEWARE) - if middleware_path not in middleware_list: - # Add our middleware to the end of the list - middleware_list.append(middleware_path) - settings.MIDDLEWARE = middleware_list - _logger.debug( - "Added Application Insights middleware to Django MIDDLEWARE" - ) - elif hasattr(settings, "MIDDLEWARE_CLASSES"): # Legacy Django support - middleware_list = list(settings.MIDDLEWARE_CLASSES) - if middleware_path not in middleware_list: - middleware_list.append(middleware_path) - settings.MIDDLEWARE_CLASSES = middleware_list - _logger.debug( - "Added Application Insights middleware to Django MIDDLEWARE_CLASSES" - ) - # Store configuration globally for the middleware to access - _store_django_config(config) - except Exception as ex: # pylint: disable=broad-exception-caught - _logger.warning("Failed to register Django middleware: %s", ex, exc_info=True) - - -def _store_django_config(config: BrowserSDKConfig) -> None: - """Store the Application Insights configuration for Django middleware access. - - :param config: BrowserSDK configuration. - :type config: BrowserSDKConfig - :rtype: None - """ - try: - from django.conf import settings # pylint: disable=import-error - - # Store config in Django settings for middleware to access - if not hasattr(settings, "AZURE_MONITOR_WEB_SNIPPET_CONFIG"): - settings.AZURE_MONITOR_WEB_SNIPPET_CONFIG = config - _logger.debug( - "Stored Application Insights configuration in Django settings" - ) - except Exception as ex: # pylint: disable=broad-exception-caught - _logger.debug("Failed to store Django configuration: %s", ex, exc_info=True) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_browser_sdk_loader/_config.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_browser_sdk_loader/_config.py deleted file mode 100644 index 22f3e47d..00000000 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_browser_sdk_loader/_config.py +++ /dev/null @@ -1,48 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License in the project root for -# license information. -# ------------------------------------------------------------------------- - -from typing import Optional, Dict, Any - - -class BrowserSDKConfig: - """Configuration class for the Browser SDK to be injected. - - This class handles the configuration settings for injecting the Browser SDK - snippet into web responses. - - :param enabled: Whether snippet injection is enabled. - :type enabled: bool - :param connection_string: Application Insights connection string. - :type connection_string: str - :param config: Additional configuration options for the web SDK. - :type config: Dict[str, Any] - """ - - def __init__( - self, enabled: bool = True, connection_string: Optional[str] = None - ) -> None: - """Initialize the BrowserSDKConfig. - - :param enabled: Whether snippet injection is enabled. - :type enabled: bool - :param connection_string: Application Insights connection string. - :type connection_string: str or None - :param config: Additional configuration options for the web SDK. - :rtype: None - """ - self.enabled = enabled - self.connection_string = connection_string - - def to_dict(self) -> Dict[str, Dict[str, Any]]: - """Convert the config to a dictionary for the web snippet. - - :return: Dictionary representation of the configuration. - :rtype: dict - """ - cfg: Dict[str, Any] = {} - if self.connection_string: - cfg["connectionString"] = self.connection_string - return {"cfg": cfg} diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_browser_sdk_loader/django_middleware.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_browser_sdk_loader/django_middleware.py deleted file mode 100644 index fdf31ca7..00000000 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_browser_sdk_loader/django_middleware.py +++ /dev/null @@ -1,180 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License in the project root for -# license information. -# ------------------------------------------------------------------------- - -from typing import Optional, Callable, Any, Union -from logging import getLogger - -try: - from django.http import HttpRequest, HttpResponse # type: ignore - from django.utils.deprecation import MiddlewareMixin # type: ignore - - DJANGO_AVAILABLE = True -except ImportError: - # Define stub classes when Django is not available - HttpRequest = Any - HttpResponse = Any - MiddlewareMixin = object - DJANGO_AVAILABLE = False - -from .snippet_injector import WebSnippetInjector -from ._config import BrowserSDKConfig - -_logger = getLogger(__name__) - - -class ApplicationInsightsWebSnippetMiddleware(MiddlewareMixin): - """Django middleware for injecting Application Insights web snippet into HTML responses. - - This middleware automatically injects the Application Insights JavaScript SDK snippet - into HTML responses from Django applications. - - :param get_response: Django's get_response callable. - :type get_response: Callable - """ - - def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]) -> None: - """Initialize the middleware. - - :param get_response: Django's get_response callable. - :type get_response: Callable[[HttpRequest], HttpResponse] - :rtype: None - """ - super().__init__(get_response) - self.get_response = get_response - self._injector: Optional[WebSnippetInjector] = None - if not DJANGO_AVAILABLE: - _logger.warning( - "Django not available - ApplicationInsightsWebSnippetMiddleware will not function" - ) - return - - # Auto-configure from Django settings if available - self._auto_configure_from_settings() - - def __call__(self, request: HttpRequest) -> HttpResponse: - """Process the request through the middleware. - - :param request: Django HTTP request object. - :type request: HttpRequest - :return: Django HTTP response object with potentially injected snippet. - :rtype: HttpResponse - """ - response = self.get_response(request) - return self.process_response(request, response) - - def process_response( - self, request: HttpRequest, response: HttpResponse - ) -> HttpResponse: - """Process the response to inject the Application Insights snippet if appropriate. - - :param request: Django HTTP request object. - :type request: HttpRequest - :param response: Django HTTP response object to potentially modify. - :type response: HttpResponse - :return: Django HTTP response object with potentially injected snippet. - :rtype: HttpResponse - """ - if not DJANGO_AVAILABLE or not self._injector: - return response - try: - # Get response details - content_type = response.get("Content-Type", "") - content_encoding = response.get("Content-Encoding") - # Check if we should inject the snippet - if not self._injector.should_inject( - request.method, content_type, response.content, content_encoding - ): - return response - # Inject the snippet with compression handling - modified_content, new_encoding = self._injector.inject_with_compression( - response.content, content_encoding - ) - # Update response with modified content - response.content = modified_content - if new_encoding and new_encoding != content_encoding: - response["Content-Encoding"] = new_encoding - # Update content length if it changed - response["Content-Length"] = str(len(modified_content)) - except Exception as ex: # pylint: disable=broad-exception-caught - _logger.warning( - "Failed to inject Application Insights snippet: %s", ex, exc_info=True - ) - return response - - def configure( - self, - config: Union[BrowserSDKConfig, dict, str], - legacy_config: Optional[dict] = None, - ) -> None: - """Configure the middleware with Application Insights settings. - - :param config: Configuration object, dict, or connection string. - :type config: Union[BrowserSDKConfig, dict, str] - :param legacy_config: Legacy configuration options (for backward compatibility). - :type legacy_config: dict or None - :rtype: None - """ - if legacy_config: - _logger.debug( - "legacy_config provided to configure(); parameter retained for backward compatibility" - ) - - try: - if isinstance(config, BrowserSDKConfig): - # Use BrowserSDKConfig object directly - snippet_config = config - elif isinstance(config, dict): - # Create BrowserSDKConfig from dictionary - snippet_kwargs = {} - if "enabled" in config: - snippet_kwargs["enabled"] = config["enabled"] - if "connection_string" in config: - snippet_kwargs["connection_string"] = config["connection_string"] - snippet_config = BrowserSDKConfig(**snippet_kwargs) - elif isinstance(config, str): - # Legacy mode: config is connection string - snippet_config = BrowserSDKConfig(connection_string=config) - else: - _logger.error( - "Invalid config type provided to configure(): %s", type(config) - ) - return - - self._injector = WebSnippetInjector(snippet_config) - except Exception as ex: # pylint: disable=broad-exception-caught - _logger.error("Failed to configure middleware: %s", ex, exc_info=True) - - def _auto_configure_from_settings(self) -> None: - """Auto-configure the middleware from Django settings if available. - - :rtype: None - """ - try: - from django.conf import settings # pylint: disable=import-error - - # Look for configuration in Django settings - if hasattr(settings, "AZURE_MONITOR_WEB_SNIPPET_CONFIG"): - config = settings.AZURE_MONITOR_WEB_SNIPPET_CONFIG - - if isinstance(config, (dict, BrowserSDKConfig)): - # Pass the configuration to configure method (handles both dict and BrowserSDKConfig) - self.configure(config) - _logger.debug( - "Auto-configured Application Insights middleware from Django settings" - ) - else: - _logger.debug( - "Invalid AZURE_MONITOR_WEB_SNIPPET_CONFIG format in Django settings" - ) - else: - _logger.debug( - "No AZURE_MONITOR_WEB_SNIPPET_CONFIG found in Django settings" - ) - - except Exception as ex: # pylint: disable=broad-exception-caught - _logger.debug( - "Failed to auto-configure from Django settings: %s", ex, exc_info=True - ) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_browser_sdk_loader/snippet_injector.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_browser_sdk_loader/snippet_injector.py deleted file mode 100644 index 5d55322f..00000000 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_browser_sdk_loader/snippet_injector.py +++ /dev/null @@ -1,497 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License in the project root for -# license information. -# ------------------------------------------------------------------------- - -import gzip -import importlib -import re -from logging import getLogger -from typing import Any, Dict, Optional, Tuple - -from ._config import BrowserSDKConfig - -# Optional compression libraries -_BROTLI_MODULE: Optional[Any] -try: - _BROTLI_MODULE = importlib.import_module("brotli") - HAS_BROTLI = True -except ImportError: - _BROTLI_MODULE = None - HAS_BROTLI = False - -_ZLIB_MODULE: Optional[Any] -try: - import zlib as _imported_zlib - - _ZLIB_MODULE = _imported_zlib - HAS_ZLIB = True -except ImportError: - _ZLIB_MODULE = None - HAS_ZLIB = False - -_logger = getLogger(__name__) - - -def _mark_browser_loader_feature(is_enabled: bool) -> None: - """Record browser SDK loader usage in statsbeat when available. - - :param is_enabled: Indicates whether the browser loader is enabled. - :type is_enabled: bool - """ - if not is_enabled: - return - try: - from azure.monitor.opentelemetry.exporter.statsbeat._state import ( - get_statsbeat_browser_sdk_loader_feature_set, - get_statsbeat_shutdown, - is_statsbeat_enabled, - set_statsbeat_browser_sdk_loader_feature_set, - ) - except ImportError: - return - - try: - if ( - is_statsbeat_enabled() - and not get_statsbeat_shutdown() - and not get_statsbeat_browser_sdk_loader_feature_set() - ): - set_statsbeat_browser_sdk_loader_feature_set() - except Exception: # pylint: disable=broad-exception-caught - _logger.debug("Failed to record browser loader statsbeat usage", exc_info=True) - - -# Web SDK snippet template -_WEB_SDK_SNIPPET_TEMPLATE = """""" - - -class WebSnippetInjector: - """Handles injection of Application Insights web snippet into HTML responses. - - :param config: Configuration object for the web snippet injector. - :type config: BrowserSDKConfig - """ - - def __init__(self, config: BrowserSDKConfig) -> None: - """Initialize the WebSnippetInjector. - - :param config: Configuration object for the web snippet injector. - :type config: BrowserSDKConfig - :rtype: None - """ - self.config = config - self._web_sdk_snippet_cache: Optional[str] = None - self._decompressed_content_cache: Optional[bytes] = None - self._cache_key: Optional[tuple] = None - # Regex patterns for detecting existing Web SDK - more specific to avoid false positives - self._existing_sdk_patterns = [ - # Look for actual JavaScript variables/objects, not just text content - re.compile( - r"\bappInsights\s*[=\.]", re.IGNORECASE - ), # appInsights= or appInsights. - re.compile( - r"\bApplicationInsights\s*[=\.]", re.IGNORECASE - ), # ApplicationInsights= or ApplicationInsights. - re.compile( - r"window\[.*appInsights.*\]", re.IGNORECASE - ), # window["appInsights"] patterns - re.compile( - r"Microsoft\.ApplicationInsights", re.IGNORECASE - ), # Microsoft.ApplicationInsights namespace - # Look for actual script URLs - re.compile(r"ai\.2\.min\.js", re.IGNORECASE), - re.compile(r"js\.monitor\.azure\.com", re.IGNORECASE), - # Look for HTML comments indicating snippet is already present - re.compile( - r"", - re.IGNORECASE, - ), - ] - _mark_browser_loader_feature(self.config.enabled) - - def should_inject( - self, - request_method: str, - content_type: Optional[str], - content: bytes, - content_encoding: Optional[str] = None, - ) -> bool: - """Determine whether the web snippet should be injected into the response. - - :param request_method: HTTP request method (e.g., 'GET', 'POST'). - :type request_method: str - :param content_type: Content-Type header value from the response. - :type content_type: str or None - :param content: Response content bytes. - :type content: bytes - :param content_encoding: Content-Encoding header value (e.g., 'gzip', 'br'). - :type content_encoding: str or None - :return: True if the snippet should be injected, False otherwise. - :rtype: bool - """ - if not self.config.enabled: - return False - # Only inject in GET requests - if request_method.upper() != "GET": - return False - # Check content type for HTML - if not content_type or "html" not in content_type.lower(): - return False - # Get decompressed content once and cache it for reuse - decompressed_content = self._get_decompressed_content(content, content_encoding) - # Check if Web SDK is already present using cached decompressed content - if self._has_existing_web_sdk_from_decompressed(decompressed_content): - _logger.debug("Web SDK already detected in HTML, skipping injection") - return False - return True - - def inject_snippet(self, content: bytes, encoding: str = "utf-8") -> bytes: - """Inject the web snippet into HTML content. - - :param content: HTML content as bytes to inject the snippet into. - :type content: bytes - :param encoding: Text encoding to use for decoding/encoding content. - :type encoding: str - :return: Modified HTML content with the injected snippet. - :rtype: bytes - """ - try: - # Decode content - html_content = content.decode(encoding) - # Generate snippet - snippet = self._get_web_snippet() - # Find insertion point (before or ) - insertion_point = self._find_insertion_point(html_content) - if insertion_point == -1: - _logger.warning( - "Could not find suitable insertion point for web snippet" - ) - return content - # Insert snippet - modified_html = ( - html_content[:insertion_point] - + snippet - + html_content[insertion_point:] - ) - return modified_html.encode(encoding) - except Exception as ex: # pylint: disable=broad-exception-caught - _logger.warning("Failed to inject web snippet: %s", ex, exc_info=True) - return content - - def inject_with_compression( - self, - content: bytes, - content_encoding: Optional[str] = None, - encoding: str = "utf-8", - ) -> Tuple[bytes, Optional[str]]: # pylint: disable=too-many-return-statements - """Inject snippet handling compression/decompression efficiently using cached decompressed content. - - :param content: Response content bytes, potentially compressed. - :type content: bytes - :param content_encoding: Content-Encoding header value (e.g., 'gzip', 'br', 'deflate'). - :type content_encoding: str or None - :param encoding: Text encoding to use for decoding/encoding HTML content. - :type encoding: str - :return: Tuple of (modified_content, detected_encoding) where modified_content is the - response content with injected snippet and detected_encoding is the compression format used. - :rtype: Tuple[bytes, Optional[str]] - """ - try: - # Use cached decompressed content if available, otherwise decompress - decompressed_content = self._get_decompressed_content( - content, content_encoding - ) - # Auto-detect encoding if not specified and content was compressed - detected_encoding = content_encoding - if not content_encoding and decompressed_content != content: - # Content was decompressed, try to detect which format was used - for enc in ["gzip", "br", "deflate"]: - try: - test_compressed = self._compress_content( - decompressed_content, enc - ) - if test_compressed == content: - detected_encoding = enc - break - except Exception: # pylint: disable=broad-exception-caught - continue - # Check for existing SDK using cached decompressed content - if self._has_existing_web_sdk_from_decompressed(decompressed_content): - _logger.debug("Web SDK already detected in HTML, skipping injection") - return content, content_encoding - # Inject snippet into decompressed content - modified_content = self.inject_snippet(decompressed_content, encoding) - # Recompress if original was compressed - if detected_encoding: - compressed_content = self._compress_content( - modified_content, detected_encoding - ) - return compressed_content, detected_encoding - return modified_content, None - except Exception as ex: # pylint: disable=broad-exception-caught - _logger.warning( - "Failed to inject snippet with compression handling: %s", - ex, - exc_info=True, - ) - return content, content_encoding - - def _get_web_snippet(self) -> str: - """Generate the web snippet with configuration. - - :return: The complete web snippet JavaScript code as a string. - :rtype: str - """ - if self._web_sdk_snippet_cache is None: - config_dict = self.config.to_dict() - config_json = self._dict_to_js_object(config_dict.get("cfg", {})) - self._web_sdk_snippet_cache = _WEB_SDK_SNIPPET_TEMPLATE.replace( - "{CONFIG_PLACEHOLDER}", config_json - ) - return self._web_sdk_snippet_cache - - def _get_decompressed_content( - self, content: bytes, content_encoding: Optional[str] = None - ) -> bytes: - """Get decompressed content with caching to avoid redundant decompression. - - :param content: Content bytes to decompress if needed. - :type content: bytes - :param content_encoding: Content-Encoding header value (e.g., 'gzip', 'br', 'deflate'). - :type content_encoding: str or None - :return: Decompressed content bytes. - :rtype: bytes - """ - # Fast path: if no encoding specified and content doesn't look compressed, return as-is - if not content_encoding: - # Quick check for compression headers to avoid unnecessary processing - if not self._appears_compressed(content): - return content - # For compressed content or when encoding is specified, use caching - cache_key = (hash(content), content_encoding) - # Return cached result if available - if ( - self._cache_key == cache_key - and self._decompressed_content_cache is not None - ): - return self._decompressed_content_cache - # Decompress content based on encoding header - decompressed_content = self._decompress_content(content, content_encoding) - # If no encoding was specified and content wasn't decompressed, try auto-detection - if not content_encoding and decompressed_content == content: - for encoding in ["gzip", "br", "deflate"]: - try: - test_decompressed = self._decompress_content(content, encoding) - if test_decompressed != content: # Successfully decompressed - decompressed_content = test_decompressed - break - except Exception: # pylint: disable=broad-exception-caught - continue - # Cache the result only if we actually did decompression work - if content_encoding or decompressed_content != content: - self._cache_key = cache_key - self._decompressed_content_cache = decompressed_content - return decompressed_content - - def _appears_compressed(self, content: bytes) -> bool: - """Quick check if content appears to be compressed based on magic numbers. - - :param content: Content bytes to check for compression signatures. - :type content: bytes - :return: True if content appears to be compressed, False otherwise. - :rtype: bool - """ - if len(content) < 3: - return False - # Check for common compression magic numbers - # Gzip: starts with 0x1f 0x8b - if content.startswith(b"\x1f\x8b"): - return True - # Brotli: no reliable magic number, but check for common patterns - # (Brotli detection is harder without trying to decompress) - # zlib/deflate: starts with 0x78 followed by various values - if content[0] == 0x78 and content[1] in (0x01, 0x5E, 0x9C, 0xDA): - return True - return False - - def _clear_decompression_cache(self) -> None: - """Clear the decompressed content cache. - - :rtype: None - """ - self._decompressed_content_cache = None - self._cache_key = None - - def _has_existing_web_sdk( - self, content: bytes, content_encoding: Optional[str] = None - ) -> bool: - """Check if Web SDK is already present in the HTML, handling compressed content. - - :param content: HTML content bytes to check for existing Web SDK. - :type content: bytes - :param content_encoding: Content-Encoding header value (e.g., 'gzip', 'br', 'deflate'). - :type content_encoding: str or None - :return: True if Web SDK is detected in the content, False otherwise. - :rtype: bool - """ - try: - # Decompress content once based on encoding - decompressed_content = self._decompress_content(content, content_encoding) - # If no encoding was specified and content wasn't decompressed, try auto-detection - if not content_encoding and decompressed_content == content: - for encoding in ["gzip", "br", "deflate"]: - try: - test_decompressed = self._decompress_content(content, encoding) - if test_decompressed != content: # Successfully decompressed - decompressed_content = test_decompressed - break - except Exception: # pylint: disable=broad-exception-caught - continue - return self._has_existing_web_sdk_from_decompressed(decompressed_content) - - except Exception: # pylint: disable=broad-exception-caught - return False - - def _has_existing_web_sdk_from_decompressed( - self, decompressed_content: bytes - ) -> bool: - """Check if Web SDK is already present in decompressed HTML content. - - :param decompressed_content: Decompressed HTML content bytes to scan for Web SDK patterns. - :type decompressed_content: bytes - :return: True if Web SDK patterns are found, False otherwise. - :rtype: bool - """ - try: - # Convert decompressed content to string and check for patterns - content_str = decompressed_content.decode("utf-8", errors="ignore") - return any( - pattern.search(content_str) for pattern in self._existing_sdk_patterns - ) - except Exception: # pylint: disable=broad-exception-caught - return False - - def _find_insertion_point(self, html_content: str) -> int: - """Find the best insertion point for the snippet. - - :param html_content: HTML content as a string to search for insertion points. - :type html_content: str - :return: Character position where snippet should be inserted, or -1 if no suitable point found. - :rtype: int - """ - # Try to insert before - head_end = html_content.lower().find("") - if head_end != -1: - return head_end - # Fallback: insert before - body_start = html_content.lower().find(" - html_start = html_content.lower().find(" bytes: - """Decompress content based on encoding. - - :param content: Compressed content bytes to decompress. - :type content: bytes - :param encoding: Compression encoding type (e.g., 'gzip', 'br', 'deflate'). - :type encoding: str or None - :return: Decompressed content bytes. - :rtype: bytes - """ - if not encoding: - return content - - result = content - try: - normalized = encoding.lower() - if normalized == "gzip": - result = gzip.decompress(content) - elif normalized == "br": - if not HAS_BROTLI or _BROTLI_MODULE is None: - _logger.warning("brotli library not available for decompression") - else: - result = _BROTLI_MODULE.decompress(content) - elif normalized == "deflate": - if not HAS_ZLIB or _ZLIB_MODULE is None: - _logger.warning("zlib library not available for decompression") - else: - result = _ZLIB_MODULE.decompress(content) - except Exception as ex: # pylint: disable=broad-exception-caught - _logger.warning( - "Failed to decompress content with encoding %s: %s", encoding, ex - ) - return result - - def _compress_content(self, content: bytes, encoding: str) -> bytes: - """Compress content using specified encoding. - - :param content: Uncompressed content bytes to compress. - :type content: bytes - :param encoding: Compression encoding type (e.g., 'gzip', 'br', 'deflate'). - :type encoding: str - :return: Compressed content bytes. - :rtype: bytes - """ - result = content - try: - normalized = encoding.lower() - if normalized == "gzip": - result = gzip.compress(content) - elif normalized == "br": - if not HAS_BROTLI or _BROTLI_MODULE is None: - _logger.warning("brotli library not available for compression") - else: - result = _BROTLI_MODULE.compress(content) - elif normalized == "deflate": - if not HAS_ZLIB or _ZLIB_MODULE is None: - _logger.warning("zlib library not available for compression") - else: - result = _ZLIB_MODULE.compress(content) - except Exception as ex: # pylint: disable=broad-exception-caught - _logger.warning( - "Failed to compress content with encoding %s: %s", encoding, ex - ) - return result - - def _format_config_value(self, value): - """Format a configuration value for JavaScript. - - :param value: The value to format. - :type value: Any - :return: Formatted string for JavaScript. - :rtype: str - """ - if isinstance(value, str): - return f'"{value}"' - if isinstance(value, bool): - return "true" if value else "false" - if isinstance(value, (int, float)): - return str(value) - if isinstance(value, dict): - return self._dict_to_js_object(value) - return f'"{str(value)}"' - - def _dict_to_js_object(self, obj: Dict[str, Any]) -> str: - """Convert Python dict to JavaScript object literal. - - :param obj: Python dictionary to convert to JavaScript object syntax. - :type obj: Dict[str, Any] - :return: JavaScript object literal as a string. - :rtype: str - """ - items = [] - for key, value in obj.items(): - items.append(f"{key}: {self._format_config_value(value)}") - return "{" + ", ".join(items) + "}" diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py deleted file mode 100644 index add81edf..00000000 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py +++ /dev/null @@ -1,470 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License in the project root for -# license information. -# -------------------------------------------------------------------------- -from functools import cached_property -from logging import getLogger, Formatter -from typing import Any, Dict, List, Optional, cast -from opentelemetry.metrics import set_meter_provider -from opentelemetry.sdk.metrics import MeterProvider -from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader, MetricReader -from opentelemetry.sdk.metrics.view import View -from opentelemetry.sdk.resources import Resource -from opentelemetry.sdk.trace import TracerProvider -from opentelemetry.sdk.trace.export import BatchSpanProcessor -from opentelemetry.trace import set_tracer_provider -from opentelemetry.util._importlib_metadata import ( # pylint: disable=import-error - EntryPoint, - distributions, - entry_points, -) - -from azure.monitor.opentelemetry._browser_sdk_loader import setup_snippet_injection -from azure.monitor.opentelemetry._browser_sdk_loader._config import BrowserSDKConfig -from azure.monitor.opentelemetry._constants import ( - _ALL_SUPPORTED_INSTRUMENTED_LIBRARIES, - _AZURE_SDK_INSTRUMENTATION_NAME, - BROWSER_SDK_LOADER_CONFIG_ARG, - DISABLE_LOGGING_ARG, - DISABLE_METRICS_ARG, - DISABLE_TRACING_ARG, - ENABLE_LIVE_METRICS_ARG, - ENABLE_PERFORMANCE_COUNTERS_ARG, - LOGGER_NAME_ARG, - LOGGING_FORMATTER_ARG, - RESOURCE_ARG, - SAMPLING_RATIO_ARG, - SAMPLING_TRACES_PER_SECOND_ARG, - SPAN_PROCESSORS_ARG, - LOG_RECORD_PROCESSORS_ARG, - METRIC_READERS_ARG, - VIEWS_ARG, - ENABLE_TRACE_BASED_SAMPLING_ARG, - SAMPLING_ARG, - SAMPLER_TYPE, -) -from azure.monitor.opentelemetry._types import ConfigurationValue -from azure.monitor.opentelemetry.exporter._quickpulse import ( # pylint: disable=import-error,no-name-in-module - enable_live_metrics, -) -from azure.monitor.opentelemetry.exporter._performance_counters import ( # pylint: disable=import-error,no-name-in-module - enable_performance_counters, -) -from azure.monitor.opentelemetry.exporter._performance_counters._processor import ( # pylint: disable=import-error,no-name-in-module - _PerformanceCountersLogRecordProcessor, - _PerformanceCountersSpanProcessor, -) -from azure.monitor.opentelemetry.exporter._quickpulse._processor import ( # pylint: disable=import-error,no-name-in-module - _QuickpulseLogRecordProcessor, - _QuickpulseSpanProcessor, -) -from azure.monitor.opentelemetry.exporter import ( # pylint: disable=import-error,no-name-in-module - ApplicationInsightsSampler, - AzureMonitorMetricExporter, - AzureMonitorTraceExporter, - RateLimitedSampler, -) -from azure.monitor.opentelemetry.exporter._utils import ( # pylint: disable=import-error,no-name-in-module - _is_attach_enabled, - _is_on_functions, -) -from azure.monitor.opentelemetry._diagnostics.diagnostic_logging import ( - _DISTRO_DETECTS_ATTACH, - AzureDiagnosticLogging, -) -from azure.monitor.opentelemetry._utils.configurations import ( - _get_configurations, - _is_instrumentation_enabled, - _get_sampler_from_name, -) -from azure.monitor.opentelemetry._utils.instrumentation import ( - get_dist_dependency_conflicts, -) - -_logger = getLogger(__name__) - - -def configure_azure_monitor(**kwargs) -> None: # pylint: disable=C4758 - """This function works as a configuration layer that allows the - end user to configure OpenTelemetry and Azure monitor components. The - configuration can be done via arguments passed to this function. - - :keyword str connection_string: Connection string for your Application Insights resource. - :keyword credential: Token credential, such as `ManagedIdentityCredential` or `ClientSecretCredential`, - used for Azure Active Directory (AAD) authentication. Defaults to `None`. - :paramtype credential: ~azure.core.credentials.TokenCredential or None - :keyword bool disable_offline_storage: Boolean value to determine whether to disable storing failed - telemetry records for retry. Defaults to `False`. - :keyword str logger_name: The name of the Python logger that telemetry will be collected. - :keyword dict instrumentation_options: A nested dictionary that determines which instrumentations - to enable or disable. Instrumentations are referred to by their Library Names. For example, - `{"azure_sdk": {"enabled": False}, "flask": {"enabled": False}, "django": {"enabled": True}}` - will disable Azure Core Tracing and the Flask instrumentation but leave Django and the other default - instrumentations enabled. - :keyword ~opentelemetry.sdk.resources.Resource resource: OpenTelemetry Resource object. Passed in Resource - Attributes take priority over default attributes and those from Resource Detectors. - :keyword list[~opentelemetry.sdk.trace.SpanProcessor] span_processors: List of `SpanProcessor` objects - to process every span prior to exporting. Will be run sequentially. - :keyword list[~opentelemetry.sdk._logs.LogRecordProcessor] log_record_processors: List of `LogRecordProcessor` - objects to process every log record prior to exporting. Will be run sequentially. - :keyword list[~opentelemetry.sdk.metrics.MetricReader] metric_readers: List of MetricReader objects to read and - export metrics. Each reader can have its own exporter and collection interval. - :keyword bool enable_live_metrics: Boolean value to determine whether to enable live metrics feature. - Defaults to `True`. - :keyword bool enable_performance_counters: Boolean value to determine whether to enable performance counters. - Defaults to `True`. - :keyword str storage_directory: Storage directory in which to store retry files. Defaults to - `/Microsoft/AzureMonitor/opentelemetry-python-`. - :keyword list[~opentelemetry.sdk.metrics.view.View] views: List of `View` objects to configure and filter - metric output. - :keyword bool enable_trace_based_sampling_for_logs: Boolean value to determine whether to enable trace based - sampling for logs. Defaults to `False` - :keyword dict browser_sdk_loader_config: Configuration dictionary for browser SDK loader behavior. - Supports keys like 'connection_string' (separate connection string for browser SDK), 'enabled' (boolean), - and framework-specific options. Defaults to `{}`. - :rtype: None - """ - - _send_attach_warning() - - configurations = _get_configurations(**kwargs) - - disable_tracing = configurations[DISABLE_TRACING_ARG] - disable_logging = configurations[DISABLE_LOGGING_ARG] - disable_metrics = configurations[DISABLE_METRICS_ARG] - enable_live_metrics_config = configurations[ENABLE_LIVE_METRICS_ARG] - - # Set up metrics pipeline - # Set up metrics with Performance Counters before _PerformanceCountersSpanProcessor and - # _PerformanceCountersLogRecordProcessor. This avoids a circular dependency in the case that Performance Counter - # setup produces a log. - if not disable_metrics: - _setup_metrics(configurations) - - # Set up live metrics - if enable_live_metrics_config: - _setup_live_metrics(configurations) - - # Set up tracing pipeline - if not disable_tracing: - _setup_tracing(configurations) - - # Set up logging pipeline - if not disable_logging: - _setup_logging(configurations) - - # Set up instrumentations - # Instrumentations need to be set up last so to use the global providers - # instantiated in the other setup steps - _setup_instrumentations(configurations) - - # Setup browser SDK loader for supported frameworks - _setup_browser_sdk_loader(configurations) - - -def _setup_tracing(configurations: Dict[str, ConfigurationValue]): - resource: Resource = configurations[RESOURCE_ARG] # type: ignore - enable_performance_counters_config = configurations[ENABLE_PERFORMANCE_COUNTERS_ARG] - if SAMPLING_ARG in configurations: - sampler_arg = configurations[SAMPLING_ARG] - sampler_type = configurations[SAMPLER_TYPE] - sampler = _get_sampler_from_name(sampler_type, sampler_arg) - tracer_provider = TracerProvider(sampler=sampler, resource=resource) - elif SAMPLING_RATIO_ARG in configurations: - sampling_ratio = configurations[SAMPLING_RATIO_ARG] - tracer_provider = TracerProvider( - sampler=ApplicationInsightsSampler( - sampling_ratio=cast(float, sampling_ratio) - ), - resource=resource, - ) - else: - traces_per_second = configurations[SAMPLING_TRACES_PER_SECOND_ARG] - tracer_provider = TracerProvider( - sampler=RateLimitedSampler( - target_spans_per_second_limit=cast(float, traces_per_second) - ), - resource=resource, - ) - - for span_processor in configurations[SPAN_PROCESSORS_ARG]: # type: ignore - tracer_provider.add_span_processor(span_processor) # type: ignore - if configurations.get(ENABLE_LIVE_METRICS_ARG): - qsp = _QuickpulseSpanProcessor() - tracer_provider.add_span_processor(qsp) - if enable_performance_counters_config: - pcsp = _PerformanceCountersSpanProcessor() - tracer_provider.add_span_processor(pcsp) - trace_exporter = AzureMonitorTraceExporter(**configurations) - bsp = BatchSpanProcessor( - trace_exporter, - ) - tracer_provider.add_span_processor(bsp) - set_tracer_provider(tracer_provider) - - if _is_instrumentation_enabled(configurations, _AZURE_SDK_INSTRUMENTATION_NAME): - try: - from azure.core.settings import settings - from azure.core.tracing.ext.opentelemetry_span import OpenTelemetrySpan - - settings.tracing_implementation = OpenTelemetrySpan - except ImportError as ex: - # This could possibly be due to breaking change in upstream OpenTelemetry - # Advise user to upgrade to latest OpenTelemetry version - _logger.warning( # pylint: disable=do-not-log-exceptions-if-not-debug - "Exception occurred when importing Azure SDK Tracing." - "Please upgrade to the latest OpenTelemetry version: %s.", - ex, - ) - except Exception as ex: # pylint: disable=broad-except - _logger.warning( # pylint: disable=do-not-log-exceptions-if-not-debug - "Exception occurred when setting Azure SDK Tracing: %s.", - ex, - ) - - -def _setup_logging(configurations: Dict[str, ConfigurationValue]): - # Setup logging - # Use try catch while signal is experimental - try: - from opentelemetry._logs import set_logger_provider - from opentelemetry.sdk._logs import LoggerProvider - from opentelemetry.instrumentation.logging.handler import LoggingHandler - from azure.monitor.opentelemetry.exporter.export.logs._processor import ( - _AzureBatchLogRecordProcessor, - ) - - from azure.monitor.opentelemetry.exporter import ( # pylint: disable=import-error,no-name-in-module - AzureMonitorLogExporter, - ) - - resource: Resource = configurations[RESOURCE_ARG] # type: ignore - enable_performance_counters_config = configurations[ - ENABLE_PERFORMANCE_COUNTERS_ARG - ] - logger_provider = LoggerProvider(resource=resource) - enable_trace_based_sampling_for_logs = configurations[ - ENABLE_TRACE_BASED_SAMPLING_ARG - ] - for custom_log_record_processor in configurations[LOG_RECORD_PROCESSORS_ARG]: # type: ignore - logger_provider.add_log_record_processor(custom_log_record_processor) # type: ignore - if configurations.get(ENABLE_LIVE_METRICS_ARG): - qlp = _QuickpulseLogRecordProcessor() - logger_provider.add_log_record_processor(qlp) - if enable_performance_counters_config: - pclp = _PerformanceCountersLogRecordProcessor() - logger_provider.add_log_record_processor(pclp) - log_record_exporter = AzureMonitorLogExporter(**configurations) - log_record_processor = _AzureBatchLogRecordProcessor( - log_record_exporter, - { - "enable_trace_based_sampling_for_logs": enable_trace_based_sampling_for_logs - }, - ) - logger_provider.add_log_record_processor(log_record_processor) - set_logger_provider(logger_provider) - logger_name: str = configurations[LOGGER_NAME_ARG] # type: ignore - logging_formatter: Optional[Formatter] = configurations.get(LOGGING_FORMATTER_ARG) # type: ignore - logger = getLogger(logger_name) - # Only add OpenTelemetry LoggingHandler if logger does not already have the handler - # This is to prevent most duplicate logging telemetry - if not any(isinstance(handler, LoggingHandler) for handler in logger.handlers): - handler = LoggingHandler(logger_provider=logger_provider) - if logging_formatter: - try: - handler.setFormatter(logging_formatter) - except Exception as ex: # pylint: disable=broad-except - _logger.warning( # pylint: disable=do-not-log-exceptions-if-not-debug - "Exception occurred when adding logging Formatter: %s.", - ex, - ) - logger.addHandler(handler) - - except ImportError as ex: - # If the events is not available, we will not set it up. - # This could possibly be due to breaking change in upstream OpenTelemetry - # Advise user to upgrade to latest OpenTelemetry version - _logger.warning( # pylint: disable=do-not-log-exceptions-if-not-debug - "Exception occurred when setting up Logging. Please upgrade to the latest OpenTelemetry version: %s.", - ex, - ) - - -def _setup_metrics(configurations: Dict[str, ConfigurationValue]): - resource: Resource = configurations[RESOURCE_ARG] # type: ignore - views: List[View] = configurations[VIEWS_ARG] # type: ignore - readers: list[MetricReader] = configurations[METRIC_READERS_ARG] # type: ignore - enable_performance_counters_config = configurations[ENABLE_PERFORMANCE_COUNTERS_ARG] - metric_exporter = AzureMonitorMetricExporter(**configurations) - readers.append(PeriodicExportingMetricReader(metric_exporter)) - meter_provider = MeterProvider( - metric_readers=readers, - resource=resource, - views=views, - ) - if enable_performance_counters_config: - enable_performance_counters(meter_provider=meter_provider) - set_meter_provider(meter_provider) - - -def _setup_live_metrics(configurations): - enable_live_metrics(**configurations) - - -class _EntryPointDistFinder: - @cached_property - def _mapping(self): - return { - self._key_for(ep): dist - for dist in distributions() - for ep in dist.entry_points - } - - def dist_for(self, entry_point: EntryPoint): - dist = getattr(entry_point, "dist", None) - if dist: - return dist - - return self._mapping.get(self._key_for(entry_point)) - - @staticmethod - def _key_for(entry_point: EntryPoint): - return f"{entry_point.group}:{entry_point.name}:{entry_point.value}" - - -def _setup_instrumentations(configurations: Dict[str, ConfigurationValue]): - entry_point_finder = _EntryPointDistFinder() - # use pkg_resources for now until https://github.com/open-telemetry/opentelemetry-python/pull/3168 is merged - for entry_point in entry_points(group="opentelemetry_instrumentor"): - lib_name = entry_point.name - if lib_name not in _ALL_SUPPORTED_INSTRUMENTED_LIBRARIES: - continue - if not _is_instrumentation_enabled(configurations, lib_name): - _logger.debug("Instrumentation skipped for library %s", entry_point.name) - continue - try: - # Check if dependent libraries/version are installed - entry_point_dist = entry_point_finder.dist_for(entry_point) # type: ignore - conflict = get_dist_dependency_conflicts(entry_point_dist) # type: ignore - if conflict: - _logger.debug( - "Skipping instrumentation %s: %s", - entry_point.name, - conflict, - ) - continue - # Load the instrumentor via entrypoint - instrumentor: Any = entry_point.load() - # tell instrumentation to not run dep checks again as we already did it above - instrumentor().instrument(skip_dep_check=True) - except Exception as ex: # pylint: disable=broad-except - _logger.warning( - "Exception occurred when instrumenting: %s.", - lib_name, - exc_info=ex, - ) - _setup_additional_azure_sdk_instrumentations(configurations) - - -def _send_attach_warning(): - if _is_attach_enabled() and not _is_on_functions(): - # TODO: When AKS attach is public, update this message with disablement instructions for AKS - message = ( - "Distro detected that automatic instrumentation may have occurred. Only use autoinstrumentation if you " - "are not using manual instrumentation of OpenTelemetry in your code, such as with " - "azure-monitor-opentelemetry or azure-monitor-opentelemetry-exporter. For App Service resources, disable " - "autoinstrumentation in the Application Insights experience on your App Service resource or by setting " - "the ApplicationInsightsAgent_EXTENSION_VERSION app setting to 'disabled'." - ) - _logger.warning(message) - AzureDiagnosticLogging.warning( - message, - _DISTRO_DETECTS_ATTACH, - ) - - -def _setup_additional_azure_sdk_instrumentations( - configurations: Dict[str, ConfigurationValue], -): - if _AZURE_SDK_INSTRUMENTATION_NAME not in _ALL_SUPPORTED_INSTRUMENTED_LIBRARIES: - return - - if not _is_instrumentation_enabled(configurations, _AZURE_SDK_INSTRUMENTATION_NAME): - _logger.debug("Instrumentation skipped for library azure_sdk") - return - - instrumentors = [ - ("azure.ai.inference.tracing", "AIInferenceInstrumentor"), - ("azure.ai.agents.telemetry", "AIAgentsInstrumentor"), - ("azure.ai.projects.telemetry", "AIProjectInstrumentor"), - ] - - for module_path, class_name in instrumentors: - instrumentor_imported = False - try: - module = __import__(module_path, fromlist=[class_name]) - instrumentor_imported = True - except Exception as ex: # pylint: disable=broad-except - _logger.debug( - "Failed to import %s from %s", - class_name, - module_path, - exc_info=ex, - ) - - if instrumentor_imported: - try: - instrumentor_class = getattr(module, class_name) - instrumentor_class().instrument() - except Exception as ex: # pylint: disable=broad-except - _logger.warning( - "Exception occurred when instrumenting using: %s.", - class_name, - exc_info=ex, - ) - - -def _setup_browser_sdk_loader(configurations: Dict[str, ConfigurationValue]): - """Setup browser SDK loader for supported frameworks. - - :param configurations: Configuration dictionary containing browser SDK loader settings. - :type configurations: Dict[str, ConfigurationValue] - """ - try: - # Get browser SDK loader configuration - browser_sdk_loader_config_value = configurations.get( - BROWSER_SDK_LOADER_CONFIG_ARG - ) - if isinstance(browser_sdk_loader_config_value, dict): - browser_sdk_loader_config = browser_sdk_loader_config_value - else: - # Create typed empty dict to satisfy mypy - browser_sdk_loader_config = cast(Dict[str, Any], {}) - - # Check if browser SDK loader should be enabled (default False) - enabled = browser_sdk_loader_config.get("enabled", False) - if not enabled: - _logger.debug("Browser SDK loader disabled via configuration") - return - - # Get connection string (use browser SDK config first, then main config) - connection_string = browser_sdk_loader_config.get("connection_string") or cast( - str, configurations.get("connection_string", "") - ) - if not connection_string or not isinstance(connection_string, str): - _logger.debug( - "No valid connection string - skipping browser SDK loader setup" - ) - return - - # Create BrowserSDKConfig object - browser_config = BrowserSDKConfig( - enabled=enabled, connection_string=connection_string - ) - - # Setup snippet injection for supported frameworks - setup_snippet_injection(browser_config) - - except Exception as ex: # pylint: disable=broad-except - _logger.debug("Failed to setup browser SDK loader: %s", ex, exc_info=True) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_constants.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_constants.py deleted file mode 100644 index d82e4714..00000000 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_constants.py +++ /dev/null @@ -1,98 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License in the project root for -# license information. -# -------------------------------------------------------------------------- - -try: - from azure.monitor.opentelemetry.exporter._constants import ( # pylint: disable=import-error,no-name-in-module - _AZURE_MONITOR_DISTRO_VERSION_ARG, - ) -except ImportError: - _AZURE_MONITOR_DISTRO_VERSION_ARG = "distro_version" - -# --------------------Distro Configuration------------------------------------------ - -CONNECTION_STRING_ARG = "connection_string" -ENABLE_LIVE_METRICS_ARG = "enable_live_metrics" -DISABLE_AZURE_CORE_TRACING_ARG = "disable_azure_core_tracing" -DISABLE_LOGGING_ARG = "disable_logging" -DISABLE_METRICS_ARG = "disable_metrics" -DISABLE_TRACING_ARG = "disable_tracing" -ENABLE_PERFORMANCE_COUNTERS_ARG = "enable_performance_counters" -DISTRO_VERSION_ARG = _AZURE_MONITOR_DISTRO_VERSION_ARG -LOGGER_NAME_ARG = "logger_name" -LOGGING_FORMATTER_ARG = "logging_formatter" -INSTRUMENTATION_OPTIONS_ARG = "instrumentation_options" -RESOURCE_ARG = "resource" -SAMPLING_RATIO_ARG = "sampling_ratio" -SPAN_PROCESSORS_ARG = "span_processors" -LOG_RECORD_PROCESSORS_ARG = "log_record_processors" -METRIC_READERS_ARG = "metric_readers" -VIEWS_ARG = "views" -RATE_LIMITED_SAMPLER = "microsoft.rate_limited" -FIXED_PERCENTAGE_SAMPLER = "microsoft.fixed_percentage" -SAMPLING_TRACES_PER_SECOND_ARG = "traces_per_second" -ENABLE_TRACE_BASED_SAMPLING_ARG = "enable_trace_based_sampling_for_logs" -BROWSER_SDK_LOADER_CONFIG_ARG = "browser_sdk_loader_config" -SAMPLER_TYPE = "sampler_type" -SAMPLING_ARG = "sampling_arg" -ALWAYS_ON_SAMPLER = "always_on" -ALWAYS_OFF_SAMPLER = "always_off" -TRACE_ID_RATIO_SAMPLER = "trace_id_ratio" -PARENT_BASED_ALWAYS_ON_SAMPLER = "parentbased_always_on" -PARENT_BASED_ALWAYS_OFF_SAMPLER = "parentbased_always_off" -PARENT_BASED_TRACE_ID_RATIO_SAMPLER = "parentbased_trace_id_ratio" -SUPPORTED_OTEL_SAMPLERS = ( - RATE_LIMITED_SAMPLER, - FIXED_PERCENTAGE_SAMPLER, - ALWAYS_ON_SAMPLER, - ALWAYS_OFF_SAMPLER, - TRACE_ID_RATIO_SAMPLER, - PARENT_BASED_ALWAYS_ON_SAMPLER, - PARENT_BASED_ALWAYS_OFF_SAMPLER, - PARENT_BASED_TRACE_ID_RATIO_SAMPLER, -) - -# --------------------Autoinstrumentation Configuration------------------------------------------ - -LOG_EXPORTER_NAMES_ARG = "log_exporter_names" -METRIC_EXPORTER_NAMES_ARG = "metric_exporter_names" -SAMPLER_ARG = "sampler" -TRACE_EXPORTER_NAMES_ARG = "trace_exporter_names" - -LOGGER_NAME_ENV_ARG = "PYTHON_APPLICATIONINSIGHTS_LOGGER_NAME" -LOGGING_FORMAT_ENV_ARG = "PYTHON_APPLICATIONINSIGHTS_LOGGING_FORMAT" - - -# --------------------Diagnostic/status logging------------------------------ - -_LOG_PATH_LINUX = "/var/log/applicationinsights" -_LOG_PATH_WINDOWS = "\\LogFiles\\ApplicationInsights" -_PREVIEW_ENTRY_POINT_WARNING = ( - "Autoinstrumentation for the Azure Monitor OpenTelemetry Distro is in preview." -) - - -# --------------------Instrumentations------------------------------ - -# Opt-out -_AZURE_SDK_INSTRUMENTATION_NAME = "azure_sdk" -_FULLY_SUPPORTED_INSTRUMENTED_LIBRARIES = ( - _AZURE_SDK_INSTRUMENTATION_NAME, - "django", - "fastapi", - "flask", - "psycopg2", - "requests", - "urllib", - "urllib3", -) -# Opt-in -_PREVIEW_INSTRUMENTED_LIBRARIES = () -_ALL_SUPPORTED_INSTRUMENTED_LIBRARIES = ( - _FULLY_SUPPORTED_INSTRUMENTED_LIBRARIES + _PREVIEW_INSTRUMENTED_LIBRARIES -) - -_AZURE_APP_SERVICE_RESOURCE_DETECTOR_NAME = "azure_app_service" -_AZURE_VM_RESOURCE_DETECTOR_NAME = "azure_vm" diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_diagnostics/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_diagnostics/__init__.py deleted file mode 100644 index 0bdee620..00000000 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_diagnostics/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# ------------------------------------------------------------------------- -# 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/_diagnostics/diagnostic_logging.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_diagnostics/diagnostic_logging.py deleted file mode 100644 index 594200dd..00000000 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_diagnostics/diagnostic_logging.py +++ /dev/null @@ -1,140 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License in the project root for -# license information. -# -------------------------------------------------------------------------- - -import logging -import threading -from os import makedirs -from os.path import exists, join - -from azure.monitor.opentelemetry._utils import ( - _EXTENSION_VERSION, - _env_var_or_default, - _get_customer_ikey_from_env_var, - _get_log_path, - _is_diagnostics_enabled, -) -from azure.monitor.opentelemetry._version import VERSION - -# This logger is used for logging messages about the setup of AzureDiagnosticLogging -_logger = logging.getLogger("azure.monitor.opentelemetry._diagnostics") - -_DIAGNOSTIC_LOGGER_FILE_NAME = "applicationinsights-extension.log" -_SITE_NAME = _env_var_or_default("WEBSITE_SITE_NAME") -_SUBSCRIPTION_ID_ENV_VAR = _env_var_or_default("WEBSITE_OWNER_NAME") -_SUBSCRIPTION_ID = ( - _SUBSCRIPTION_ID_ENV_VAR.split("+")[0] if _SUBSCRIPTION_ID_ENV_VAR else None -) -# This logger is used for logging the diagnostic logs themselves to a log file -_diagnostic_file_logger = logging.getLogger(__name__) -_diagnostic_file_logger.propagate = False -_diagnostic_file_logger.setLevel(logging.DEBUG) -_DIAGNOSTIC_LOG_PATH = _get_log_path() - -_DISTRO_DETECTS_ATTACH = "4100" -_INSTRUMENTATION_SKIPPED = "4101" -_INFO = "4102" - -_ATTACH_SUCCESS_DISTRO = "4200" -_ATTACH_SUCCESS_CONFIGURATOR = "4201" -_INSTRUMENTATION_SUCCEEDED = "4202" - -_DEPENDENCY_OVERLAP = "4300" -_BACKOFF_EXPORTER = "4301" -_BACKOFF_CONFLICT = "4302" -_BACKOFF_UNSUPPORTED_PYTHON_VERSION = "4303" - -_ATTACH_FAILURE_DISTRO = "4400" -_ATTACH_FAILURE_CONFIGURATOR = "4401" -_ATTACH_DETECTS_SDK = "4402" -_INSTRUMENTATION_FAILED = "4403" -_EXCEPTION = "4404" - - -class AzureDiagnosticLogging: - _instance = None - _initialized = False - _lock = threading.Lock() - - def __new__(cls): - if cls._instance is None: - with cls._lock: - if cls._instance is None: - cls._instance = super(AzureDiagnosticLogging, cls).__new__(cls) - return cls._instance - - @classmethod - def _initialize(cls): - with cls._lock: - if not cls._initialized: - if _is_diagnostics_enabled() and _DIAGNOSTIC_LOG_PATH: - log_format = ( - "{" - + '"time":"%(asctime)s.%(msecs)03d", ' - + '"level":"%(levelname)s", ' - + '"logger":"%(name)s", ' - + '"message":"%(message)s", ' - + '"properties":{' - + '"operation":"Startup", ' - + f'"siteName":"{_SITE_NAME}", ' - + f'"ikey":"{_get_customer_ikey_from_env_var()}", ' - + f'"extensionVersion":"{_EXTENSION_VERSION}", ' - + f'"sdkVersion":"{VERSION}", ' - + f'"subscriptionId":"{_SUBSCRIPTION_ID}", ' - + '"msgId":"%(msgId)s", ' - + '"language":"python"' - + "}" - + "}" - ) - try: - if not exists(_DIAGNOSTIC_LOG_PATH): - try: - makedirs(_DIAGNOSTIC_LOG_PATH) - # Multi-thread can create a race condition for creating the log file - except FileExistsError: - pass - f_handler = logging.FileHandler( - join(_DIAGNOSTIC_LOG_PATH, _DIAGNOSTIC_LOGGER_FILE_NAME) - ) - formatter = logging.Formatter( - fmt=log_format, datefmt="%Y-%m-%dT%H:%M:%S" - ) - f_handler.setFormatter(formatter) - _diagnostic_file_logger.addHandler(f_handler) - cls._initialized = True - except Exception as e: # pylint: disable=broad-except - _logger.error( - "Failed to initialize Azure Monitor diagnostic logging: %s", - e, - ) # pylint: disable=do-not-log-exceptions-if-not-debug - cls._initialized = False - - @classmethod - def debug(cls, message: str, message_id: str): - if not cls._initialized: - cls._initialize() - if cls._initialized: - _diagnostic_file_logger.debug(message, extra={"msgId": message_id}) - - @classmethod - def info(cls, message: str, message_id: str): - if not cls._initialized: - cls._initialize() - if cls._initialized: - _diagnostic_file_logger.info(message, extra={"msgId": message_id}) - - @classmethod - def warning(cls, message: str, message_id: str): - if not cls._initialized: - cls._initialize() - if cls._initialized: - _diagnostic_file_logger.warning(message, extra={"msgId": message_id}) - - @classmethod - def error(cls, message: str, message_id: str): - if not cls._initialized: - cls._initialize() - if cls._initialized: - _diagnostic_file_logger.error(message, extra={"msgId": message_id}) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_diagnostics/status_logger.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_diagnostics/status_logger.py deleted file mode 100644 index bd6e845c..00000000 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_diagnostics/status_logger.py +++ /dev/null @@ -1,64 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License in the project root for -# license information. -# -------------------------------------------------------------------------- - -from json import dumps -from os import getpid, makedirs -from os.path import exists, join -from platform import node - -from azure.monitor.opentelemetry._utils import ( - _EXTENSION_VERSION, - _get_customer_ikey_from_env_var, - _get_log_path, - _is_diagnostics_enabled, -) -from azure.monitor.opentelemetry._version import VERSION - -_MACHINE_NAME = node() -_STATUS_LOG_PATH = _get_log_path(status_log_path=True) - - -def _get_status_logger_file_name(pid): - return f"status_{_MACHINE_NAME}_{pid}.json" - - -class AzureStatusLogger: - @classmethod - def _get_status_json( - cls, agent_initialized_successfully, pid, reason=None, sdk_present=None - ): - status_json = { - "AgentInitializedSuccessfully": agent_initialized_successfully, - "AppType": "python", - "MachineName": _MACHINE_NAME, - "PID": pid, - "SdkVersion": VERSION, - "Ikey": _get_customer_ikey_from_env_var(), - "ExtensionVersion": _EXTENSION_VERSION, - } - if reason: - status_json["Reason"] = reason - if sdk_present: - status_json["SDKPresent"] = sdk_present - return status_json - - @classmethod - def log_status(cls, agent_initialized_successfully, reason=None, sdk_present=None): - if _is_diagnostics_enabled() and _STATUS_LOG_PATH: - pid = getpid() - status_json = AzureStatusLogger._get_status_json( - agent_initialized_successfully, pid, reason, sdk_present - ) - if not exists(_STATUS_LOG_PATH): - makedirs(_STATUS_LOG_PATH) - # Change to be hostname and pid - status_logger_file_name = _get_status_logger_file_name(pid) - with open( - join(_STATUS_LOG_PATH, status_logger_file_name), "w", encoding="utf8" - ) as f: - f.seek(0) - f.write(dumps(status_json)) - f.truncate() diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_types.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_types.py deleted file mode 100644 index 62f01fc9..00000000 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_types.py +++ /dev/null @@ -1,25 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License in the project root for -# license information. -# -------------------------------------------------------------------------- - -from typing import Sequence, Union - -from opentelemetry.sdk.metrics.export import MetricReader -from opentelemetry.sdk.metrics.view import View -from opentelemetry.sdk.resources import Resource - -ConfigurationValue = Union[ - str, - bool, - int, - float, - Resource, - Sequence[str], - Sequence[bool], - Sequence[int], - Sequence[float], - Sequence[MetricReader], - Sequence[View], -] diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_utils/__init__.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_utils/__init__.py deleted file mode 100644 index a2fb6285..00000000 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_utils/__init__.py +++ /dev/null @@ -1,78 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License in the project root for -# license information. -# -------------------------------------------------------------------------- - -import logging -import platform -from os import environ -from pathlib import Path - -from azure.monitor.opentelemetry.exporter._connection_string_parser import ( # pylint: disable=import-error,no-name-in-module - ConnectionStringParser, -) -from azure.monitor.opentelemetry.exporter._utils import ( # pylint: disable=import-error,no-name-in-module - _is_on_app_service, - _is_on_aks, - _is_on_functions, - _is_attach_enabled, -) -from azure.monitor.opentelemetry._constants import ( - _LOG_PATH_LINUX, - _LOG_PATH_WINDOWS, -) - -logger = logging.getLogger(__name__) - - -# --------------------Diagnostic/status logging------------------------------ - -_CUSTOMER_IKEY_ENV_VAR = None - - -# pylint: disable=global-statement -def _get_customer_ikey_from_env_var(): - global _CUSTOMER_IKEY_ENV_VAR - if not _CUSTOMER_IKEY_ENV_VAR: - _CUSTOMER_IKEY_ENV_VAR = "unknown" - try: - _CUSTOMER_IKEY_ENV_VAR = ConnectionStringParser().instrumentation_key - except ValueError as e: - logger.error( - "Failed to parse Instrumentation Key: %s", e - ) # pylint: disable=C - return _CUSTOMER_IKEY_ENV_VAR - - -# TODO: Add environment variable to enable/disable diagnostics -def _is_diagnostics_enabled(): - if _is_on_functions(): - return False - if _is_on_app_service() or _is_on_aks(): - return _is_attach_enabled() - return False - - -def _get_log_path(status_log_path=False): - system = platform.system() - if system == "Linux": - return _LOG_PATH_LINUX - if system == "Windows": - log_path = str(Path.home()) + _LOG_PATH_WINDOWS - if status_log_path: - return log_path + "\\status" - return log_path - return None - - -def _env_var_or_default(var_name, default_val=""): - try: - return environ[var_name] - except KeyError: - return default_val - - -_EXTENSION_VERSION = _env_var_or_default( - "ApplicationInsightsAgent_EXTENSION_VERSION", "disabled" -) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_utils/configurations.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_utils/configurations.py deleted file mode 100644 index 68dea0e1..00000000 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_utils/configurations.py +++ /dev/null @@ -1,428 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License in the project root for -# license information. -# -------------------------------------------------------------------------- - -from logging import getLogger, Formatter -from os import environ -from typing import Dict - -from opentelemetry.environment_variables import ( - OTEL_LOGS_EXPORTER, - OTEL_METRICS_EXPORTER, - OTEL_TRACES_EXPORTER, -) - -try: - from opentelemetry.instrumentation.environment_variables import ( # type: ignore - OTEL_PYTHON_DISABLED_INSTRUMENTATIONS, - ) -except ImportError: # pragma: no cover - OTEL_PYTHON_DISABLED_INSTRUMENTATIONS = "" -from opentelemetry.sdk.environment_variables import ( - OTEL_EXPERIMENTAL_RESOURCE_DETECTORS, - OTEL_TRACES_SAMPLER_ARG, - OTEL_TRACES_SAMPLER, -) -from opentelemetry.sdk.resources import Resource - -from opentelemetry.sdk.trace.sampling import ( - TraceIdRatioBased, - ALWAYS_OFF, - ALWAYS_ON, - ParentBased, -) - -from azure.monitor.opentelemetry._constants import ( - _AZURE_APP_SERVICE_RESOURCE_DETECTOR_NAME, - _AZURE_VM_RESOURCE_DETECTOR_NAME, - _FULLY_SUPPORTED_INSTRUMENTED_LIBRARIES, - _PREVIEW_INSTRUMENTED_LIBRARIES, - BROWSER_SDK_LOADER_CONFIG_ARG, - CONNECTION_STRING_ARG, - DISABLE_LOGGING_ARG, - DISABLE_METRICS_ARG, - DISABLE_TRACING_ARG, - DISTRO_VERSION_ARG, - ENABLE_LIVE_METRICS_ARG, - ENABLE_PERFORMANCE_COUNTERS_ARG, - INSTRUMENTATION_OPTIONS_ARG, - LOGGER_NAME_ARG, - LOGGER_NAME_ENV_ARG, - LOGGING_FORMATTER_ARG, - LOGGING_FORMAT_ENV_ARG, - RESOURCE_ARG, - SAMPLING_RATIO_ARG, - SAMPLING_TRACES_PER_SECOND_ARG, - SPAN_PROCESSORS_ARG, - LOG_RECORD_PROCESSORS_ARG, - METRIC_READERS_ARG, - VIEWS_ARG, - RATE_LIMITED_SAMPLER, - FIXED_PERCENTAGE_SAMPLER, - ENABLE_TRACE_BASED_SAMPLING_ARG, - SUPPORTED_OTEL_SAMPLERS, - ALWAYS_OFF_SAMPLER, - ALWAYS_ON_SAMPLER, - TRACE_ID_RATIO_SAMPLER, - PARENT_BASED_ALWAYS_ON_SAMPLER, - PARENT_BASED_ALWAYS_OFF_SAMPLER, - PARENT_BASED_TRACE_ID_RATIO_SAMPLER, - SAMPLING_ARG, - SAMPLER_TYPE, -) -from azure.monitor.opentelemetry._types import ConfigurationValue -from azure.monitor.opentelemetry._version import VERSION - -_INVALID_FLOAT_MESSAGE = "Value of %s must be a float. Defaulting to %s: %s" -_INVALID_TRACES_PER_SECOND_MESSAGE = ( - "Value of %s must be a positive number for traces per second. Defaulting to %s: %s" -) -_SUPPORTED_RESOURCE_DETECTORS = ( - _AZURE_APP_SERVICE_RESOURCE_DETECTOR_NAME, - _AZURE_VM_RESOURCE_DETECTOR_NAME, -) - -_logger = getLogger(__name__) - - -def _get_configurations(**kwargs) -> Dict[str, ConfigurationValue]: - configurations = {} - - for key, val in kwargs.items(): - configurations[key] = val - configurations[DISTRO_VERSION_ARG] = VERSION - - _default_disable_logging(configurations) - _default_disable_metrics(configurations) - _default_disable_tracing(configurations) - _default_connection_string(configurations) - _default_logger_name(configurations) - _default_logging_formatter(configurations) - _default_resource(configurations) - _default_sampling_ratio(configurations) - _default_instrumentation_options(configurations) - _default_span_processors(configurations) - _default_log_record_processors(configurations) - _default_metric_readers(configurations) - _default_enable_live_metrics(configurations) - _default_enable_performance_counters(configurations) - _default_views(configurations) - _default_enable_trace_based_sampling(configurations) - _default_browser_sdk_loader(configurations) - - return configurations - - -def _default_disable_logging(configurations): - default = False - if OTEL_LOGS_EXPORTER in environ: - if environ[OTEL_LOGS_EXPORTER].lower().strip() == "none": - default = True - configurations[DISABLE_LOGGING_ARG] = default - - -def _default_disable_metrics(configurations): - default = False - if OTEL_METRICS_EXPORTER in environ: - if environ[OTEL_METRICS_EXPORTER].lower().strip() == "none": - default = True - configurations[DISABLE_METRICS_ARG] = default - - -def _default_disable_tracing(configurations): - default = False - if OTEL_TRACES_EXPORTER in environ: - if environ[OTEL_TRACES_EXPORTER].lower().strip() == "none": - default = True - configurations[DISABLE_TRACING_ARG] = default - - -def _default_connection_string(configurations): - if CONNECTION_STRING_ARG in configurations: - return - env_connection_string = environ.get("APPLICATIONINSIGHTS_CONNECTION_STRING") - if env_connection_string is not None: - configurations[CONNECTION_STRING_ARG] = env_connection_string - - -def _default_logger_name(configurations): - if LOGGER_NAME_ARG in configurations: - return - if LOGGER_NAME_ENV_ARG in environ: - configurations[LOGGER_NAME_ARG] = environ[LOGGER_NAME_ENV_ARG] - else: - configurations.setdefault(LOGGER_NAME_ARG, "") - - -def _default_logging_formatter(configurations): - formatter = configurations.get(LOGGING_FORMATTER_ARG) - if formatter: - if not isinstance(formatter, Formatter): - configurations[LOGGING_FORMATTER_ARG] = None - return - elif LOGGING_FORMAT_ENV_ARG in environ: - try: - configurations[LOGGING_FORMATTER_ARG] = Formatter( - environ[LOGGING_FORMAT_ENV_ARG] - ) - except Exception as ex: # pylint: disable=broad-exception-caught - _logger.warning( # pylint: disable=do-not-log-exceptions-if-not-debug - "Exception occurred when creating logging Formatter from format: %s, %s.", - environ[LOGGING_FORMAT_ENV_ARG], - ex, - ) - configurations[LOGGING_FORMATTER_ARG] = None - - -def _default_resource(configurations): - environ.setdefault( - OTEL_EXPERIMENTAL_RESOURCE_DETECTORS, ",".join(_SUPPORTED_RESOURCE_DETECTORS) - ) - if RESOURCE_ARG not in configurations: - configurations[RESOURCE_ARG] = Resource.create() - else: - configurations[RESOURCE_ARG] = Resource.create( - configurations[RESOURCE_ARG].attributes - ) - - -# pylint: disable=too-many-statements,too-many-branches -def _default_sampling_ratio(configurations): - default_value = 1.0 - default_value_for_rate_limited_sampler = 5.0 - sampler_type = environ.get(OTEL_TRACES_SAMPLER) - sampler_arg = environ.get(OTEL_TRACES_SAMPLER_ARG) - - # Handle rate-limited sampler - if sampler_type == RATE_LIMITED_SAMPLER: - try: - sampler_value = ( - float(sampler_arg) - if sampler_arg is not None - else default_value_for_rate_limited_sampler - ) - if sampler_value < 0.0: - _logger.error( - "Invalid value for OTEL_TRACES_SAMPLER_ARG. It should be a non-negative number." - ) - sampler_value = default_value_for_rate_limited_sampler - else: - _logger.info( - "Using rate limited sampler: %s traces per second", sampler_value - ) - configurations[SAMPLING_TRACES_PER_SECOND_ARG] = sampler_value - except ValueError as e: - _logger.error( # pylint: disable=C - _INVALID_TRACES_PER_SECOND_MESSAGE, - OTEL_TRACES_SAMPLER_ARG, - default_value_for_rate_limited_sampler, - e, - ) - configurations[SAMPLING_TRACES_PER_SECOND_ARG] = ( - default_value_for_rate_limited_sampler - ) - - # Handle fixed percentage sampler - elif sampler_type in ( - FIXED_PERCENTAGE_SAMPLER, - "microsoft.fixed.percentage", - ): # to support older string - try: - sampler_value = ( - float(sampler_arg) if sampler_arg is not None else default_value - ) - if sampler_value < 0.0 or sampler_value > 1.0: - _logger.error( - "Invalid value for OTEL_TRACES_SAMPLER_ARG. It should be a value between 0 and 1." - ) - sampler_value = default_value - else: - _logger.info("Using sampling ratio: %s", sampler_value) - configurations[SAMPLING_RATIO_ARG] = sampler_value - except ValueError as e: - _logger.error( # pylint: disable=C - _INVALID_FLOAT_MESSAGE, - OTEL_TRACES_SAMPLER_ARG, - default_value, - e, - ) - configurations[SAMPLING_RATIO_ARG] = default_value - - # Handle always_on sampler - elif sampler_type == ALWAYS_ON_SAMPLER: - configurations[SAMPLING_ARG] = 1.0 - configurations[SAMPLER_TYPE] = ALWAYS_ON_SAMPLER - - # Handle always_off sampler - elif sampler_type == ALWAYS_OFF_SAMPLER: - configurations[SAMPLING_ARG] = 0.0 - configurations[SAMPLER_TYPE] = ALWAYS_OFF_SAMPLER - - # Handle trace_id_ratio sampler - elif sampler_type == TRACE_ID_RATIO_SAMPLER: - try: - sampler_value = ( - float(sampler_arg) if sampler_arg is not None else default_value - ) - if sampler_value < 0.0 or sampler_value > 1.0: - _logger.error( - "Invalid value for OTEL_TRACES_SAMPLER_ARG. It should be a value between 0 and 1." - ) - sampler_value = default_value - else: - _logger.info("Using sampling value: %s", sampler_value) - configurations[SAMPLING_ARG] = sampler_value - except ValueError as e: - _logger.error( # pylint: disable=C - _INVALID_FLOAT_MESSAGE, - OTEL_TRACES_SAMPLER_ARG, - default_value, - e, - ) - configurations[SAMPLING_ARG] = default_value - configurations[SAMPLER_TYPE] = TRACE_ID_RATIO_SAMPLER - - # Handle parentbased_always_on sampler - elif sampler_type == PARENT_BASED_ALWAYS_ON_SAMPLER: - configurations[SAMPLING_ARG] = 1.0 - configurations[SAMPLER_TYPE] = PARENT_BASED_ALWAYS_ON_SAMPLER - - # Handle parentbased_always_off sampler - elif sampler_type == PARENT_BASED_ALWAYS_OFF_SAMPLER: - configurations[SAMPLING_ARG] = 0.0 - configurations[SAMPLER_TYPE] = PARENT_BASED_ALWAYS_OFF_SAMPLER - - # Handle parentbased_trace_id_ratio sampler - elif sampler_type == PARENT_BASED_TRACE_ID_RATIO_SAMPLER: - try: - sampler_value = ( - float(sampler_arg) if sampler_arg is not None else default_value - ) - if sampler_value < 0.0 or sampler_value > 1.0: - _logger.error( - "Invalid value for OTEL_TRACES_SAMPLER_ARG. It should be a value between 0 and 1." - ) - sampler_value = default_value - else: - _logger.info("Using sampling value: %s", sampler_value) - configurations[SAMPLING_ARG] = sampler_value - except ValueError as e: - _logger.error( # pylint: disable=C - _INVALID_FLOAT_MESSAGE, - OTEL_TRACES_SAMPLER_ARG, - default_value, - e, - ) - configurations[SAMPLING_ARG] = default_value - configurations[SAMPLER_TYPE] = PARENT_BASED_TRACE_ID_RATIO_SAMPLER - - # Handle all other cases (no sampler type specified or unsupported sampler type) - else: - if configurations.get(SAMPLING_TRACES_PER_SECOND_ARG) is None: - configurations[SAMPLING_TRACES_PER_SECOND_ARG] = ( - default_value_for_rate_limited_sampler - ) - if sampler_type is not None: - _logger.error( # pylint: disable=C - "Invalid argument for the sampler to be used for tracing. " - "Supported values are %s. Defaulting to %s: %s", - SUPPORTED_OTEL_SAMPLERS, - RATE_LIMITED_SAMPLER, - configurations[SAMPLING_TRACES_PER_SECOND_ARG], - ) - - -def _default_instrumentation_options(configurations): - otel_disabled_instrumentations = _get_otel_disabled_instrumentations() - - merged_instrumentation_options = {} - instrumentation_options = configurations.get(INSTRUMENTATION_OPTIONS_ARG, {}) - for lib_name in _FULLY_SUPPORTED_INSTRUMENTED_LIBRARIES: - disabled_by_env_var = lib_name in otel_disabled_instrumentations - options = {"enabled": not disabled_by_env_var} - options.update(instrumentation_options.get(lib_name, {})) - merged_instrumentation_options[lib_name] = options - for lib_name in _PREVIEW_INSTRUMENTED_LIBRARIES: - options = {"enabled": False} - options.update(instrumentation_options.get(lib_name, {})) - merged_instrumentation_options[lib_name] = options - - configurations[INSTRUMENTATION_OPTIONS_ARG] = merged_instrumentation_options - - -def _default_span_processors(configurations): - configurations.setdefault(SPAN_PROCESSORS_ARG, []) - - -def _default_log_record_processors(configurations): - configurations.setdefault(LOG_RECORD_PROCESSORS_ARG, []) - - -def _default_metric_readers(configurations): - configurations.setdefault(METRIC_READERS_ARG, []) - - -def _default_enable_live_metrics(configurations): - configurations.setdefault(ENABLE_LIVE_METRICS_ARG, True) - - -def _default_enable_performance_counters(configurations): - configurations.setdefault(ENABLE_PERFORMANCE_COUNTERS_ARG, True) - - -def _default_views(configurations): - configurations.setdefault(VIEWS_ARG, []) - - -def _default_browser_sdk_loader(configurations): - """Set default browser SDK loader configuration. - - :param configurations: Configuration dictionary to update. - :type configurations: dict - """ - # Use cast to Dict[str, Any] to avoid MyPy ConfigurationValue Union type issues - from typing import cast, Any - - configurations.setdefault(BROWSER_SDK_LOADER_CONFIG_ARG, cast(Dict[str, Any], {})) - - -def _get_otel_disabled_instrumentations(): - disabled_instrumentation = environ.get(OTEL_PYTHON_DISABLED_INSTRUMENTATIONS, "") - disabled_instrumentation = disabled_instrumentation.split(",") - # to handle users entering "requests , flask" or "requests, flask" with spaces - disabled_instrumentation = [x.strip() for x in disabled_instrumentation] - return disabled_instrumentation - - -def _is_instrumentation_enabled(configurations, lib_name): - if INSTRUMENTATION_OPTIONS_ARG not in configurations: - return False - instrumentation_options = configurations[INSTRUMENTATION_OPTIONS_ARG] - if lib_name not in instrumentation_options: - return False - library_options = instrumentation_options[lib_name] - if "enabled" not in library_options: - return False - return library_options["enabled"] is True - - -def _default_enable_trace_based_sampling(configurations): - configurations.setdefault(ENABLE_TRACE_BASED_SAMPLING_ARG, False) - - -def _get_sampler_from_name(sampler_type, sampler_arg): - if sampler_type == ALWAYS_ON_SAMPLER: - return ALWAYS_ON - if sampler_type == ALWAYS_OFF_SAMPLER: - return ALWAYS_OFF - if sampler_type == TRACE_ID_RATIO_SAMPLER: - ratio = float(sampler_arg) if sampler_arg is not None else 1.0 - return TraceIdRatioBased(ratio) - if sampler_type == PARENT_BASED_ALWAYS_OFF_SAMPLER: - return ParentBased(ALWAYS_OFF) - if sampler_type == PARENT_BASED_TRACE_ID_RATIO_SAMPLER: - ratio = float(sampler_arg) if sampler_arg is not None else 1.0 - return ParentBased(TraceIdRatioBased(ratio)) - return ParentBased(ALWAYS_ON) diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_utils/instrumentation.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_utils/instrumentation.py deleted file mode 100644 index 80fe30cd..00000000 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_utils/instrumentation.py +++ /dev/null @@ -1,160 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License in the project root for -# license information. -# -------------------------------------------------------------------------- - -from __future__ import annotations - -from logging import getLogger -from typing import Collection - -from packaging.requirements import InvalidRequirement, Requirement - -from opentelemetry.util._importlib_metadata import ( - Distribution, - PackageNotFoundError, - version, -) - -logger = getLogger(__name__) - -# --------------------Instrumentation------------------------------ - -# The below classes/functions are vendored from upstream OpenTelemetry Python due to being experimental. -# Due to possible breaking changes in the upstream code, we have decided to vendor this code - -# TODO: Remove this vendored code once the upstream code is stable and released. -# Original breaking PR: https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3202 -# Tracking issue: https://github.com/open-telemetry/opentelemetry-python-contrib/issues/3434 - - -class DependencyConflict: - required: str | None = None - found: str | None = None - # The following fields are used when an instrumentation requires any of a set of dependencies rather than all. - required_any: list[str] | None = None - found_any: list[str] | None = None - - def __init__( - self, - required: str | None = None, - found: str | None = None, - required_any: list[str] | None = None, - found_any: list[str] | None = None, - ): - self.required = required - self.found = found - self.required_any = required_any - self.found_any = found_any - - def __str__(self): - if not self.required and (self.required_any or self.found_any): - return ( - f'DependencyConflict: requested any of the following: "{self.required_any}" ' - f'but found: "{self.found_any}"' - ) - return f'DependencyConflict: requested: "{self.required}" but found: "{self.found}"' - - -def get_dist_dependency_conflicts( - dist: Distribution, -) -> DependencyConflict | None: - instrumentation_deps = [] - instrumentation_any_deps = [] - extra = "extra" - instruments = "instruments" - instruments_any = "instruments-any" - instruments_marker = {extra: instruments} - instruments_any_marker = {extra: instruments_any} - if dist.requires: - for dep in dist.requires: - if ( - extra not in dep - or instruments not in dep - and instruments_any not in dep - ): - continue - - req = Requirement(dep) - if req.marker.evaluate(instruments_marker): # type: ignore - instrumentation_deps.append(req) - if req.marker.evaluate(instruments_any_marker): # type: ignore - instrumentation_any_deps.append(req) - - return get_dependency_conflicts(instrumentation_deps, instrumentation_any_deps) - - -def get_dependency_conflicts( - deps: Collection[str | Requirement], - deps_any: Collection[str | Requirement] | None = None, -) -> DependencyConflict | None: - for dep in deps: - if isinstance(dep, Requirement): - req = dep - else: - try: - req = Requirement(dep) - except InvalidRequirement as exc: - logger.warning( # pylint: disable=do-not-log-exceptions-if-not-debug - 'error parsing dependency, reporting as a conflict: "%s" - %s', - dep, - exc, - ) - return DependencyConflict(dep) - - try: - dist_version = version(req.name) - except PackageNotFoundError: - return DependencyConflict(dep) # type: ignore - - if not req.specifier.contains(dist_version): - return DependencyConflict(dep, f"{req.name} {dist_version}") # type: ignore - - if deps_any: - return _get_dependency_conflicts_any(deps_any) - return None - - -def _get_dependency_conflicts_any( - deps_any: Collection[str | Requirement], -) -> DependencyConflict | None: - if not deps_any: - return None - is_dependency_conflict = True - required_any: list[str] = [] - found_any: list[str] = [] - for dep in deps_any: - if isinstance(dep, Requirement): - req = dep - else: - try: - req = Requirement(dep) - except InvalidRequirement as exc: - logger.warning( # pylint: disable=do-not-log-exceptions-if-not-debug - 'error parsing dependency, reporting as a conflict: "%s" - %s', - dep, - exc, - ) - return DependencyConflict(dep) - - try: - dist_version = version(req.name) - except PackageNotFoundError: - required_any.append(str(dep)) - continue - - if req.specifier.contains(dist_version): - # Since only one of the instrumentation_any dependencies is required, there is no dependency conflict. - is_dependency_conflict = False - break - # If the version does not match, add it to the list of unfulfilled requirement options. - required_any.append(str(dep)) - found_any.append(f"{req.name} {dist_version}") - - if is_dependency_conflict: - return DependencyConflict( - required_any=required_any, - found_any=found_any, - ) - return None diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_version.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_version.py deleted file mode 100644 index e1817857..00000000 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_version.py +++ /dev/null @@ -1,7 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License in the project root for -# license information. -# -------------------------------------------------------------------------- - -VERSION = "1.8.8" diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/py.typed b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/py.typed deleted file mode 100644 index cd38a61c..00000000 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/py.typed +++ /dev/null @@ -1,7 +0,0 @@ -recursive-include tests *.py -recursive-include samples *.py -include *.md -include LICENSE -include azure/__init__.py -include azure/monitor/__init__.py -include azure/monitor/opentelemetry/py.typed \ No newline at end of file diff --git a/azure-monitor-opentelemetry/pyproject.toml b/azure-monitor-opentelemetry/pyproject.toml deleted file mode 100644 index 99506856..00000000 --- a/azure-monitor-opentelemetry/pyproject.toml +++ /dev/null @@ -1,8 +0,0 @@ -[tool.azure-sdk-build] -pyright = false -pylint = true -bandit = true -black = true - -[tool.azure-sdk-conda] -in_bundle = false diff --git a/azure-monitor-opentelemetry/setup.py b/azure-monitor-opentelemetry/setup.py deleted file mode 100644 index 50b20a4c..00000000 --- a/azure-monitor-opentelemetry/setup.py +++ /dev/null @@ -1,107 +0,0 @@ -#!/usr/bin/env python - -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -------------------------------------------------------------------------- - - -import os -import re - -from setuptools import find_packages, setup - -# Change the PACKAGE_NAME only to change folder and different name -PACKAGE_NAME = "azure-monitor-opentelemetry" -PACKAGE_PPRINT_NAME = "Azure Monitor Opentelemetry Distro" - -# a-b-c => a/b/c -package_folder_path = PACKAGE_NAME.replace("-", "/") - - -# azure v0.x is not compatible with this package -# azure v0.x used to have a __version__ attribute (newer versions don't) -try: - import azure - - try: - ver = azure.__version__ - raise Exception( - "This package is incompatible with azure=={}. ".format(ver) + 'Uninstall it with "pip uninstall azure".' - ) - except AttributeError: - pass -except ImportError: - pass - -# Version extraction inspired from 'requests' -with open(os.path.join(package_folder_path, "_version.py"), "r") as fd: - version = re.search(r'^VERSION\s*=\s*[\'"]([^\'"]*)[\'"]', fd.read(), re.MULTILINE).group(1) - -if not version: - raise RuntimeError("Cannot find version information") - -setup( - name=PACKAGE_NAME, - version=version, - description="Microsoft {} Client Library for Python".format(PACKAGE_PPRINT_NAME), - long_description=open("README.md", "r").read(), - long_description_content_type="text/markdown", - license="MIT License", - author="Microsoft Corporation", - author_email="ascl@microsoft.com", - url="https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/monitor/azure-monitor-opentelemetry", - keywords="azure, azure sdk", - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Developers", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - "Programming Language :: Python :: 3.14", - "License :: OSI Approved :: MIT License", - ], - zip_safe=False, - packages=find_packages( - exclude=[ - "tests", - "samples", - # Exclude packages that will be covered by PEP420 or nspkg - "azure", - "azure.monitor", - ] - ), - include_package_data=True, - package_data={ - "azure.monitor.opentelemetry": ["py.typed"], - }, - python_requires=">=3.8", - install_requires=[ - "azure-core<2.0.0,>=1.28.0", - "azure-core-tracing-opentelemetry~=1.0.0b11", - "azure-monitor-opentelemetry-exporter~=1.0.0b49", - "opentelemetry-sdk==1.40", - "opentelemetry-instrumentation-django==0.61b0", - "opentelemetry-instrumentation-fastapi==0.61b0", - "opentelemetry-instrumentation-flask==0.61b0", - "opentelemetry-instrumentation-psycopg2==0.61b0", - "opentelemetry-instrumentation-requests==0.61b0", - "opentelemetry-instrumentation-urllib==0.61b0", - "opentelemetry-instrumentation-urllib3==0.61b0", - "opentelemetry-instrumentation-logging==0.61b0", - "opentelemetry-resource-detector-azure<1.0.0,>=0.1.5", - ], - entry_points={ - "opentelemetry_distro": [ - "azure_monitor_opentelemetry_distro = azure.monitor.opentelemetry._autoinstrumentation.distro:AzureMonitorDistro" - ], - "opentelemetry_configurator": [ - "azure_monitor_opentelemetry_configurator = azure.monitor.opentelemetry._autoinstrumentation.configurator:AzureMonitorConfigurator" - ], - }, -) From 1fe0f692fbd1112790b46fc8fa021eae65fbddb4 Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Thu, 9 Apr 2026 11:58:00 -0700 Subject: [PATCH 2/2] Remove CHANGELOG --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77943dbf..a357f80d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,8 +19,6 @@ ### Other Changes -- Remove the duplicate azure monitor folder - - Add support for local mypy, pylint, black checks ([#14](https://github.com/microsoft/opentelemetry-distro-python/pull/14)) - Add mypy and pyright checks