From de65c50b104f9172471de19f79fc7d4e70350128 Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Thu, 10 Jul 2025 12:36:14 -0700 Subject: [PATCH 1/5] RateLimitedSampler distro changes --- .../azure-monitor-opentelemetry/CHANGELOG.md | 1 + .../azure/monitor/opentelemetry/_configure.py | 18 +- .../azure/monitor/opentelemetry/_constants.py | 4 +- .../opentelemetry/_utils/configurations.py | 54 +++++- .../tests/utils/test_configurations.py | 175 +++++++++++++++++- 5 files changed, 230 insertions(+), 22 deletions(-) diff --git a/sdk/monitor/azure-monitor-opentelemetry/CHANGELOG.md b/sdk/monitor/azure-monitor-opentelemetry/CHANGELOG.md index 2f8986a85ba1..940e724492b7 100644 --- a/sdk/monitor/azure-monitor-opentelemetry/CHANGELOG.md +++ b/sdk/monitor/azure-monitor-opentelemetry/CHANGELOG.md @@ -3,6 +3,7 @@ ## 1.6.11 (Unreleased) ### Features Added +- Added RateLimited Sampler Config changes ### Breaking Changes diff --git a/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py b/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py index 07412ad8cdc1..d13e12cbf6bb 100644 --- a/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py +++ b/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py @@ -35,6 +35,7 @@ LOGGING_FORMATTER_ARG, RESOURCE_ARG, SAMPLING_RATIO_ARG, + SAMPLING_TRACES_PER_SECOND_ARG, SPAN_PROCESSORS_ARG, VIEWS_ARG, ) @@ -50,6 +51,7 @@ ApplicationInsightsSampler, AzureMonitorMetricExporter, AzureMonitorTraceExporter, + RateLimitedSampler, ) from azure.monitor.opentelemetry.exporter._utils import ( # pylint: disable=import-error,no-name-in-module _is_attach_enabled, @@ -133,10 +135,18 @@ def configure_azure_monitor(**kwargs) -> None: # pylint: disable=C4758 def _setup_tracing(configurations: Dict[str, ConfigurationValue]): resource: Resource = configurations[RESOURCE_ARG] # type: ignore - sampling_ratio = configurations[SAMPLING_RATIO_ARG] - tracer_provider = TracerProvider( - sampler=ApplicationInsightsSampler(sampling_ratio=cast(float, sampling_ratio)), resource=resource - ) + if SAMPLING_TRACES_PER_SECOND_ARG in configurations: + sampling_traces_per_second = configurations[SAMPLING_TRACES_PER_SECOND_ARG] + tracer_provider = TracerProvider( + sampler=RateLimitedSampler(sampling_ratio=cast(float, sampling_traces_per_second), resource=resource) + ) + else: + sampling_ratio = configurations[SAMPLING_RATIO_ARG] + tracer_provider = TracerProvider( + sampler=ApplicationInsightsSampler(sampling_ratio=cast(float, sampling_ratio)), 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): diff --git a/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_constants.py b/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_constants.py index af0d9ec0fedb..5d6c3a0ea71b 100644 --- a/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_constants.py +++ b/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_constants.py @@ -24,7 +24,9 @@ SAMPLING_RATIO_ARG = "sampling_ratio" SPAN_PROCESSORS_ARG = "span_processors" VIEWS_ARG = "views" - +RATE_LIMITED_SAMPLER = "microsoft.rate_limited" +FIXED_PERCENTAGE_SAMPLER = "microsoft.fixed.percentage" +SAMPLING_TRACES_PER_SECOND_ARG = "sampling_traces_per_second" # --------------------Autoinstrumentation Configuration------------------------------------------ diff --git a/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_utils/configurations.py b/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_utils/configurations.py index c84a6bdb2cff..3558a4de7195 100644 --- a/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_utils/configurations.py +++ b/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_utils/configurations.py @@ -19,6 +19,7 @@ from opentelemetry.sdk.environment_variables import ( OTEL_EXPERIMENTAL_RESOURCE_DETECTORS, OTEL_TRACES_SAMPLER_ARG, + OTEL_TRACES_SAMPLER ) from opentelemetry.sdk.resources import Resource @@ -37,25 +38,25 @@ LOGGING_FORMATTER_ARG, RESOURCE_ARG, SAMPLING_RATIO_ARG, + SAMPLING_TRACES_PER_SECOND_ARG, SPAN_PROCESSORS_ARG, VIEWS_ARG, + RATE_LIMITED_SAMPLER, + FIXED_PERCENTAGE_SAMPLER, ) 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, ) -# TODO: remove when sampler uses env var instead -SAMPLING_RATIO_ENV_VAR = OTEL_TRACES_SAMPLER_ARG - _logger = getLogger(__name__) - def _get_configurations(**kwargs) -> Dict[str, ConfigurationValue]: configurations = {} @@ -120,21 +121,54 @@ def _default_resource(configurations): configurations[RESOURCE_ARG] = Resource.create(configurations[RESOURCE_ARG].attributes) -# TODO: remove when sampler uses env var instead def _default_sampling_ratio(configurations): default = 1.0 - if SAMPLING_RATIO_ENV_VAR in environ: + + if environ.get(OTEL_TRACES_SAMPLER_ARG) is not None: + try: + if float(environ[OTEL_TRACES_SAMPLER_ARG]) < 0: + _logger.error("Invalid value for OTEL_TRACES_SAMPLER_ARG. It should be a non-negative number.") + except ValueError: + pass + else: + _logger.error("OTEL_TRACES_SAMPLER_ARG is not set.") + + # Check if rate-limited sampler is configured + if environ.get(OTEL_TRACES_SAMPLER) == RATE_LIMITED_SAMPLER: + try: + default = float(environ[OTEL_TRACES_SAMPLER_ARG]) + print(f"Using rate limited sampler: {default} traces per second") + except ValueError as e: + _logger.error( # pylint: disable=C + _INVALID_TRACES_PER_SECOND_MESSAGE, + OTEL_TRACES_SAMPLER_ARG, + default, + e, + ) + configurations[SAMPLING_TRACES_PER_SECOND_ARG] = default + elif environ.get(OTEL_TRACES_SAMPLER) == FIXED_PERCENTAGE_SAMPLER: try: - default = float(environ[SAMPLING_RATIO_ENV_VAR]) + default = float(environ[OTEL_TRACES_SAMPLER_ARG]) + print(f"Using sampling ratio: {default}") except ValueError as e: _logger.error( # pylint: disable=C _INVALID_FLOAT_MESSAGE, - SAMPLING_RATIO_ENV_VAR, + OTEL_TRACES_SAMPLER_ARG, default, e, ) - configurations[SAMPLING_RATIO_ARG] = default - + configurations[SAMPLING_RATIO_ARG] = default + else: + # Default behavior - always set sampling_ratio + configurations[SAMPLING_RATIO_ARG] = default + _logger.error( # pylint: disable=C + "Invalid argument for the sampler to be used for tracing. " + "Supported values are %s and %s. Defaulting to %s: %s", + RATE_LIMITED_SAMPLER, + FIXED_PERCENTAGE_SAMPLER, + OTEL_TRACES_SAMPLER, + OTEL_TRACES_SAMPLER_ARG, + ) def _default_instrumentation_options(configurations): otel_disabled_instrumentations = _get_otel_disabled_instrumentations() diff --git a/sdk/monitor/azure-monitor-opentelemetry/tests/utils/test_configurations.py b/sdk/monitor/azure-monitor-opentelemetry/tests/utils/test_configurations.py index 7663e5bec6c9..e0a694828f52 100644 --- a/sdk/monitor/azure-monitor-opentelemetry/tests/utils/test_configurations.py +++ b/sdk/monitor/azure-monitor-opentelemetry/tests/utils/test_configurations.py @@ -16,13 +16,20 @@ from unittest import TestCase from unittest.mock import patch -from opentelemetry.instrumentation.environment_variables import ( - OTEL_PYTHON_DISABLED_INSTRUMENTATIONS, +from opentelemetry.sdk.environment_variables import ( + OTEL_EXPERIMENTAL_RESOURCE_DETECTORS, + OTEL_TRACES_SAMPLER_ARG, + OTEL_TRACES_SAMPLER ) +from opentelemetry.instrumentation.environment_variables import ( + OTEL_PYTHON_DISABLED_INSTRUMENTATIONS,) from azure.monitor.opentelemetry._utils.configurations import ( - SAMPLING_RATIO_ENV_VAR, _get_configurations, ) +from azure.monitor.opentelemetry._constants import ( + RATE_LIMITED_SAMPLER, + FIXED_PERCENTAGE_SAMPLER, +) from opentelemetry.environment_variables import ( OTEL_LOGS_EXPORTER, OTEL_METRICS_EXPORTER, @@ -134,7 +141,7 @@ def test_get_configurations_defaults(self, resource_create_mock): "os.environ", { OTEL_PYTHON_DISABLED_INSTRUMENTATIONS: "flask,requests,fastapi,azure_sdk", - SAMPLING_RATIO_ENV_VAR: "0.5", + OTEL_TRACES_SAMPLER_ARG: "0.5", OTEL_TRACES_EXPORTER: "None", OTEL_LOGS_EXPORTER: "none", OTEL_METRICS_EXPORTER: "NONE", @@ -166,12 +173,13 @@ def test_get_configurations_env_vars(self, resource_create_mock): self.assertEqual(configurations["resource"].attributes, TEST_DEFAULT_RESOURCE.attributes) self.assertEqual(environ[OTEL_EXPERIMENTAL_RESOURCE_DETECTORS], "custom_resource_detector") resource_create_mock.assert_called_once_with() - self.assertEqual(configurations["sampling_ratio"], 0.5) + self.assertEqual(configurations["sampling_ratio"], 1.0) @patch.dict( "os.environ", { - SAMPLING_RATIO_ENV_VAR: "Half", + OTEL_TRACES_SAMPLER: FIXED_PERCENTAGE_SAMPLER, + OTEL_TRACES_SAMPLER_ARG: "Half", OTEL_TRACES_EXPORTER: "False", OTEL_LOGS_EXPORTER: "no", OTEL_METRICS_EXPORTER: "True", @@ -181,7 +189,7 @@ def test_get_configurations_env_vars(self, resource_create_mock): @patch("opentelemetry.sdk.resources.Resource.create", return_value=TEST_DEFAULT_RESOURCE) def test_get_configurations_env_vars_validation(self, resource_create_mock): configurations = _get_configurations() - + print(configurations) self.assertTrue("connection_string" not in configurations) self.assertEqual(configurations["disable_logging"], False) self.assertEqual(configurations["disable_metrics"], False) @@ -260,3 +268,156 @@ def test_merge_instrumentation_options_extra_args(self, resource_create_mock): "urllib3": {"enabled": True}, }, ) + @patch.dict( + "os.environ", + { + OTEL_PYTHON_DISABLED_INSTRUMENTATIONS: "flask,requests,fastapi,azure_sdk", + OTEL_TRACES_SAMPLER: RATE_LIMITED_SAMPLER, + OTEL_TRACES_SAMPLER_ARG: "0.5", + OTEL_TRACES_EXPORTER: "None", + OTEL_LOGS_EXPORTER: "none", + OTEL_METRICS_EXPORTER: "NONE", + OTEL_EXPERIMENTAL_RESOURCE_DETECTORS: "custom_resource_detector", + }, + clear=True, + ) + @patch("opentelemetry.sdk.resources.Resource.create", return_value=TEST_DEFAULT_RESOURCE) + def test_get_configurations_env_vars_rate_limited(self, resource_create_mock): + configurations = _get_configurations() + + self.assertTrue("connection_string" not in configurations) + self.assertEqual(configurations["disable_logging"], True) + self.assertEqual(configurations["disable_metrics"], True) + self.assertEqual(configurations["disable_tracing"], True) + self.assertEqual( + configurations["instrumentation_options"], + { + "azure_sdk": {"enabled": False}, + "django": {"enabled": True}, + "fastapi": {"enabled": False}, + "flask": {"enabled": False}, + "psycopg2": {"enabled": True}, + "requests": {"enabled": False}, + "urllib": {"enabled": True}, + "urllib3": {"enabled": True}, + }, + ) + self.assertEqual(configurations["resource"].attributes, TEST_DEFAULT_RESOURCE.attributes) + self.assertEqual(environ[OTEL_EXPERIMENTAL_RESOURCE_DETECTORS], "custom_resource_detector") + resource_create_mock.assert_called_once_with() + self.assertEqual(configurations["sampling_traces_per_second"], 0.5) + + @patch.dict( + "os.environ", + { + OTEL_PYTHON_DISABLED_INSTRUMENTATIONS: "flask,requests,fastapi,azure_sdk", + OTEL_TRACES_SAMPLER_ARG: "34", + OTEL_TRACES_EXPORTER: "None", + OTEL_LOGS_EXPORTER: "none", + OTEL_METRICS_EXPORTER: "NONE", + OTEL_EXPERIMENTAL_RESOURCE_DETECTORS: "custom_resource_detector", + }, + clear=True, + ) + @patch("opentelemetry.sdk.resources.Resource.create", return_value=TEST_DEFAULT_RESOURCE) + def test_get_configurations_env_vars_no_preference(self, resource_create_mock): + configurations = _get_configurations() + + self.assertTrue("connection_string" not in configurations) + self.assertEqual(configurations["disable_logging"], True) + self.assertEqual(configurations["disable_metrics"], True) + self.assertEqual(configurations["disable_tracing"], True) + self.assertEqual( + configurations["instrumentation_options"], + { + "azure_sdk": {"enabled": False}, + "django": {"enabled": True}, + "fastapi": {"enabled": False}, + "flask": {"enabled": False}, + "psycopg2": {"enabled": True}, + "requests": {"enabled": False}, + "urllib": {"enabled": True}, + "urllib3": {"enabled": True}, + }, + ) + self.assertEqual(configurations["resource"].attributes, TEST_DEFAULT_RESOURCE.attributes) + self.assertEqual(environ[OTEL_EXPERIMENTAL_RESOURCE_DETECTORS], "custom_resource_detector") + resource_create_mock.assert_called_once_with() + self.assertEqual(configurations["sampling_ratio"], 1.0) + + @patch.dict( + "os.environ", + { + OTEL_PYTHON_DISABLED_INSTRUMENTATIONS: "flask,requests,fastapi,azure_sdk", + OTEL_TRACES_SAMPLER_ARG: "2 traces per second", + OTEL_TRACES_EXPORTER: "None", + OTEL_LOGS_EXPORTER: "none", + OTEL_METRICS_EXPORTER: "NONE", + OTEL_EXPERIMENTAL_RESOURCE_DETECTORS: "custom_resource_detector", + }, + clear=True, + ) + @patch("opentelemetry.sdk.resources.Resource.create", return_value=TEST_DEFAULT_RESOURCE) + def test_get_configurations_env_vars_check_default(self, resource_create_mock): + configurations = _get_configurations() + + self.assertTrue("connection_string" not in configurations) + self.assertEqual(configurations["disable_logging"], True) + self.assertEqual(configurations["disable_metrics"], True) + self.assertEqual(configurations["disable_tracing"], True) + self.assertEqual( + configurations["instrumentation_options"], + { + "azure_sdk": {"enabled": False}, + "django": {"enabled": True}, + "fastapi": {"enabled": False}, + "flask": {"enabled": False}, + "psycopg2": {"enabled": True}, + "requests": {"enabled": False}, + "urllib": {"enabled": True}, + "urllib3": {"enabled": True}, + }, + ) + self.assertEqual(configurations["resource"].attributes, TEST_DEFAULT_RESOURCE.attributes) + self.assertEqual(environ[OTEL_EXPERIMENTAL_RESOURCE_DETECTORS], "custom_resource_detector") + resource_create_mock.assert_called_once_with() + self.assertEqual(configurations["sampling_ratio"], 1.0) + + @patch.dict( + "os.environ", + { + OTEL_PYTHON_DISABLED_INSTRUMENTATIONS: "flask,requests,fastapi,azure_sdk", + OTEL_TRACES_SAMPLER: FIXED_PERCENTAGE_SAMPLER, + OTEL_TRACES_SAMPLER_ARG: "0.9", + OTEL_TRACES_EXPORTER: "None", + OTEL_LOGS_EXPORTER: "none", + OTEL_METRICS_EXPORTER: "NONE", + OTEL_EXPERIMENTAL_RESOURCE_DETECTORS: "custom_resource_detector", + }, + clear=True, + ) + @patch("opentelemetry.sdk.resources.Resource.create", return_value=TEST_DEFAULT_RESOURCE) + def test_get_configurations_env_vars_fixed_percentage(self, resource_create_mock): + configurations = _get_configurations() + + self.assertTrue("connection_string" not in configurations) + self.assertEqual(configurations["disable_logging"], True) + self.assertEqual(configurations["disable_metrics"], True) + self.assertEqual(configurations["disable_tracing"], True) + self.assertEqual( + configurations["instrumentation_options"], + { + "azure_sdk": {"enabled": False}, + "django": {"enabled": True}, + "fastapi": {"enabled": False}, + "flask": {"enabled": False}, + "psycopg2": {"enabled": True}, + "requests": {"enabled": False}, + "urllib": {"enabled": True}, + "urllib3": {"enabled": True}, + }, + ) + self.assertEqual(configurations["resource"].attributes, TEST_DEFAULT_RESOURCE.attributes) + self.assertEqual(environ[OTEL_EXPERIMENTAL_RESOURCE_DETECTORS], "custom_resource_detector") + resource_create_mock.assert_called_once_with() + self.assertEqual(configurations["sampling_ratio"], 0.9) \ No newline at end of file From c9c635d5c21b3d6f6899a6746cbfcbff887b81f7 Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Thu, 10 Jul 2025 13:19:30 -0700 Subject: [PATCH 2/5] Fixed formatting errors --- .../azure/monitor/opentelemetry/_configure.py | 2 +- .../azure/monitor/opentelemetry/_utils/configurations.py | 4 ++-- .../tests/utils/test_configurations.py | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py b/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py index d13e12cbf6bb..804d965d3890 100644 --- a/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py +++ b/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py @@ -138,7 +138,7 @@ def _setup_tracing(configurations: Dict[str, ConfigurationValue]): if SAMPLING_TRACES_PER_SECOND_ARG in configurations: sampling_traces_per_second = configurations[SAMPLING_TRACES_PER_SECOND_ARG] tracer_provider = TracerProvider( - sampler=RateLimitedSampler(sampling_ratio=cast(float, sampling_traces_per_second), resource=resource) + sampler=RateLimitedSampler(sampling_traces_per_second=cast(float, sampling_traces_per_second)), resource=resource ) else: sampling_ratio = configurations[SAMPLING_RATIO_ARG] diff --git a/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_utils/configurations.py b/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_utils/configurations.py index 3558a4de7195..c15eac199ccf 100644 --- a/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_utils/configurations.py +++ b/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_utils/configurations.py @@ -137,7 +137,7 @@ def _default_sampling_ratio(configurations): if environ.get(OTEL_TRACES_SAMPLER) == RATE_LIMITED_SAMPLER: try: default = float(environ[OTEL_TRACES_SAMPLER_ARG]) - print(f"Using rate limited sampler: {default} traces per second") + _logger.info(f"Using rate limited sampler: {default} traces per second") except ValueError as e: _logger.error( # pylint: disable=C _INVALID_TRACES_PER_SECOND_MESSAGE, @@ -149,7 +149,7 @@ def _default_sampling_ratio(configurations): elif environ.get(OTEL_TRACES_SAMPLER) == FIXED_PERCENTAGE_SAMPLER: try: default = float(environ[OTEL_TRACES_SAMPLER_ARG]) - print(f"Using sampling ratio: {default}") + _logger.info(f"Using sampling ratio: {default}") except ValueError as e: _logger.error( # pylint: disable=C _INVALID_FLOAT_MESSAGE, diff --git a/sdk/monitor/azure-monitor-opentelemetry/tests/utils/test_configurations.py b/sdk/monitor/azure-monitor-opentelemetry/tests/utils/test_configurations.py index e0a694828f52..3b4e31be1c2d 100644 --- a/sdk/monitor/azure-monitor-opentelemetry/tests/utils/test_configurations.py +++ b/sdk/monitor/azure-monitor-opentelemetry/tests/utils/test_configurations.py @@ -189,7 +189,6 @@ def test_get_configurations_env_vars(self, resource_create_mock): @patch("opentelemetry.sdk.resources.Resource.create", return_value=TEST_DEFAULT_RESOURCE) def test_get_configurations_env_vars_validation(self, resource_create_mock): configurations = _get_configurations() - print(configurations) self.assertTrue("connection_string" not in configurations) self.assertEqual(configurations["disable_logging"], False) self.assertEqual(configurations["disable_metrics"], False) From 302bc6564cdeb7df5f30e64c4d47be123a074088 Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Fri, 25 Jul 2025 16:10:57 -0700 Subject: [PATCH 3/5] Updated config and tests --- .../azure/monitor/opentelemetry/_configure.py | 5 +- .../opentelemetry/_utils/configurations.py | 71 +++++++++++-------- .../tests/test_configure.py | 60 ++++++++++++++++ 3 files changed, 106 insertions(+), 30 deletions(-) diff --git a/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py b/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py index 55c6ecee0b8d..6b9d66100ca1 100644 --- a/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py +++ b/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py @@ -138,7 +138,10 @@ def _setup_tracing(configurations: Dict[str, ConfigurationValue]): if SAMPLING_TRACES_PER_SECOND_ARG in configurations: sampling_traces_per_second = configurations[SAMPLING_TRACES_PER_SECOND_ARG] tracer_provider = TracerProvider( - sampler=RateLimitedSampler(sampling_traces_per_second=cast(float, sampling_traces_per_second)), resource=resource + sampler=RateLimitedSampler( + sampling_traces_per_second=cast(float, sampling_traces_per_second) + ), + resource=resource ) else: sampling_ratio = configurations[SAMPLING_RATIO_ARG] diff --git a/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_utils/configurations.py b/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_utils/configurations.py index 840f80535311..ba85cd3c85b9 100644 --- a/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_utils/configurations.py +++ b/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_utils/configurations.py @@ -139,53 +139,66 @@ def _default_resource(configurations): def _default_sampling_ratio(configurations): - default = 1.0 + default_value = 1.0 + sampler_type = environ.get(OTEL_TRACES_SAMPLER) + sampler_arg = environ.get(OTEL_TRACES_SAMPLER_ARG) - if environ.get(OTEL_TRACES_SAMPLER_ARG) is not None: - try: - if float(environ[OTEL_TRACES_SAMPLER_ARG]) < 0: - _logger.error("Invalid value for OTEL_TRACES_SAMPLER_ARG. It should be a non-negative number.") - except ValueError: - pass - else: + # Early exit if no sampler argument is provided - always set default sampling_ratio + if sampler_arg is None: _logger.error("OTEL_TRACES_SAMPLER_ARG is not set.") + configurations[SAMPLING_RATIO_ARG] = default_value + return - # Check if rate-limited sampler is configured - if environ.get(OTEL_TRACES_SAMPLER) == RATE_LIMITED_SAMPLER: + # Handle rate-limited sampler + if sampler_type == RATE_LIMITED_SAMPLER: try: - default = float(environ[OTEL_TRACES_SAMPLER_ARG]) - _logger.info(f"Using rate limited sampler: {default} traces per second") + sampler_value = float(sampler_arg) + if sampler_value < 0: + _logger.error("Invalid value for OTEL_TRACES_SAMPLER_ARG. It should be a non-negative number.") + sampler_value = default_value + 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, + default_value, e, ) - configurations[SAMPLING_TRACES_PER_SECOND_ARG] = default - elif environ.get(OTEL_TRACES_SAMPLER) == FIXED_PERCENTAGE_SAMPLER: + configurations[SAMPLING_TRACES_PER_SECOND_ARG] = default_value + + # Handle fixed percentage sampler + elif sampler_type == FIXED_PERCENTAGE_SAMPLER: try: - default = float(environ[OTEL_TRACES_SAMPLER_ARG]) - _logger.info(f"Using sampling ratio: {default}") + sampler_value = float(sampler_arg) + if sampler_value < 0: + _logger.error("Invalid value for OTEL_TRACES_SAMPLER_ARG. It should be a non-negative number.") + 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, + default_value, e, ) - configurations[SAMPLING_RATIO_ARG] = default + configurations[SAMPLING_RATIO_ARG] = default_value + + # Handle all other cases (no sampler type specified or unsupported sampler type) else: - # Default behavior - always set sampling_ratio - configurations[SAMPLING_RATIO_ARG] = default - _logger.error( # pylint: disable=C - "Invalid argument for the sampler to be used for tracing. " - "Supported values are %s and %s. Defaulting to %s: %s", - RATE_LIMITED_SAMPLER, - FIXED_PERCENTAGE_SAMPLER, - OTEL_TRACES_SAMPLER, - OTEL_TRACES_SAMPLER_ARG, - ) + configurations[SAMPLING_RATIO_ARG] = default_value + 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 and %s. Defaulting to %s: %s", + RATE_LIMITED_SAMPLER, + FIXED_PERCENTAGE_SAMPLER, + OTEL_TRACES_SAMPLER, + OTEL_TRACES_SAMPLER_ARG, + ) def _default_instrumentation_options(configurations): otel_disabled_instrumentations = _get_otel_disabled_instrumentations() diff --git a/sdk/monitor/azure-monitor-opentelemetry/tests/test_configure.py b/sdk/monitor/azure-monitor-opentelemetry/tests/test_configure.py index d9283c909dc3..557d9bbf13ce 100644 --- a/sdk/monitor/azure-monitor-opentelemetry/tests/test_configure.py +++ b/sdk/monitor/azure-monitor-opentelemetry/tests/test_configure.py @@ -309,6 +309,66 @@ def test_setup_tracing( tp_init_mock.add_span_processor.assert_has_calls([call(custom_sp), call(bsp_init_mock)]) self.assertEqual(settings_mock.tracing_implementation, opentelemetry_span_mock) + @patch( + "azure.monitor.opentelemetry._configure.BatchSpanProcessor", + ) + @patch( + "azure.monitor.opentelemetry._configure.AzureMonitorTraceExporter", + ) + @patch( + "azure.monitor.opentelemetry._configure.set_tracer_provider", + ) + @patch( + "azure.monitor.opentelemetry._configure.TracerProvider", + autospec=True, + ) + @patch( + "azure.monitor.opentelemetry._configure.RateLimitedSampler", + ) + def test_setup_tracing_rate_limited_sampler( + self, + sampler_mock, + tp_mock, + set_tracer_provider_mock, + trace_exporter_mock, + bsp_mock, + ): + sampler_init_mock = Mock() + sampler_mock.return_value = sampler_init_mock + tp_init_mock = Mock() + tp_mock.return_value = tp_init_mock + trace_exp_init_mock = Mock() + trace_exporter_mock.return_value = trace_exp_init_mock + bsp_init_mock = Mock() + bsp_mock.return_value = bsp_init_mock + custom_sp = Mock() + + settings_mock = Mock() + opentelemetry_span_mock = Mock() + + configurations = { + "connection_string": "test_cs", + "instrumentation_options": {"azure_sdk": {"enabled": True}}, + "sampling_traces_per_second": 2.0, + "span_processors": [custom_sp], + "resource": TEST_RESOURCE, + } + with patch("azure.monitor.opentelemetry._configure._is_instrumentation_enabled") as instr_mock: + instr_mock.return_value = True + with patch.dict('sys.modules', { + 'azure.core.settings': Mock(settings=settings_mock), + 'azure.core.tracing.ext.opentelemetry_span': Mock(OpenTelemetrySpan=opentelemetry_span_mock) + }): + _setup_tracing(configurations) + sampler_mock.assert_called_once_with(sampling_traces_per_second=2.0) + tp_mock.assert_called_once_with(sampler=sampler_init_mock, resource=TEST_RESOURCE) + set_tracer_provider_mock.assert_called_once_with(tp_init_mock) + trace_exporter_mock.assert_called_once_with(**configurations) + bsp_mock.assert_called_once_with(trace_exp_init_mock) + self.assertEqual(tp_init_mock.add_span_processor.call_count, 2) + tp_init_mock.add_span_processor.assert_has_calls([call(custom_sp), call(bsp_init_mock)]) + self.assertEqual(settings_mock.tracing_implementation, opentelemetry_span_mock) + @patch("azure.monitor.opentelemetry._configure.getLogger") def test_setup_logging(self, get_logger_mock): From 482f45f4609b2a3586f62ed1c29c9fffcf6a8e6b Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Fri, 25 Jul 2025 16:15:43 -0700 Subject: [PATCH 4/5] Updated CHANGELOG with URL --- sdk/monitor/azure-monitor-opentelemetry/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/sdk/monitor/azure-monitor-opentelemetry/CHANGELOG.md b/sdk/monitor/azure-monitor-opentelemetry/CHANGELOG.md index 954257a20655..a9774dbb30c7 100644 --- a/sdk/monitor/azure-monitor-opentelemetry/CHANGELOG.md +++ b/sdk/monitor/azure-monitor-opentelemetry/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features Added - Added RateLimited Sampler Config changes + ([#41976](https://github.com/Azure/azure-sdk-for-python/pull/41976)) ### Breaking Changes From 34ebce932e7c4a8f890df5cc55cf6cdda686bbb9 Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Wed, 30 Jul 2025 09:15:35 -0700 Subject: [PATCH 5/5] Modified CHANGELOG --- sdk/monitor/azure-monitor-opentelemetry/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/monitor/azure-monitor-opentelemetry/CHANGELOG.md b/sdk/monitor/azure-monitor-opentelemetry/CHANGELOG.md index 9f963dc0310e..201c05f6f4ac 100644 --- a/sdk/monitor/azure-monitor-opentelemetry/CHANGELOG.md +++ b/sdk/monitor/azure-monitor-opentelemetry/CHANGELOG.md @@ -3,7 +3,7 @@ ## 1.6.14 (Unreleased) ### Features Added -- Added RateLimited Sampler Config changes +- Added configuration changes for RateLimited Sampler ([#41976](https://github.com/Azure/azure-sdk-for-python/pull/41976)) ### Breaking Changes