7
7
8
8
from azure .monitor .opentelemetry .exporter ._constants import (
9
9
_ONE_SETTINGS_DEFAULT_REFRESH_INTERVAL_SECONDS ,
10
- _ONE_SETTINGS_PYTHON_KEY ,
11
10
_ONE_SETTINGS_CHANGE_URL ,
12
11
_ONE_SETTINGS_CONFIG_URL ,
13
12
)
14
13
from azure .monitor .opentelemetry .exporter ._configuration ._utils import make_onesettings_request
14
+ from azure .monitor .opentelemetry .exporter ._utils import Singleton
15
15
16
16
# Set up logger
17
17
logger = logging .getLogger (__name__ )
@@ -35,34 +35,42 @@ def with_updates(self, **kwargs) -> '_ConfigurationState': # pylint: disable=C4
35
35
)
36
36
37
37
38
- class _ConfigurationManager :
38
+ class _ConfigurationManager ( metaclass = Singleton ) :
39
39
"""Singleton class to manage configuration settings."""
40
40
41
- _instance = None
42
- _configuration_worker = None
43
- _instance_lock = Lock ()
44
- _state_lock = Lock () # Single lock for all state
45
- _current_state = _ConfigurationState ()
41
+ def __init__ (self ):
42
+ """Initialize the ConfigurationManager instance."""
46
43
47
- def __new__ (cls ):
48
- with cls ._instance_lock :
49
- if cls ._instance is None :
50
- cls ._instance = super (_ConfigurationManager , cls ).__new__ (cls )
51
- # Initialize the instance here to avoid re-initialization
52
- cls ._instance ._initialize_worker ()
53
- return cls ._instance
44
+ self ._configuration_worker = None
45
+ self ._state_lock = Lock () # Single lock for all state
46
+ self ._current_state = _ConfigurationState ()
47
+ self ._callbacks = []
48
+ self ._initialize_worker ()
54
49
55
50
def _initialize_worker (self ):
56
51
"""Initialize the ConfigurationManager and start the configuration worker."""
57
52
# Lazy import to avoid circular import
58
53
from azure .monitor .opentelemetry .exporter ._configuration ._worker import _ConfigurationWorker
59
54
60
55
# Get initial refresh interval from state
61
- with _ConfigurationManager ._state_lock :
62
- initial_refresh_interval = _ConfigurationManager ._current_state .refresh_interval
56
+ with self ._state_lock :
57
+ initial_refresh_interval = self ._current_state .refresh_interval
63
58
64
- self ._configuration_worker = _ConfigurationWorker (initial_refresh_interval )
59
+ self ._configuration_worker = _ConfigurationWorker (self , initial_refresh_interval )
65
60
61
+ def register_callback (self , callback ):
62
+ # Register a callback to be invoked when configuration changes.
63
+ self ._callbacks .append (callback )
64
+
65
+ def _notify_callbacks (self , settings : Dict [str , str ]):
66
+ # Notify all registered callbacks of configuration changes.
67
+ for cb in self ._callbacks :
68
+ try :
69
+ cb (settings )
70
+ except Exception as ex : # pylint: disable=broad-except
71
+ logger .warning ("Callback failed: %s" , ex )
72
+
73
+ # pylint: disable=too-many-statements
66
74
def get_configuration_and_refresh_interval (self , query_dict : Optional [Dict [str , str ]] = None ) -> int :
67
75
"""Fetch configuration from OneSettings and update local cache atomically.
68
76
@@ -122,8 +130,8 @@ def get_configuration_and_refresh_interval(self, query_dict: Optional[Dict[str,
122
130
headers = {}
123
131
124
132
# Read current state atomically
125
- with _ConfigurationManager ._state_lock :
126
- current_state = _ConfigurationManager ._current_state
133
+ with self ._state_lock :
134
+ current_state = self ._current_state
127
135
if current_state .etag :
128
136
headers ["If-None-Match" ] = current_state .etag
129
137
if current_state .refresh_interval :
@@ -145,8 +153,8 @@ def get_configuration_and_refresh_interval(self, query_dict: Optional[Dict[str,
145
153
# Handle version and settings updates
146
154
elif response .settings and response .version is not None :
147
155
needs_config_fetch = False
148
- with _ConfigurationManager ._state_lock :
149
- current_state = _ConfigurationManager ._current_state
156
+ with self ._state_lock :
157
+ current_state = self ._current_state
150
158
151
159
if response .version > current_state .version_cache :
152
160
# Version increase: new config available
@@ -182,34 +190,41 @@ def get_configuration_and_refresh_interval(self, query_dict: Optional[Dict[str,
182
190
# No settings or version provided
183
191
logger .warning ("No settings or version provided in config response. Config not updated." )
184
192
193
+
194
+ notify_callbacks = False
195
+ current_refresh_interval = _ONE_SETTINGS_DEFAULT_REFRESH_INTERVAL_SECONDS
196
+ state_for_callbacks = None
197
+
185
198
# Atomic state update
186
- with _ConfigurationManager ._state_lock :
187
- latest_state = _ConfigurationManager ._current_state # Always use latest state
188
- _ConfigurationManager ._current_state = latest_state .with_updates (** new_state_updates )
189
- return _ConfigurationManager ._current_state .refresh_interval
199
+ with self ._state_lock :
200
+ latest_state = self ._current_state # Always use latest state
201
+ self ._current_state = latest_state .with_updates (** new_state_updates )
202
+ current_refresh_interval = self ._current_state .refresh_interval
203
+ if 'settings_cache' in new_state_updates :
204
+ notify_callbacks = True
205
+ state_for_callbacks = self ._current_state
206
+
207
+ # Handle configuration updates throughout the SDK
208
+ if notify_callbacks and state_for_callbacks is not None and state_for_callbacks .settings_cache :
209
+ self ._notify_callbacks (state_for_callbacks .settings_cache )
210
+
211
+ return current_refresh_interval
190
212
191
213
def get_settings (self ) -> Dict [str , str ]: # pylint: disable=C4741,C4742
192
214
"""Get current settings cache."""
193
- with _ConfigurationManager ._state_lock :
194
- return _ConfigurationManager ._current_state .settings_cache .copy ()
215
+ with self ._state_lock :
216
+ return self ._current_state .settings_cache .copy () # type: ignore
195
217
196
218
def get_current_version (self ) -> int : # pylint: disable=C4741,C4742
197
219
"""Get current version."""
198
- with _ConfigurationManager ._state_lock :
199
- return _ConfigurationManager ._current_state .version_cache
220
+ with self ._state_lock :
221
+ return self ._current_state .version_cache # type: ignore
200
222
201
223
def shutdown (self ) -> None :
202
224
"""Shutdown the configuration worker."""
203
- with _ConfigurationManager ._instance_lock :
204
- if self ._configuration_worker :
205
- self ._configuration_worker .shutdown ()
206
- self ._configuration_worker = None
207
- if _ConfigurationManager ._instance :
208
- _ConfigurationManager ._instance = None
209
-
210
-
211
- def _update_configuration_and_get_refresh_interval () -> int :
212
- targeting = {
213
- "namespaces" : _ONE_SETTINGS_PYTHON_KEY ,
214
- }
215
- return _ConfigurationManager ().get_configuration_and_refresh_interval (targeting )
225
+ if self ._configuration_worker :
226
+ self ._configuration_worker .shutdown ()
227
+ self ._configuration_worker = None
228
+ # Clear the singleton instance from the metaclass
229
+ if self .__class__ in Singleton ._instances : # pylint: disable=protected-access
230
+ del Singleton ._instances [self .__class__ ] # pylint: disable=protected-access
0 commit comments