From df9272bf1fef7aca8c05513e2ce1bba901e0ae26 Mon Sep 17 00:00:00 2001 From: Yuan Qu Date: Mon, 29 Jun 2026 11:16:00 -0700 Subject: [PATCH 01/12] updated telemetry event dependency to opentelemetry LoggingHandler --- .../azuremonitor/_send_telemetry.py | 57 ++++++--- pyproject.toml | 4 +- tests/test_send_telemetry_appinsights.py | 114 +++++++++++------- 3 files changed, 116 insertions(+), 59 deletions(-) diff --git a/featuremanagement/azuremonitor/_send_telemetry.py b/featuremanagement/azuremonitor/_send_telemetry.py index 48c33d3..e2067c8 100644 --- a/featuremanagement/azuremonitor/_send_telemetry.py +++ b/featuremanagement/azuremonitor/_send_telemetry.py @@ -12,20 +12,38 @@ logger = logging.getLogger(__name__) +# Set up a separate logger for feature-evaluation telemetry events to avoid propagating to the root logger +_event_logger = logging.getLogger(__name__ + ".events") +_event_logger.propagate = False + try: - from azure.monitor.events.extension import track_event as azure_monitor_track_event # type: ignore - from opentelemetry.context.context import Context - from opentelemetry.sdk.trace import Span, SpanProcessor + from logging import INFO + from opentelemetry.instrumentation.logging.handler import LoggingHandler + from opentelemetry.sdk.trace import SpanProcessor - HAS_AZURE_MONITOR_EVENTS_EXTENSION = True + HAS_OPENTELEMETRY_LOGGING = True except ImportError: - HAS_AZURE_MONITOR_EVENTS_EXTENSION = False - logger.warning( - "azure-monitor-events-extension is not installed. Telemetry will not be sent to Application Insights." - ) + HAS_OPENTELEMETRY_LOGGING = False + LoggingHandler = object # type: ignore SpanProcessor = object # type: ignore - Span = object # type: ignore - Context = object # type: ignore + + +_events_logger_initialized = False + +def _initialize_event_logger() -> None: + global _events_logger_initialized + if _events_logger_initialized: + return + + if not HAS_OPENTELEMETRY_LOGGING: + logger.warning( + "OpenTelemetry logging handler is not installed. Telemetry will not be sent to Application Insights." + ) + return + + _event_logger.addHandler(LoggingHandler()) + _event_logger.setLevel(INFO) + _events_logger_initialized = True FEATURE_NAME = "FeatureName" ENABLED = "Enabled" @@ -51,15 +69,22 @@ def track_event(event_name: str, user: str, event_properties: Optional[Dict[str, :param str user: The user ID to associate with the event. :param dict[str, str] event_properties: A dictionary of named string properties. """ - if not HAS_AZURE_MONITOR_EVENTS_EXTENSION: + if not HAS_OPENTELEMETRY_LOGGING: return + _initialize_event_logger() + event_properties = event_properties or {} if user: event_properties[TARGETING_ID] = user - azure_monitor_track_event(event_name, event_properties) + # Azure Monitor exporter maps this attribute to customEvent telemetry name. + custom_event_attributes = { + **event_properties, + "microsoft.custom_event.name": event_name, + } + _event_logger.info(event_name, extra=custom_event_attributes) def publish_telemetry(evaluation_event: EvaluationEvent) -> None: @@ -68,7 +93,7 @@ def publish_telemetry(evaluation_event: EvaluationEvent) -> None: :param EvaluationEvent evaluation_event: The evaluation event to publish telemetry for. """ - if not HAS_AZURE_MONITOR_EVENTS_EXTENSION: + if not HAS_OPENTELEMETRY_LOGGING: return feature = evaluation_event.feature @@ -129,15 +154,15 @@ def __init__(self, **kwargs: Any) -> None: "targeting_context_accessor", None ) - def on_start(self, span: Span, parent_context: Optional[Context] = None) -> None: # pylint: disable=unused-argument + def on_start(self, span: Any, parent_context: Optional[Any] = None) -> None: # pylint: disable=unused-argument """ Attaches the targeting ID to the span and baggage when a new span is started. :param Span span: The span that was started. :param parent_context: The parent context of the span. """ - if not HAS_AZURE_MONITOR_EVENTS_EXTENSION: - logger.warning("Azure Monitor Events Extension is not installed.") + if not HAS_OPENTELEMETRY_LOGGING: + logger.warning("OpenTelemetry logging handler is not installed.") return if self._targeting_context_accessor and callable(self._targeting_context_accessor): if inspect.iscoroutinefunction(self._targeting_context_accessor): diff --git a/pyproject.toml b/pyproject.toml index da3aa6c..ee191ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,10 +58,9 @@ Homepage = "https://github.com/microsoft/FeatureManagement-Python" Issues = "https://github.com/microsoft/FeatureManagement-Python/issues" [project.optional-dependencies] -AzureMonitor = ["azure-monitor-events-extension<2.0.0"] +AzureMonitor = ["azure-monitor-opentelemetry < 2.0.0"] test = [ "azure-monitor-opentelemetry < 2.0.0", - "azure-monitor-events-extension < 2.0.0", ] dev = [ "pytest >= 7.0.0, < 10.0.0", @@ -76,4 +75,5 @@ dev = [ "myst_parser >= 2.0.0, < 6.0.0", "opentelemetry-api >= 1.20.0, < 2.0.0", "opentelemetry-sdk >= 1.20.0, < 2.0.0", + "opentelemetry-instrumentation-logging >= 0.40b0, < 1.0.0", ] diff --git a/tests/test_send_telemetry_appinsights.py b/tests/test_send_telemetry_appinsights.py index 4922070..7ed41c1 100644 --- a/tests/test_send_telemetry_appinsights.py +++ b/tests/test_send_telemetry_appinsights.py @@ -13,6 +13,10 @@ from featuremanagement.azuremonitor import TargetingSpanProcessor +def _event_properties(mock_track_event): + return mock_track_event.call_args.kwargs["event_properties"] + + @pytest.mark.usefixtures("caplog") class TestSendTelemetryAppinsights: @@ -40,23 +44,45 @@ def test_send_telemetry_appinsights(self): evaluation_event.variant = variant evaluation_event.reason = VariantAssignmentReason.DEFAULT_WHEN_DISABLED - with patch("featuremanagement.azuremonitor._send_telemetry.azure_monitor_track_event") as mock_track_event: + with patch("featuremanagement.azuremonitor._send_telemetry.track_event") as mock_track_event: # This is called like this so we can override the track_event function featuremanagement.azuremonitor._send_telemetry.publish_telemetry( # pylint: disable=protected-access evaluation_event ) mock_track_event.assert_called_once() assert mock_track_event.call_args[0][0] == "FeatureEvaluation" - assert mock_track_event.call_args[0][1]["FeatureName"] == "TestFeature" - assert mock_track_event.call_args[0][1]["Enabled"] == "True" - assert mock_track_event.call_args[0][1]["TargetingId"] == "test_user" - assert mock_track_event.call_args[0][1]["Variant"] == "TestVariant" - assert mock_track_event.call_args[0][1]["ETag"] == "cmwBRcIAq1jUyKL3Kj8bvf9jtxBrFg-R-ayExStMC90" + assert mock_track_event.call_args[0][1] == "test_user" + event_properties = _event_properties(mock_track_event) + assert event_properties["FeatureName"] == "TestFeature" + assert event_properties["Enabled"] == "True" + assert event_properties["Variant"] == "TestVariant" + assert event_properties["ETag"] == "cmwBRcIAq1jUyKL3Kj8bvf9jtxBrFg-R-ayExStMC90" assert ( - mock_track_event.call_args[0][1]["FeatureFlagReference"] + event_properties["FeatureFlagReference"] == "fake-store-uri/kv/.appconfig.featureflag/TestFeature" ) - assert mock_track_event.call_args[0][1]["FeatureFlagId"] == "fake-feature-flag-id" + assert event_properties["FeatureFlagId"] == "fake-feature-flag-id" + + def test_track_event_preserves_reserved_custom_event_name(self): + with patch("featuremanagement.azuremonitor._send_telemetry._initialize_event_logger"), patch( + "featuremanagement.azuremonitor._send_telemetry._event_logger.info" + ) as mock_event_logger_info, patch( + "featuremanagement.azuremonitor._send_telemetry.HAS_OPENTELEMETRY_LOGGING", True + ): + featuremanagement.azuremonitor._send_telemetry.track_event( + "FeatureEvaluation", + "test_user", + { + "microsoft.custom_event.name": "override_attempt", + "CustomProperty": "custom_value", + }, + ) + + mock_event_logger_info.assert_called_once() + assert mock_event_logger_info.call_args[0][0] == "FeatureEvaluation" + assert mock_event_logger_info.call_args.kwargs["extra"]["microsoft.custom_event.name"] == "FeatureEvaluation" + assert mock_event_logger_info.call_args.kwargs["extra"]["CustomProperty"] == "custom_value" + assert mock_event_logger_info.call_args.kwargs["extra"]["TargetingId"] == "test_user" def test_send_telemetry_appinsights_no_user(self): feature_flag = FeatureFlag.convert_from_json({"id": "TestFeature"}) @@ -67,18 +93,20 @@ def test_send_telemetry_appinsights_no_user(self): evaluation_event.variant = variant evaluation_event.reason = VariantAssignmentReason.DEFAULT_WHEN_DISABLED - with patch("featuremanagement.azuremonitor._send_telemetry.azure_monitor_track_event") as mock_track_event: + with patch("featuremanagement.azuremonitor._send_telemetry.track_event") as mock_track_event: # This is called like this so we can override the track_event function featuremanagement.azuremonitor._send_telemetry.publish_telemetry( # pylint: disable=protected-access evaluation_event ) mock_track_event.assert_called_once() assert mock_track_event.call_args[0][0] == "FeatureEvaluation" - assert mock_track_event.call_args[0][1]["FeatureName"] == "TestFeature" - assert mock_track_event.call_args[0][1]["Enabled"] == "False" - assert "TargetingId" not in mock_track_event.call_args[0][1] - assert mock_track_event.call_args[0][1]["Variant"] == "TestVariant" - assert mock_track_event.call_args[0][1]["VariantAssignmentReason"] == "DefaultWhenDisabled" + assert mock_track_event.call_args[0][1] == "" + event_properties = _event_properties(mock_track_event) + assert event_properties["FeatureName"] == "TestFeature" + assert event_properties["Enabled"] == "False" + assert "TargetingId" not in event_properties + assert event_properties["Variant"] == "TestVariant" + assert event_properties["VariantAssignmentReason"] == "DefaultWhenDisabled" def test_send_telemetry_appinsights_no_variant(self): feature_flag = FeatureFlag.convert_from_json({"id": "TestFeature"}) @@ -87,25 +115,26 @@ def test_send_telemetry_appinsights_no_variant(self): evaluation_event.enabled = True evaluation_event.user = "test_user" - with patch("featuremanagement.azuremonitor._send_telemetry.azure_monitor_track_event") as mock_track_event: + with patch("featuremanagement.azuremonitor._send_telemetry.track_event") as mock_track_event: # This is called like this so we can override the track_event function featuremanagement.azuremonitor._send_telemetry.publish_telemetry( # pylint: disable=protected-access evaluation_event ) mock_track_event.assert_called_once() assert mock_track_event.call_args[0][0] == "FeatureEvaluation" - assert mock_track_event.call_args[0][1]["FeatureName"] == "TestFeature" - assert mock_track_event.call_args[0][1]["Enabled"] == "True" - assert mock_track_event.call_args[0][1]["TargetingId"] == "test_user" - assert "Variant" not in mock_track_event.call_args[0][1] - assert "Reason" not in mock_track_event.call_args[0][1] + assert mock_track_event.call_args[0][1] == "test_user" + event_properties = _event_properties(mock_track_event) + assert event_properties["FeatureName"] == "TestFeature" + assert event_properties["Enabled"] == "True" + assert "Variant" not in event_properties + assert "Reason" not in event_properties def test_send_telemetry_appinsights_no_feature_flag(self): evaluation_event = EvaluationEvent(None) evaluation_event.enabled = True evaluation_event.user = "test_user" - with patch("featuremanagement.azuremonitor._send_telemetry.azure_monitor_track_event") as mock_track_event: + with patch("featuremanagement.azuremonitor._send_telemetry.track_event") as mock_track_event: # This is called like this so we can override the track_event function featuremanagement.azuremonitor._send_telemetry.publish_telemetry( # pylint: disable=protected-access evaluation_event @@ -130,18 +159,19 @@ def test_send_telemetry_appinsights_default_when_enabled(self): evaluation_event.variant = variant evaluation_event.reason = VariantAssignmentReason.DEFAULT_WHEN_ENABLED - with patch("featuremanagement.azuremonitor._send_telemetry.azure_monitor_track_event") as mock_track_event: + with patch("featuremanagement.azuremonitor._send_telemetry.track_event") as mock_track_event: # This is called like this so we can override the track_event function featuremanagement.azuremonitor._send_telemetry.publish_telemetry( # pylint: disable=protected-access evaluation_event ) mock_track_event.assert_called_once() assert mock_track_event.call_args[0][0] == "FeatureEvaluation" - assert mock_track_event.call_args[0][1]["FeatureName"] == "TestFeature" - assert mock_track_event.call_args[0][1]["Enabled"] == "True" - assert mock_track_event.call_args[0][1]["TargetingId"] == "test_user" - assert mock_track_event.call_args[0][1]["Variant"] == "big" - assert mock_track_event.call_args[0][1]["VariantAssignmentReason"] == "DefaultWhenEnabled" + assert mock_track_event.call_args[0][1] == "test_user" + event_properties = _event_properties(mock_track_event) + assert event_properties["FeatureName"] == "TestFeature" + assert event_properties["Enabled"] == "True" + assert event_properties["Variant"] == "big" + assert event_properties["VariantAssignmentReason"] == "DefaultWhenEnabled" def test_send_telemetry_appinsights_default_when_enabled_no_percentile(self): feature_flag = FeatureFlag.convert_from_json( @@ -160,18 +190,19 @@ def test_send_telemetry_appinsights_default_when_enabled_no_percentile(self): evaluation_event.variant = variant evaluation_event.reason = VariantAssignmentReason.DEFAULT_WHEN_ENABLED - with patch("featuremanagement.azuremonitor._send_telemetry.azure_monitor_track_event") as mock_track_event: + with patch("featuremanagement.azuremonitor._send_telemetry.track_event") as mock_track_event: # This is called like this so we can override the track_event function featuremanagement.azuremonitor._send_telemetry.publish_telemetry( # pylint: disable=protected-access evaluation_event ) mock_track_event.assert_called_once() assert mock_track_event.call_args[0][0] == "FeatureEvaluation" - assert mock_track_event.call_args[0][1]["FeatureName"] == "TestFeature" - assert mock_track_event.call_args[0][1]["Enabled"] == "True" - assert mock_track_event.call_args[0][1]["TargetingId"] == "test_user" - assert mock_track_event.call_args[0][1]["Variant"] == "big" - assert mock_track_event.call_args[0][1]["VariantAssignmentReason"] == "DefaultWhenEnabled" + assert mock_track_event.call_args[0][1] == "test_user" + event_properties = _event_properties(mock_track_event) + assert event_properties["FeatureName"] == "TestFeature" + assert event_properties["Enabled"] == "True" + assert event_properties["Variant"] == "big" + assert event_properties["VariantAssignmentReason"] == "DefaultWhenEnabled" def test_send_telemetry_appinsights_allocation(self): feature_flag = FeatureFlag.convert_from_json( @@ -190,20 +221,21 @@ def test_send_telemetry_appinsights_allocation(self): evaluation_event.variant = variant evaluation_event.reason = VariantAssignmentReason.PERCENTILE - with patch("featuremanagement.azuremonitor._send_telemetry.azure_monitor_track_event") as mock_track_event: + with patch("featuremanagement.azuremonitor._send_telemetry.track_event") as mock_track_event: # This is called like this so we can override the track_event function featuremanagement.azuremonitor._send_telemetry.publish_telemetry( # pylint: disable=protected-access evaluation_event ) mock_track_event.assert_called_once() assert mock_track_event.call_args[0][0] == "FeatureEvaluation" - assert mock_track_event.call_args[0][1]["FeatureName"] == "TestFeature" - assert mock_track_event.call_args[0][1]["Enabled"] == "True" - assert mock_track_event.call_args[0][1]["TargetingId"] == "test_user" - assert mock_track_event.call_args[0][1]["Variant"] == "big" - assert mock_track_event.call_args[0][1]["VariantAssignmentReason"] == "Percentile" - assert mock_track_event.call_args[0][1]["VariantAssignmentPercentage"] == "25" - assert "DefaultWhenEnabled" not in mock_track_event.call_args[0][1] + assert mock_track_event.call_args[0][1] == "test_user" + event_properties = _event_properties(mock_track_event) + assert event_properties["FeatureName"] == "TestFeature" + assert event_properties["Enabled"] == "True" + assert event_properties["Variant"] == "big" + assert event_properties["VariantAssignmentReason"] == "Percentile" + assert event_properties["VariantAssignmentPercentage"] == "25" + assert "DefaultWhenEnabled" not in event_properties def test_targeting_span_processor(self, caplog): processor = TargetingSpanProcessor() From ab2003e15b19162734d11f24182745df38a3e4bd Mon Sep 17 00:00:00 2001 From: Yuan Qu Date: Mon, 29 Jun 2026 15:24:20 -0700 Subject: [PATCH 02/12] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- featuremanagement/azuremonitor/_send_telemetry.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/featuremanagement/azuremonitor/_send_telemetry.py b/featuremanagement/azuremonitor/_send_telemetry.py index e2067c8..8dd2906 100644 --- a/featuremanagement/azuremonitor/_send_telemetry.py +++ b/featuremanagement/azuremonitor/_send_telemetry.py @@ -80,11 +80,16 @@ def track_event(event_name: str, user: str, event_properties: Optional[Dict[str, event_properties[TARGETING_ID] = user # Azure Monitor exporter maps this attribute to customEvent telemetry name. - custom_event_attributes = { - **event_properties, - "microsoft.custom_event.name": event_name, - } - _event_logger.info(event_name, extra=custom_event_attributes) + custom_event_attributes = {**event_properties, "microsoft.custom_event.name": event_name} + + # logging raises KeyError if an `extra` key overwrites a built-in LogRecord attribute (e.g. "name", "message"). + reserved = logging.makeLogRecord({}).__dict__ + safe_attributes: Dict[str, Optional[str]] = {} + for key, value in custom_event_attributes.items(): + safe_key = key if key not in reserved else f"telemetry.{key}" + safe_attributes[safe_key] = value + + _event_logger.info(event_name, extra=safe_attributes) def publish_telemetry(evaluation_event: EvaluationEvent) -> None: From 41962f89820eb8976fb978fae591dc62c6576959 Mon Sep 17 00:00:00 2001 From: Yuan Qu Date: Mon, 29 Jun 2026 15:49:37 -0700 Subject: [PATCH 03/12] ran validations and fixed formatting issues --- .../azuremonitor/_send_telemetry.py | 23 +++++++++++-------- pyproject.toml | 3 ++- samples/requirements.txt | 1 - tests/test_send_telemetry_appinsights.py | 17 +++++++------- 4 files changed, 24 insertions(+), 20 deletions(-) diff --git a/featuremanagement/azuremonitor/_send_telemetry.py b/featuremanagement/azuremonitor/_send_telemetry.py index e2067c8..90ebdbe 100644 --- a/featuremanagement/azuremonitor/_send_telemetry.py +++ b/featuremanagement/azuremonitor/_send_telemetry.py @@ -28,11 +28,10 @@ SpanProcessor = object # type: ignore -_events_logger_initialized = False +_EVENTS_LOGGER_INITIALIZED: list[bool] = [] def _initialize_event_logger() -> None: - global _events_logger_initialized - if _events_logger_initialized: + if _EVENTS_LOGGER_INITIALIZED: return if not HAS_OPENTELEMETRY_LOGGING: @@ -43,7 +42,8 @@ def _initialize_event_logger() -> None: _event_logger.addHandler(LoggingHandler()) _event_logger.setLevel(INFO) - _events_logger_initialized = True + _EVENTS_LOGGER_INITIALIZED.append(True) + FEATURE_NAME = "FeatureName" ENABLED = "Enabled" @@ -80,11 +80,16 @@ def track_event(event_name: str, user: str, event_properties: Optional[Dict[str, event_properties[TARGETING_ID] = user # Azure Monitor exporter maps this attribute to customEvent telemetry name. - custom_event_attributes = { - **event_properties, - "microsoft.custom_event.name": event_name, - } - _event_logger.info(event_name, extra=custom_event_attributes) + custom_event_attributes = {**event_properties, "microsoft.custom_event.name": event_name} + + # logging raises KeyError if an `extra` key overwrites a built-in LogRecord attribute (e.g. "name", "message"). + reserved = logging.makeLogRecord({}).__dict__ + safe_attributes: Dict[str, Optional[str]] = {} + for key, value in custom_event_attributes.items(): + safe_key = key if key not in reserved else f"telemetry.{key}" + safe_attributes[safe_key] = value + + _event_logger.info(event_name, extra=safe_attributes) def publish_telemetry(evaluation_event: EvaluationEvent) -> None: diff --git a/pyproject.toml b/pyproject.toml index ee191ba..9cd099f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,9 +58,10 @@ Homepage = "https://github.com/microsoft/FeatureManagement-Python" Issues = "https://github.com/microsoft/FeatureManagement-Python/issues" [project.optional-dependencies] -AzureMonitor = ["azure-monitor-opentelemetry < 2.0.0"] +AzureMonitor = ["azure-monitor-opentelemetry < 2.0.0", "opentelemetry-instrumentation-logging >= 0.40b0, < 1.0.0"] test = [ "azure-monitor-opentelemetry < 2.0.0", + "opentelemetry-instrumentation-logging >= 0.40b0, < 1.0.0", ] dev = [ "pytest >= 7.0.0, < 10.0.0", diff --git a/samples/requirements.txt b/samples/requirements.txt index 1c84dca..cba21c9 100644 --- a/samples/requirements.txt +++ b/samples/requirements.txt @@ -1,6 +1,5 @@ featuremanagement azure-appconfiguration-provider azure-monitor-opentelemetry -azure-monitor-events-extension quart azure-identity diff --git a/tests/test_send_telemetry_appinsights.py b/tests/test_send_telemetry_appinsights.py index 7ed41c1..ab2ae9f 100644 --- a/tests/test_send_telemetry_appinsights.py +++ b/tests/test_send_telemetry_appinsights.py @@ -57,17 +57,14 @@ def test_send_telemetry_appinsights(self): assert event_properties["Enabled"] == "True" assert event_properties["Variant"] == "TestVariant" assert event_properties["ETag"] == "cmwBRcIAq1jUyKL3Kj8bvf9jtxBrFg-R-ayExStMC90" - assert ( - event_properties["FeatureFlagReference"] - == "fake-store-uri/kv/.appconfig.featureflag/TestFeature" - ) + assert event_properties["FeatureFlagReference"] == "fake-store-uri/kv/.appconfig.featureflag/TestFeature" assert event_properties["FeatureFlagId"] == "fake-feature-flag-id" def test_track_event_preserves_reserved_custom_event_name(self): - with patch("featuremanagement.azuremonitor._send_telemetry._initialize_event_logger"), patch( - "featuremanagement.azuremonitor._send_telemetry._event_logger.info" - ) as mock_event_logger_info, patch( - "featuremanagement.azuremonitor._send_telemetry.HAS_OPENTELEMETRY_LOGGING", True + with ( + patch("featuremanagement.azuremonitor._send_telemetry._initialize_event_logger"), + patch("featuremanagement.azuremonitor._send_telemetry._event_logger.info") as mock_event_logger_info, + patch("featuremanagement.azuremonitor._send_telemetry.HAS_OPENTELEMETRY_LOGGING", True), ): featuremanagement.azuremonitor._send_telemetry.track_event( "FeatureEvaluation", @@ -80,7 +77,9 @@ def test_track_event_preserves_reserved_custom_event_name(self): mock_event_logger_info.assert_called_once() assert mock_event_logger_info.call_args[0][0] == "FeatureEvaluation" - assert mock_event_logger_info.call_args.kwargs["extra"]["microsoft.custom_event.name"] == "FeatureEvaluation" + assert ( + mock_event_logger_info.call_args.kwargs["extra"]["microsoft.custom_event.name"] == "FeatureEvaluation" + ) assert mock_event_logger_info.call_args.kwargs["extra"]["CustomProperty"] == "custom_value" assert mock_event_logger_info.call_args.kwargs["extra"]["TargetingId"] == "test_user" From cc2c47fb23e5c0cda46298449398ddca64e5ca19 Mon Sep 17 00:00:00 2001 From: Yuan Qu Date: Mon, 29 Jun 2026 15:54:12 -0700 Subject: [PATCH 04/12] reformatted _send_telemetry.py --- featuremanagement/azuremonitor/_send_telemetry.py | 1 + 1 file changed, 1 insertion(+) diff --git a/featuremanagement/azuremonitor/_send_telemetry.py b/featuremanagement/azuremonitor/_send_telemetry.py index 90ebdbe..1dc5a19 100644 --- a/featuremanagement/azuremonitor/_send_telemetry.py +++ b/featuremanagement/azuremonitor/_send_telemetry.py @@ -30,6 +30,7 @@ _EVENTS_LOGGER_INITIALIZED: list[bool] = [] + def _initialize_event_logger() -> None: if _EVENTS_LOGGER_INITIALIZED: return From 72e41a5987c4e0108bda6343b2614d98a4ff41b3 Mon Sep 17 00:00:00 2001 From: Yuan Qu Date: Mon, 29 Jun 2026 16:39:46 -0700 Subject: [PATCH 05/12] fixed validation warnings --- tests/test_send_telemetry_appinsights.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_send_telemetry_appinsights.py b/tests/test_send_telemetry_appinsights.py index ab2ae9f..5c926ae 100644 --- a/tests/test_send_telemetry_appinsights.py +++ b/tests/test_send_telemetry_appinsights.py @@ -66,7 +66,7 @@ def test_track_event_preserves_reserved_custom_event_name(self): patch("featuremanagement.azuremonitor._send_telemetry._event_logger.info") as mock_event_logger_info, patch("featuremanagement.azuremonitor._send_telemetry.HAS_OPENTELEMETRY_LOGGING", True), ): - featuremanagement.azuremonitor._send_telemetry.track_event( + featuremanagement.azuremonitor._send_telemetry.track_event( # pylint: disable=protected-access "FeatureEvaluation", "test_user", { From 2dbf5894f43fca9d4fa9e22eb730ac3a06f43bec Mon Sep 17 00:00:00 2001 From: Yuan Qu Date: Mon, 29 Jun 2026 23:12:14 -0700 Subject: [PATCH 06/12] took telemetry_sdk as dependency to send telemetry event directly --- featuremanagement/azuremonitor/_send_telemetry.py | 2 +- pyproject.toml | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/featuremanagement/azuremonitor/_send_telemetry.py b/featuremanagement/azuremonitor/_send_telemetry.py index 1dc5a19..373129c 100644 --- a/featuremanagement/azuremonitor/_send_telemetry.py +++ b/featuremanagement/azuremonitor/_send_telemetry.py @@ -18,7 +18,7 @@ try: from logging import INFO - from opentelemetry.instrumentation.logging.handler import LoggingHandler + from opentelemetry.sdk._logs import LoggingHandler from opentelemetry.sdk.trace import SpanProcessor HAS_OPENTELEMETRY_LOGGING = True diff --git a/pyproject.toml b/pyproject.toml index 9cd099f..32ae975 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,10 +58,9 @@ Homepage = "https://github.com/microsoft/FeatureManagement-Python" Issues = "https://github.com/microsoft/FeatureManagement-Python/issues" [project.optional-dependencies] -AzureMonitor = ["azure-monitor-opentelemetry < 2.0.0", "opentelemetry-instrumentation-logging >= 0.40b0, < 1.0.0"] +AzureMonitor = ["opentelemetry-sdk >= 1.20.0, < 2.0.0"] test = [ "azure-monitor-opentelemetry < 2.0.0", - "opentelemetry-instrumentation-logging >= 0.40b0, < 1.0.0", ] dev = [ "pytest >= 7.0.0, < 10.0.0", @@ -76,5 +75,4 @@ dev = [ "myst_parser >= 2.0.0, < 6.0.0", "opentelemetry-api >= 1.20.0, < 2.0.0", "opentelemetry-sdk >= 1.20.0, < 2.0.0", - "opentelemetry-instrumentation-logging >= 0.40b0, < 1.0.0", ] From 88b58ec877901ff99c053d6a1bfdac26f16c0207 Mon Sep 17 00:00:00 2001 From: Yuan Qu Date: Mon, 29 Jun 2026 23:21:34 -0700 Subject: [PATCH 07/12] updated warning message based on comments --- featuremanagement/azuremonitor/_send_telemetry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/featuremanagement/azuremonitor/_send_telemetry.py b/featuremanagement/azuremonitor/_send_telemetry.py index 373129c..3d1a245 100644 --- a/featuremanagement/azuremonitor/_send_telemetry.py +++ b/featuremanagement/azuremonitor/_send_telemetry.py @@ -37,7 +37,7 @@ def _initialize_event_logger() -> None: if not HAS_OPENTELEMETRY_LOGGING: logger.warning( - "OpenTelemetry logging handler is not installed. Telemetry will not be sent to Application Insights." + "OpenTelemetry logging handler is not installed. Telemetry will not be sent." ) return From 3ec77fc22fb5dd626e3a6c9fbf73c449e0344d16 Mon Sep 17 00:00:00 2001 From: Yuan Qu Date: Mon, 29 Jun 2026 23:31:53 -0700 Subject: [PATCH 08/12] reformatted _send_telemetry.py --- featuremanagement/azuremonitor/_send_telemetry.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/featuremanagement/azuremonitor/_send_telemetry.py b/featuremanagement/azuremonitor/_send_telemetry.py index 3d1a245..d5546b3 100644 --- a/featuremanagement/azuremonitor/_send_telemetry.py +++ b/featuremanagement/azuremonitor/_send_telemetry.py @@ -36,9 +36,7 @@ def _initialize_event_logger() -> None: return if not HAS_OPENTELEMETRY_LOGGING: - logger.warning( - "OpenTelemetry logging handler is not installed. Telemetry will not be sent." - ) + logger.warning("OpenTelemetry logging handler is not installed. Telemetry will not be sent.") return _event_logger.addHandler(LoggingHandler()) From d0d509b8b842269aafcac446fdcbe130eb5ec283 Mon Sep 17 00:00:00 2001 From: Yuan Qu Date: Tue, 30 Jun 2026 13:44:11 -0700 Subject: [PATCH 09/12] Addessed multiple minor comments --- .../azuremonitor/_send_telemetry.py | 29 +++++++------------ tests/test_send_telemetry_appinsights.py | 1 - 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/featuremanagement/azuremonitor/_send_telemetry.py b/featuremanagement/azuremonitor/_send_telemetry.py index d5546b3..b3a71f9 100644 --- a/featuremanagement/azuremonitor/_send_telemetry.py +++ b/featuremanagement/azuremonitor/_send_telemetry.py @@ -7,6 +7,7 @@ import logging import inspect +from logging import INFO from typing import Any, Callable, Dict, Optional from .._models import VariantAssignmentReason, EvaluationEvent, TargetingContext @@ -17,7 +18,6 @@ _event_logger.propagate = False try: - from logging import INFO from opentelemetry.sdk._logs import LoggingHandler from opentelemetry.sdk.trace import SpanProcessor @@ -28,20 +28,17 @@ SpanProcessor = object # type: ignore -_EVENTS_LOGGER_INITIALIZED: list[bool] = [] +_EVENTS_LOGGER_INITIALIZED: bool = False def _initialize_event_logger() -> None: + global _EVENTS_LOGGER_INITIALIZED # pylint: disable=global-statement if _EVENTS_LOGGER_INITIALIZED: return - if not HAS_OPENTELEMETRY_LOGGING: - logger.warning("OpenTelemetry logging handler is not installed. Telemetry will not be sent.") - return - _event_logger.addHandler(LoggingHandler()) _event_logger.setLevel(INFO) - _EVENTS_LOGGER_INITIALIZED.append(True) + _EVENTS_LOGGER_INITIALIZED = True FEATURE_NAME = "FeatureName" @@ -54,6 +51,7 @@ def _initialize_event_logger() -> None: VERSION = "Version" VARIANT_ASSIGNMENT_PERCENTAGE = "VariantAssignmentPercentage" MICROSOFT_TARGETING_ID = "Microsoft.TargetingId" +CUSTOM_EVENT_NAME = "microsoft.custom_event.name" EVENT_NAME = "FeatureEvaluation" @@ -79,16 +77,11 @@ def track_event(event_name: str, user: str, event_properties: Optional[Dict[str, event_properties[TARGETING_ID] = user # Azure Monitor exporter maps this attribute to customEvent telemetry name. - custom_event_attributes = {**event_properties, "microsoft.custom_event.name": event_name} - - # logging raises KeyError if an `extra` key overwrites a built-in LogRecord attribute (e.g. "name", "message"). - reserved = logging.makeLogRecord({}).__dict__ - safe_attributes: Dict[str, Optional[str]] = {} - for key, value in custom_event_attributes.items(): - safe_key = key if key not in reserved else f"telemetry.{key}" - safe_attributes[safe_key] = value - - _event_logger.info(event_name, extra=safe_attributes) + custom_event_attributes = { + **event_properties, + CUSTOM_EVENT_NAME: event_name, + } + _event_logger.info(event_name, extra=custom_event_attributes) def publish_telemetry(evaluation_event: EvaluationEvent) -> None: @@ -166,7 +159,7 @@ def on_start(self, span: Any, parent_context: Optional[Any] = None) -> None: # :param parent_context: The parent context of the span. """ if not HAS_OPENTELEMETRY_LOGGING: - logger.warning("OpenTelemetry logging handler is not installed.") + logger.info("OpenTelemetry logging handler is not installed.") return if self._targeting_context_accessor and callable(self._targeting_context_accessor): if inspect.iscoroutinefunction(self._targeting_context_accessor): diff --git a/tests/test_send_telemetry_appinsights.py b/tests/test_send_telemetry_appinsights.py index 5c926ae..13d1275 100644 --- a/tests/test_send_telemetry_appinsights.py +++ b/tests/test_send_telemetry_appinsights.py @@ -64,7 +64,6 @@ def test_track_event_preserves_reserved_custom_event_name(self): with ( patch("featuremanagement.azuremonitor._send_telemetry._initialize_event_logger"), patch("featuremanagement.azuremonitor._send_telemetry._event_logger.info") as mock_event_logger_info, - patch("featuremanagement.azuremonitor._send_telemetry.HAS_OPENTELEMETRY_LOGGING", True), ): featuremanagement.azuremonitor._send_telemetry.track_event( # pylint: disable=protected-access "FeatureEvaluation", From 527a4898f0d59cc56714d8297e33955abeaecd94 Mon Sep 17 00:00:00 2001 From: Yuan Qu Date: Tue, 30 Jun 2026 14:09:41 -0700 Subject: [PATCH 10/12] Preserve some old imports and formats --- featuremanagement/azuremonitor/_send_telemetry.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/featuremanagement/azuremonitor/_send_telemetry.py b/featuremanagement/azuremonitor/_send_telemetry.py index b3a71f9..2779e1f 100644 --- a/featuremanagement/azuremonitor/_send_telemetry.py +++ b/featuremanagement/azuremonitor/_send_telemetry.py @@ -19,13 +19,16 @@ try: from opentelemetry.sdk._logs import LoggingHandler - from opentelemetry.sdk.trace import SpanProcessor + from opentelemetry.context.context import Context + from opentelemetry.sdk.trace import Span, SpanProcessor HAS_OPENTELEMETRY_LOGGING = True except ImportError: HAS_OPENTELEMETRY_LOGGING = False LoggingHandler = object # type: ignore SpanProcessor = object # type: ignore + Span = object # type: ignore + Context = object # type: ignore _EVENTS_LOGGER_INITIALIZED: bool = False @@ -151,7 +154,7 @@ def __init__(self, **kwargs: Any) -> None: "targeting_context_accessor", None ) - def on_start(self, span: Any, parent_context: Optional[Any] = None) -> None: # pylint: disable=unused-argument + def on_start(self, span: Span, parent_context: Optional[Context] = None) -> None: # pylint: disable=unused-argument """ Attaches the targeting ID to the span and baggage when a new span is started. From 72c815d040ba71858eb87958b12c9a92df3605a7 Mon Sep 17 00:00:00 2001 From: Yuan Qu Date: Tue, 30 Jun 2026 14:17:51 -0700 Subject: [PATCH 11/12] update opentelemetry_sdk version to be more constrained --- pyproject.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 32ae975..f50d406 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,7 +58,7 @@ Homepage = "https://github.com/microsoft/FeatureManagement-Python" Issues = "https://github.com/microsoft/FeatureManagement-Python/issues" [project.optional-dependencies] -AzureMonitor = ["opentelemetry-sdk >= 1.20.0, < 2.0.0"] +AzureMonitor = ["opentelemetry-sdk~=1.20"] test = [ "azure-monitor-opentelemetry < 2.0.0", ] @@ -73,6 +73,6 @@ dev = [ "sphinx_rtd_theme >= 2.0.0, < 4.0.0", "sphinx-toolbox >= 3.0.0, < 5.0.0", "myst_parser >= 2.0.0, < 6.0.0", - "opentelemetry-api >= 1.20.0, < 2.0.0", - "opentelemetry-sdk >= 1.20.0, < 2.0.0", + "opentelemetry-api~=1.20", + "opentelemetry-sdk~=1.20", ] From 7d44c11ceb1adfb4e3dcbb17fbbc80f92436d366 Mon Sep 17 00:00:00 2001 From: Yuan Qu Date: Tue, 30 Jun 2026 15:18:02 -0700 Subject: [PATCH 12/12] modified unit tests to patch the correct logger --- .../azuremonitor/_send_telemetry.py | 30 +++--- tests/test_send_telemetry_appinsights.py | 98 +++++++++++-------- 2 files changed, 71 insertions(+), 57 deletions(-) diff --git a/featuremanagement/azuremonitor/_send_telemetry.py b/featuremanagement/azuremonitor/_send_telemetry.py index 2779e1f..f026214 100644 --- a/featuremanagement/azuremonitor/_send_telemetry.py +++ b/featuremanagement/azuremonitor/_send_telemetry.py @@ -30,20 +30,6 @@ Span = object # type: ignore Context = object # type: ignore - -_EVENTS_LOGGER_INITIALIZED: bool = False - - -def _initialize_event_logger() -> None: - global _EVENTS_LOGGER_INITIALIZED # pylint: disable=global-statement - if _EVENTS_LOGGER_INITIALIZED: - return - - _event_logger.addHandler(LoggingHandler()) - _event_logger.setLevel(INFO) - _EVENTS_LOGGER_INITIALIZED = True - - FEATURE_NAME = "FeatureName" ENABLED = "Enabled" TARGETING_ID = "TargetingId" @@ -54,12 +40,24 @@ def _initialize_event_logger() -> None: VERSION = "Version" VARIANT_ASSIGNMENT_PERCENTAGE = "VariantAssignmentPercentage" MICROSOFT_TARGETING_ID = "Microsoft.TargetingId" -CUSTOM_EVENT_NAME = "microsoft.custom_event.name" +AZURE_MONITOR_EVENT_NAME = "microsoft.custom_event.name" EVENT_NAME = "FeatureEvaluation" EVALUATION_EVENT_VERSION = "1.0.0" +_EVENTS_LOGGER_INITIALIZED: bool = False + + +def _initialize_event_logger() -> None: + global _EVENTS_LOGGER_INITIALIZED # pylint: disable=global-statement + if _EVENTS_LOGGER_INITIALIZED: + return + + _event_logger.addHandler(LoggingHandler()) + _event_logger.setLevel(INFO) + _EVENTS_LOGGER_INITIALIZED = True + def track_event(event_name: str, user: str, event_properties: Optional[Dict[str, Optional[str]]] = None) -> None: """ @@ -82,7 +80,7 @@ def track_event(event_name: str, user: str, event_properties: Optional[Dict[str, # Azure Monitor exporter maps this attribute to customEvent telemetry name. custom_event_attributes = { **event_properties, - CUSTOM_EVENT_NAME: event_name, + AZURE_MONITOR_EVENT_NAME: event_name, } _event_logger.info(event_name, extra=custom_event_attributes) diff --git a/tests/test_send_telemetry_appinsights.py b/tests/test_send_telemetry_appinsights.py index 13d1275..9350bbf 100644 --- a/tests/test_send_telemetry_appinsights.py +++ b/tests/test_send_telemetry_appinsights.py @@ -13,8 +13,8 @@ from featuremanagement.azuremonitor import TargetingSpanProcessor -def _event_properties(mock_track_event): - return mock_track_event.call_args.kwargs["event_properties"] +def _event_properties(mock_logger_info): + return mock_logger_info.call_args.kwargs["extra"] @pytest.mark.usefixtures("caplog") @@ -44,15 +44,18 @@ def test_send_telemetry_appinsights(self): evaluation_event.variant = variant evaluation_event.reason = VariantAssignmentReason.DEFAULT_WHEN_DISABLED - with patch("featuremanagement.azuremonitor._send_telemetry.track_event") as mock_track_event: - # This is called like this so we can override the track_event function + with ( + patch("featuremanagement.azuremonitor._send_telemetry._initialize_event_logger"), + patch("featuremanagement.azuremonitor._send_telemetry._event_logger.info") as mock_logger_info, + ): + # This is called like this so we can override the _event_logger.info function featuremanagement.azuremonitor._send_telemetry.publish_telemetry( # pylint: disable=protected-access evaluation_event ) - mock_track_event.assert_called_once() - assert mock_track_event.call_args[0][0] == "FeatureEvaluation" - assert mock_track_event.call_args[0][1] == "test_user" - event_properties = _event_properties(mock_track_event) + mock_logger_info.assert_called_once() + assert mock_logger_info.call_args[0][0] == "FeatureEvaluation" + event_properties = _event_properties(mock_logger_info) + assert event_properties["TargetingId"] == "test_user" assert event_properties["FeatureName"] == "TestFeature" assert event_properties["Enabled"] == "True" assert event_properties["Variant"] == "TestVariant" @@ -91,15 +94,17 @@ def test_send_telemetry_appinsights_no_user(self): evaluation_event.variant = variant evaluation_event.reason = VariantAssignmentReason.DEFAULT_WHEN_DISABLED - with patch("featuremanagement.azuremonitor._send_telemetry.track_event") as mock_track_event: - # This is called like this so we can override the track_event function + with ( + patch("featuremanagement.azuremonitor._send_telemetry._initialize_event_logger"), + patch("featuremanagement.azuremonitor._send_telemetry._event_logger.info") as mock_logger_info, + ): + # This is called like this so we can override the _event_logger.info function featuremanagement.azuremonitor._send_telemetry.publish_telemetry( # pylint: disable=protected-access evaluation_event ) - mock_track_event.assert_called_once() - assert mock_track_event.call_args[0][0] == "FeatureEvaluation" - assert mock_track_event.call_args[0][1] == "" - event_properties = _event_properties(mock_track_event) + mock_logger_info.assert_called_once() + assert mock_logger_info.call_args[0][0] == "FeatureEvaluation" + event_properties = _event_properties(mock_logger_info) assert event_properties["FeatureName"] == "TestFeature" assert event_properties["Enabled"] == "False" assert "TargetingId" not in event_properties @@ -113,15 +118,17 @@ def test_send_telemetry_appinsights_no_variant(self): evaluation_event.enabled = True evaluation_event.user = "test_user" - with patch("featuremanagement.azuremonitor._send_telemetry.track_event") as mock_track_event: - # This is called like this so we can override the track_event function + with ( + patch("featuremanagement.azuremonitor._send_telemetry._initialize_event_logger"), + patch("featuremanagement.azuremonitor._send_telemetry._event_logger.info") as mock_logger_info, + ): + # This is called like this so we can override the _event_logger.info function featuremanagement.azuremonitor._send_telemetry.publish_telemetry( # pylint: disable=protected-access evaluation_event ) - mock_track_event.assert_called_once() - assert mock_track_event.call_args[0][0] == "FeatureEvaluation" - assert mock_track_event.call_args[0][1] == "test_user" - event_properties = _event_properties(mock_track_event) + mock_logger_info.assert_called_once() + assert mock_logger_info.call_args[0][0] == "FeatureEvaluation" + event_properties = _event_properties(mock_logger_info) assert event_properties["FeatureName"] == "TestFeature" assert event_properties["Enabled"] == "True" assert "Variant" not in event_properties @@ -132,12 +139,15 @@ def test_send_telemetry_appinsights_no_feature_flag(self): evaluation_event.enabled = True evaluation_event.user = "test_user" - with patch("featuremanagement.azuremonitor._send_telemetry.track_event") as mock_track_event: - # This is called like this so we can override the track_event function + with ( + patch("featuremanagement.azuremonitor._send_telemetry._initialize_event_logger"), + patch("featuremanagement.azuremonitor._send_telemetry._event_logger.info") as mock_logger_info, + ): + # This is called like this so we can override the _event_logger.info function featuremanagement.azuremonitor._send_telemetry.publish_telemetry( # pylint: disable=protected-access evaluation_event ) - mock_track_event.assert_not_called() + mock_logger_info.assert_not_called() def test_send_telemetry_appinsights_default_when_enabled(self): feature_flag = FeatureFlag.convert_from_json( @@ -157,15 +167,17 @@ def test_send_telemetry_appinsights_default_when_enabled(self): evaluation_event.variant = variant evaluation_event.reason = VariantAssignmentReason.DEFAULT_WHEN_ENABLED - with patch("featuremanagement.azuremonitor._send_telemetry.track_event") as mock_track_event: - # This is called like this so we can override the track_event function + with ( + patch("featuremanagement.azuremonitor._send_telemetry._initialize_event_logger"), + patch("featuremanagement.azuremonitor._send_telemetry._event_logger.info") as mock_logger_info, + ): + # This is called like this so we can override the _event_logger.info function featuremanagement.azuremonitor._send_telemetry.publish_telemetry( # pylint: disable=protected-access evaluation_event ) - mock_track_event.assert_called_once() - assert mock_track_event.call_args[0][0] == "FeatureEvaluation" - assert mock_track_event.call_args[0][1] == "test_user" - event_properties = _event_properties(mock_track_event) + mock_logger_info.assert_called_once() + assert mock_logger_info.call_args[0][0] == "FeatureEvaluation" + event_properties = _event_properties(mock_logger_info) assert event_properties["FeatureName"] == "TestFeature" assert event_properties["Enabled"] == "True" assert event_properties["Variant"] == "big" @@ -188,15 +200,17 @@ def test_send_telemetry_appinsights_default_when_enabled_no_percentile(self): evaluation_event.variant = variant evaluation_event.reason = VariantAssignmentReason.DEFAULT_WHEN_ENABLED - with patch("featuremanagement.azuremonitor._send_telemetry.track_event") as mock_track_event: - # This is called like this so we can override the track_event function + with ( + patch("featuremanagement.azuremonitor._send_telemetry._initialize_event_logger"), + patch("featuremanagement.azuremonitor._send_telemetry._event_logger.info") as mock_logger_info, + ): + # This is called like this so we can override the _event_logger.info function featuremanagement.azuremonitor._send_telemetry.publish_telemetry( # pylint: disable=protected-access evaluation_event ) - mock_track_event.assert_called_once() - assert mock_track_event.call_args[0][0] == "FeatureEvaluation" - assert mock_track_event.call_args[0][1] == "test_user" - event_properties = _event_properties(mock_track_event) + mock_logger_info.assert_called_once() + assert mock_logger_info.call_args[0][0] == "FeatureEvaluation" + event_properties = _event_properties(mock_logger_info) assert event_properties["FeatureName"] == "TestFeature" assert event_properties["Enabled"] == "True" assert event_properties["Variant"] == "big" @@ -219,15 +233,17 @@ def test_send_telemetry_appinsights_allocation(self): evaluation_event.variant = variant evaluation_event.reason = VariantAssignmentReason.PERCENTILE - with patch("featuremanagement.azuremonitor._send_telemetry.track_event") as mock_track_event: - # This is called like this so we can override the track_event function + with ( + patch("featuremanagement.azuremonitor._send_telemetry._initialize_event_logger"), + patch("featuremanagement.azuremonitor._send_telemetry._event_logger.info") as mock_logger_info, + ): + # This is called like this so we can override the _event_logger.info function featuremanagement.azuremonitor._send_telemetry.publish_telemetry( # pylint: disable=protected-access evaluation_event ) - mock_track_event.assert_called_once() - assert mock_track_event.call_args[0][0] == "FeatureEvaluation" - assert mock_track_event.call_args[0][1] == "test_user" - event_properties = _event_properties(mock_track_event) + mock_logger_info.assert_called_once() + assert mock_logger_info.call_args[0][0] == "FeatureEvaluation" + event_properties = _event_properties(mock_logger_info) assert event_properties["FeatureName"] == "TestFeature" assert event_properties["Enabled"] == "True" assert event_properties["Variant"] == "big"