66import base64
77import hashlib
88import json
9- import os
109import time
1110import datetime
12- from importlib .metadata import version , PackageNotFoundError
1311from threading import Lock
1412import logging
1513from typing import (
3331)
3432from ._models import SettingSelector
3533from ._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 ,
5843 FEATURE_FLAG_KEY ,
5944)
6045from ._refresh_timer import _RefreshTimer
46+ from ._request_tracing_context import _RequestTracingContext
47+
6148
6249JSON = 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 ]
0 commit comments