diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/CHANGELOG.md b/sdk/monitor/azure-monitor-opentelemetry-exporter/CHANGELOG.md index 58c317765680..85dc783556a5 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/CHANGELOG.md +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/CHANGELOG.md @@ -8,6 +8,9 @@ ### Bugs Fixed +- Detect live metrics usage during runtime in addition to on startup + ([#37694](https://github.com/Azure/azure-sdk-for-python/pull/37694)) + ### Other Changes ## 1.0.0b32 (2024-11-04) diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_quickpulse/_live_metrics.py b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_quickpulse/_live_metrics.py index 5f5c056fe975..971e474ed2ee 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_quickpulse/_live_metrics.py +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_quickpulse/_live_metrics.py @@ -82,6 +82,9 @@ def enable_live_metrics(**kwargs: Any) -> None: # pylint: disable=C4758 :rtype: None """ _QuickpulseManager(**kwargs) + # We can detect feature usage for statsbeat since we are in an opt-in model currently + # Once we move to live metrics on-by-default, we will have to check for both explicit usage + # and whether or not user is actually using live metrics (being on live metrics blade in UX) set_statsbeat_live_metrics_feature_set() diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/statsbeat/_statsbeat_metrics.py b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/statsbeat/_statsbeat_metrics.py index 0eabae80c359..0d407161d0b9 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/statsbeat/_statsbeat_metrics.py +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/statsbeat/_statsbeat_metrics.py @@ -241,6 +241,9 @@ def _get_feature_metric(self, options: CallbackOptions) -> Iterable[Observation] if get_statsbeat_custom_events_feature_set(): self._feature |= _StatsbeatFeature.CUSTOM_EVENTS_EXTENSION _StatsbeatMetrics._FEATURE_ATTRIBUTES["feature"] = self._feature + if get_statsbeat_live_metrics_feature_set(): + self._feature |= _StatsbeatFeature.LIVE_METRICS + _StatsbeatMetrics._FEATURE_ATTRIBUTES["feature"] = self._feature # Don't send observation if no features enabled if self._feature is not _StatsbeatFeature.NONE: diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/tests/statsbeat/test_statsbeat.py b/sdk/monitor/azure-monitor-opentelemetry-exporter/tests/statsbeat/test_statsbeat.py index ef622bcd39e4..3c472c554d76 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/tests/statsbeat/test_statsbeat.py +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/tests/statsbeat/test_statsbeat.py @@ -733,6 +733,34 @@ def test_get_feature_metric_custom_events(self, feature_mock): self.assertEqual(obs.value, 1) self.assertEqual(obs.attributes, attributes) + # pylint: disable=protected-access + def test_get_feature_metric_custom_events_runtime(self): + mp = MeterProvider() + ikey = "1aa11111-bbbb-1ccc-8ddd-eeeeffff3334" + endpoint = "https://westus-1.in.applicationinsights.azure.com/" + _STATSBEAT_STATE["CUSTOM_EVENTS_FEATURE_SET"] = False + metric = _StatsbeatMetrics( + mp, + ikey, + endpoint, + True, + 0, + False, + ) + self.assertTrue((metric._feature >> 2) & 1 == 0) + attributes = dict(_StatsbeatMetrics._COMMON_ATTRIBUTES) + attributes.update(_StatsbeatMetrics._FEATURE_ATTRIBUTES) + self.assertEqual(attributes["feature"], 0) + self.assertEqual(attributes["type"], _FEATURE_TYPES.FEATURE) + _STATSBEAT_STATE["CUSTOM_EVENTS_FEATURE_SET"] = True + observations = metric._get_feature_metric(options=None) + attributes["feature"] = _StatsbeatMetrics._FEATURE_ATTRIBUTES["feature"] + for obs in observations: + self.assertEqual(obs.value, 1) + self.assertEqual(obs.attributes, attributes) + self.assertTrue((_StatsbeatMetrics._FEATURE_ATTRIBUTES["feature"] >> 2) & 1 == 1) + _STATSBEAT_STATE["CUSTOM_EVENTS_FEATURE_SET"] = False + # pylint: disable=protected-access @mock.patch( "azure.monitor.opentelemetry.exporter.statsbeat._statsbeat_metrics.get_statsbeat_live_metrics_feature_set" @@ -742,7 +770,6 @@ def test_get_feature_metric_live_metrics(self, feature_mock): mp = MeterProvider() ikey = "1aa11111-bbbb-1ccc-8ddd-eeeeffff3334" endpoint = "https://westus-1.in.applicationinsights.azure.com/" - _STATSBEAT_STATE["LIVE_METRICS_FEATURE_SET"] = True metric = _StatsbeatMetrics( mp, ikey, @@ -759,6 +786,33 @@ def test_get_feature_metric_live_metrics(self, feature_mock): for obs in observations: self.assertEqual(obs.value, 1) self.assertEqual(obs.attributes, attributes) + + # pylint: disable=protected-access + def test_get_feature_metric_live_metrics_runtime(self): + mp = MeterProvider() + ikey = "1aa11111-bbbb-1ccc-8ddd-eeeeffff3334" + endpoint = "https://westus-1.in.applicationinsights.azure.com/" + _STATSBEAT_STATE["LIVE_METRICS_FEATURE_SET"] = False + metric = _StatsbeatMetrics( + mp, + ikey, + endpoint, + True, + 0, + False, + ) + self.assertTrue((metric._feature >> 4) & 1 == 0) + attributes = dict(_StatsbeatMetrics._COMMON_ATTRIBUTES) + attributes.update(_StatsbeatMetrics._FEATURE_ATTRIBUTES) + self.assertEqual(attributes["feature"], 0) + self.assertEqual(attributes["type"], _FEATURE_TYPES.FEATURE) + _STATSBEAT_STATE["LIVE_METRICS_FEATURE_SET"] = True + observations = metric._get_feature_metric(options=None) + attributes["feature"] = _StatsbeatMetrics._FEATURE_ATTRIBUTES["feature"] + for obs in observations: + self.assertEqual(obs.value, 1) + self.assertEqual(obs.attributes, attributes) + self.assertTrue((_StatsbeatMetrics._FEATURE_ATTRIBUTES["feature"] >> 4) & 1 == 1) _STATSBEAT_STATE["LIVE_METRICS_FEATURE_SET"] = False # pylint: disable=protected-access diff --git a/sdk/monitor/azure-monitor-opentelemetry/CHANGELOG.md b/sdk/monitor/azure-monitor-opentelemetry/CHANGELOG.md index 9fed57f2899d..731746e7ea6d 100644 --- a/sdk/monitor/azure-monitor-opentelemetry/CHANGELOG.md +++ b/sdk/monitor/azure-monitor-opentelemetry/CHANGELOG.md @@ -8,6 +8,8 @@ ([#38549](https://github.com/Azure/azure-sdk-for-python/pull/38549)) - Distro to automatically configure event logger provider ([#38543](https://github.com/Azure/azure-sdk-for-python/pull/38543)) +- Configure live metrics first in pipeline to detect statsbeat usage + ([#37694](https://github.com/Azure/azure-sdk-for-python/pull/37694)) ### 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 bc2a90267bc8..76dfba3f3c0f 100644 --- a/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py +++ b/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py @@ -7,7 +7,7 @@ from logging import getLogger from typing import Dict, List, cast -from opentelemetry._events import set_event_logger_provider +from opentelemetry._events import _set_event_logger_provider from opentelemetry._logs import set_logger_provider from opentelemetry.instrumentation.dependencies import ( get_dist_dependency_conflicts, @@ -115,6 +115,10 @@ def configure_azure_monitor(**kwargs) -> None: # pylint: disable=C4758 disable_metrics = configurations[DISABLE_METRICS_ARG] enable_live_metrics_config = configurations[ENABLE_LIVE_METRICS_ARG] + # Setup live metrics + if enable_live_metrics_config: + _setup_live_metrics(configurations) + # Setup tracing pipeline if not disable_tracing: _setup_tracing(configurations) @@ -127,10 +131,6 @@ def configure_azure_monitor(**kwargs) -> None: # pylint: disable=C4758 if not disable_metrics: _setup_metrics(configurations) - # Setup live metrics - if enable_live_metrics_config: - _setup_live_metrics(configurations) - # Setup instrumentations # Instrumentations need to be setup last so to use the global providers # instanstiated in the other setup steps @@ -180,7 +180,7 @@ def _setup_logging(configurations: Dict[str, ConfigurationValue]): # Setup EventLoggerProvider event_provider = EventLoggerProvider(logger_provider) - set_event_logger_provider(event_provider) + _set_event_logger_provider(event_provider, False) def _setup_metrics(configurations: Dict[str, ConfigurationValue]): diff --git a/sdk/monitor/azure-monitor-opentelemetry/tests/test_configure.py b/sdk/monitor/azure-monitor-opentelemetry/tests/test_configure.py index 04d66ec6a8e5..7b0a29e6b4bb 100644 --- a/sdk/monitor/azure-monitor-opentelemetry/tests/test_configure.py +++ b/sdk/monitor/azure-monitor-opentelemetry/tests/test_configure.py @@ -305,7 +305,7 @@ def test_setup_tracing( self.assertEqual(azure_core_mock.tracing_implementation, OpenTelemetrySpan) @patch( - "azure.monitor.opentelemetry._configure.set_event_logger_provider", + "azure.monitor.opentelemetry._configure._set_event_logger_provider", ) @patch( "azure.monitor.opentelemetry._configure.EventLoggerProvider", @@ -373,7 +373,7 @@ def test_setup_logging( get_logger_mock.assert_called_once_with("test") logger_mock.addHandler.assert_called_once_with(logging_handler_init_mock) elp_mock.assert_called_once_with(lp_init_mock) - set_elp_mock.assert_called_once_with(elp_init_mock) + set_elp_mock.assert_called_once_with(elp_init_mock, False) @patch( "azure.monitor.opentelemetry._configure.getLogger",