Skip to content

Commit f6d99be

Browse files
mrm9084rossgrambo
andauthored
App Configuration Provider - Request tracing update (#43589)
* moving request tracing to it's own file * clearing up + tests * uses aap * removed unused feature * Update _azureappconfigurationproviderbase.py * Updating Constants * Updating imports and constants * Update _request_tracing_context.py * review comments * consistant name * Update _azureappconfigurationproviderbase.py * Update test_azureappconfigurationproviderbase.py * code review comment * Update test_azureappconfigurationproviderbase.py * Update sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationproviderbase.py Co-authored-by: Ross Grambo <rossgrambo@microsoft.com> * Update _azureappconfigurationproviderbase.py --------- Co-authored-by: Ross Grambo <rossgrambo@microsoft.com>
1 parent ed456b2 commit f6d99be

File tree

11 files changed

+893
-340
lines changed

11 files changed

+893
-340
lines changed

sdk/appconfiguration/azure-appconfiguration-provider/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
### Other Changes
1717

18+
* Updated Request Tracing
19+
1820
## 2.2.0 (2025-08-08)
1921

2022
### Features Added

sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ def _attempt_refresh(self, client: ConfigurationClient, replica_count: int, is_f
267267
configuration_refresh_attempted = False
268268
feature_flag_refresh_attempted = False
269269
updated_watched_settings: Mapping[Tuple[str, str], Optional[str]] = {}
270-
existing_feature_flag_usage = self._feature_filter_usage.copy()
270+
existing_feature_flag_usage = self._tracing_context.feature_filter_usage.copy()
271271
try:
272272
if self._watched_settings and self._refresh_timer.needs_refresh():
273273
configuration_refresh_attempted = True
@@ -317,7 +317,7 @@ def _attempt_refresh(self, client: ConfigurationClient, replica_count: int, is_f
317317
logger.warning("Failed to refresh configurations from endpoint %s", client.endpoint)
318318
self._replica_client_manager.backoff(client)
319319
# Restore feature flag usage on failure
320-
self._feature_filter_usage = existing_feature_flag_usage
320+
self._tracing_context.feature_filter_usage = existing_feature_flag_usage
321321
raise e
322322

323323
def refresh(self, **kwargs) -> None:

sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationproviderbase.py

Lines changed: 37 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,8 @@
66
import base64
77
import hashlib
88
import json
9-
import os
109
import time
1110
import datetime
12-
from importlib.metadata import version, PackageNotFoundError
1311
from threading import Lock
1412
import logging
1513
from typing import (
@@ -33,20 +31,7 @@
3331
)
3432
from ._models import SettingSelector
3533
from ._constants import (
36-
REQUEST_TRACING_DISABLED_ENVIRONMENT_VARIABLE,
37-
ServiceFabricEnvironmentVariable,
38-
AzureFunctionEnvironmentVariable,
39-
AzureWebAppEnvironmentVariable,
40-
ContainerAppEnvironmentVariable,
41-
KubernetesEnvironmentVariable,
4234
NULL_CHAR,
43-
CUSTOM_FILTER_KEY,
44-
PERCENTAGE_FILTER_KEY,
45-
TIME_WINDOW_FILTER_KEY,
46-
TARGETING_FILTER_KEY,
47-
PERCENTAGE_FILTER_NAMES,
48-
TIME_WINDOW_FILTER_NAMES,
49-
TARGETING_FILTER_NAMES,
5035
TELEMETRY_KEY,
5136
METADATA_KEY,
5237
ETAG_KEY,
@@ -58,6 +43,8 @@
5843
FEATURE_FLAG_KEY,
5944
)
6045
from ._refresh_timer import _RefreshTimer
46+
from ._request_tracing_context import _RequestTracingContext
47+
6148

6249
JSON = Mapping[str, Any]
6350
_T = TypeVar("_T")
@@ -247,10 +234,7 @@ def __init__(self, **kwargs: Any) -> None:
247234
self._watched_feature_flags: Dict[Tuple[str, str], Optional[str]] = {}
248235
self._feature_flag_refresh_timer: _RefreshTimer = _RefreshTimer(**kwargs)
249236
self._feature_flag_refresh_enabled = kwargs.pop("feature_flag_refresh_enabled", False)
250-
self._feature_filter_usage: Dict[str, bool] = {}
251-
self._uses_load_balancing = kwargs.pop("load_balancing_enabled", False)
252-
self._uses_ai_configuration = False
253-
self._uses_aicc_configuration = False # AI Chat Completion
237+
self._tracing_context = _RequestTracingContext(kwargs.pop("load_balancing_enabled", False))
254238
self._update_lock = Lock()
255239
self._refresh_lock = Lock()
256240

@@ -276,39 +260,36 @@ def _update_ff_telemetry_metadata(
276260
:param feature_flag_value: The feature flag value dictionary to update.
277261
:type feature_flag_value: Dict[str, Any]
278262
"""
279-
if TELEMETRY_KEY in feature_flag_value:
280-
if METADATA_KEY not in feature_flag_value[TELEMETRY_KEY]:
281-
feature_flag_value[TELEMETRY_KEY][METADATA_KEY] = {}
282-
feature_flag_value[TELEMETRY_KEY][METADATA_KEY][ETAG_KEY] = feature_flag.etag
263+
if TELEMETRY_KEY not in feature_flag_value:
264+
# Initialize telemetry dictionary if not present
265+
feature_flag_value[TELEMETRY_KEY] = {}
266+
267+
# Update telemetry metadata for application insights/logging in feature management
268+
if METADATA_KEY not in feature_flag_value[TELEMETRY_KEY]:
269+
feature_flag_value[TELEMETRY_KEY][METADATA_KEY] = {}
270+
feature_flag_value[TELEMETRY_KEY][METADATA_KEY][ETAG_KEY] = feature_flag.etag
283271

272+
if feature_flag_value[TELEMETRY_KEY].get("enabled"):
273+
self._tracing_context.uses_telemetry = True
284274
if not endpoint.endswith("/"):
285275
endpoint += "/"
286276
feature_flag_reference = f"{endpoint}kv/{feature_flag.key}"
287277
if feature_flag.label and not feature_flag.label.isspace():
288278
feature_flag_reference += f"?label={feature_flag.label}"
289-
if feature_flag_value[TELEMETRY_KEY].get("enabled"):
290-
feature_flag_value[TELEMETRY_KEY][METADATA_KEY][FEATURE_FLAG_REFERENCE_KEY] = feature_flag_reference
291-
allocation_id = self._generate_allocation_id(feature_flag_value)
292-
if allocation_id:
293-
feature_flag_value[TELEMETRY_KEY][METADATA_KEY][ALLOCATION_ID_KEY] = allocation_id
294279

295-
def _update_feature_filter_telemetry(self, feature_flag: FeatureFlagConfigurationSetting):
296-
"""
297-
Track feature filter usage for App Configuration telemetry.
280+
feature_flag_value[TELEMETRY_KEY][METADATA_KEY][FEATURE_FLAG_REFERENCE_KEY] = feature_flag_reference
281+
allocation_id = self._generate_allocation_id(feature_flag_value)
282+
if allocation_id:
283+
feature_flag_value[TELEMETRY_KEY][METADATA_KEY][ALLOCATION_ID_KEY] = allocation_id
298284

299-
:param feature_flag: The feature flag to analyze for filter usage.
300-
:type feature_flag: FeatureFlagConfigurationSetting
301-
"""
302-
if feature_flag.filters:
303-
for filter in feature_flag.filters:
304-
if filter.get("name") in PERCENTAGE_FILTER_NAMES:
305-
self._feature_filter_usage[PERCENTAGE_FILTER_KEY] = True
306-
elif filter.get("name") in TIME_WINDOW_FILTER_NAMES:
307-
self._feature_filter_usage[TIME_WINDOW_FILTER_KEY] = True
308-
elif filter.get("name") in TARGETING_FILTER_NAMES:
309-
self._feature_filter_usage[TARGETING_FILTER_KEY] = True
310-
else:
311-
self._feature_filter_usage[CUSTOM_FILTER_KEY] = True
285+
allocation = feature_flag_value.get("allocation")
286+
if allocation and allocation.get("seed"):
287+
self._tracing_context.uses_seed = True
288+
289+
variants = feature_flag_value.get("variants")
290+
if variants:
291+
# Update Usage Data for Telemetry
292+
self._tracing_context.update_max_variants(len(variants))
312293

313294
@staticmethod
314295
def _generate_allocation_id(feature_flag_value: Dict[str, JSON]) -> Optional[str]:
@@ -487,9 +468,9 @@ def _process_key_value_base(self, config: ConfigurationSetting) -> Union[str, Di
487468
# Feature flags are of type json, but don't treat them as such
488469
try:
489470
if APP_CONFIG_AI_MIME_PROFILE in config.content_type:
490-
self._uses_ai_configuration = True
471+
self._tracing_context.uses_ai_configuration = True
491472
if APP_CONFIG_AICC_MIME_PROFILE in config.content_type:
492-
self._uses_aicc_configuration = True
473+
self._tracing_context.uses_aicc_configuration = True
493474
return json.loads(config.value)
494475
except json.JSONDecodeError:
495476
try:
@@ -510,7 +491,7 @@ def _process_feature_flags(
510491
) -> Dict[str, Any]:
511492
if feature_flags:
512493
# Reset feature flag usage
513-
self._feature_filter_usage = {}
494+
self._tracing_context.reset_feature_filter_usage()
514495
processed_feature_flags = [self._process_feature_flag(ff) for ff in feature_flags]
515496
self._watched_feature_flags = self._update_watched_feature_flags(feature_flags)
516497

@@ -522,7 +503,7 @@ def _process_feature_flags(
522503
def _process_feature_flag(self, feature_flag: FeatureFlagConfigurationSetting) -> Dict[str, Any]:
523504
feature_flag_value = json.loads(feature_flag.value)
524505
self._update_ff_telemetry_metadata(self._origin_endpoint, feature_flag, feature_flag_value)
525-
self._update_feature_filter_telemetry(feature_flag)
506+
self._tracing_context.update_feature_filter_telemetry(feature_flag)
526507
return feature_flag_value
527508

528509
def _update_watched_settings(
@@ -578,73 +559,14 @@ def _update_correlation_context_header(
578559
:return: The updated headers dictionary.
579560
:rtype: Dict[str, str]
580561
"""
581-
if os.environ.get(REQUEST_TRACING_DISABLED_ENVIRONMENT_VARIABLE, default="").lower() == "true":
582-
return headers
583-
correlation_context = f"RequestType={request_type}"
584-
585-
if len(self._feature_filter_usage) > 0:
586-
filters_used = ""
587-
if CUSTOM_FILTER_KEY in self._feature_filter_usage:
588-
filters_used = CUSTOM_FILTER_KEY
589-
if PERCENTAGE_FILTER_KEY in self._feature_filter_usage:
590-
filters_used += ("+" if filters_used else "") + PERCENTAGE_FILTER_KEY
591-
if TIME_WINDOW_FILTER_KEY in self._feature_filter_usage:
592-
filters_used += ("+" if filters_used else "") + TIME_WINDOW_FILTER_KEY
593-
if TARGETING_FILTER_KEY in self._feature_filter_usage:
594-
filters_used += ("+" if filters_used else "") + TARGETING_FILTER_KEY
595-
correlation_context += f",Filters={filters_used}"
596-
597-
correlation_context += self._uses_feature_flags()
598-
599-
if uses_key_vault:
600-
correlation_context += ",UsesKeyVault"
601-
host_type = ""
602-
if AzureFunctionEnvironmentVariable in os.environ:
603-
host_type = "AzureFunction"
604-
elif AzureWebAppEnvironmentVariable in os.environ:
605-
host_type = "AzureWebApp"
606-
elif ContainerAppEnvironmentVariable in os.environ:
607-
host_type = "ContainerApp"
608-
elif KubernetesEnvironmentVariable in os.environ:
609-
host_type = "Kubernetes"
610-
elif ServiceFabricEnvironmentVariable in os.environ:
611-
host_type = "ServiceFabric"
612-
if host_type:
613-
correlation_context += f",Host={host_type}"
614-
615-
if replica_count > 0:
616-
correlation_context += f",ReplicaCount={replica_count}"
617-
618-
if is_failover_request:
619-
correlation_context += ",Failover"
620-
621-
features = ""
622-
623-
if self._uses_load_balancing:
624-
features += "LB+"
625-
626-
if self._uses_ai_configuration:
627-
features += "AI+"
628-
629-
if self._uses_aicc_configuration:
630-
features += "AICC+"
631-
632-
if features:
633-
correlation_context += f",Features={features[:-1]}"
634-
635-
headers["Correlation-Context"] = correlation_context
636-
return headers
637-
638-
def _uses_feature_flags(self) -> str:
639-
if not self._feature_flag_enabled:
640-
return ""
641-
package_name = "featuremanagement"
642-
try:
643-
feature_management_version = version(package_name)
644-
return f",FMPyVer={feature_management_version}"
645-
except PackageNotFoundError:
646-
pass
647-
return ""
562+
return self._tracing_context.update_correlation_context_header(
563+
headers=headers,
564+
request_type=request_type,
565+
replica_count=replica_count,
566+
uses_key_vault=uses_key_vault,
567+
feature_flag_enabled=self._feature_flag_enabled,
568+
is_failover_request=is_failover_request,
569+
)
648570

649571
def _deduplicate_settings(
650572
self, configuration_settings: List[ConfigurationSetting]

sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_constants.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,6 @@
2424
ETAG_KEY = "ETag"
2525
FEATURE_FLAG_REFERENCE_KEY = "FeatureFlagReference"
2626

27-
PERCENTAGE_FILTER_NAMES = ["Percentage", "PercentageFilter", "Microsoft.Percentage", "Microsoft.PercentageFilter"]
28-
TIME_WINDOW_FILTER_NAMES = ["TimeWindow", "TimeWindowFilter", "Microsoft.TimeWindow", "Microsoft.TimeWindowFilter"]
29-
TARGETING_FILTER_NAMES = ["Targeting", "TargetingFilter", "Microsoft.Targeting", "Microsoft.TargetingFilter"]
30-
31-
CUSTOM_FILTER_KEY = "CSTM" # cspell:disable-line
32-
PERCENTAGE_FILTER_KEY = "PRCNT" # cspell:disable-line
33-
TIME_WINDOW_FILTER_KEY = "TIME"
34-
TARGETING_FILTER_KEY = "TRGT" # cspell:disable-line
35-
3627
# Mime profiles
3728
APP_CONFIG_AI_MIME_PROFILE = "https://azconfig.io/mime-profiles/ai/"
3829
APP_CONFIG_AICC_MIME_PROFILE = "https://azconfig.io/mime-profiles/ai/chat-completion"

0 commit comments

Comments
 (0)