2525from abc import ABC , abstractmethod
2626from logging import getLogger
2727from os import environ
28- from typing import Optional , cast
28+ from threading import Lock
29+ from typing import List , Optional , cast
2930
3031from opentelemetry .environment_variables import OTEL_PYTHON_METER_PROVIDER
3132from opentelemetry .metrics .instrument import (
4142 ObservableGauge ,
4243 ObservableUpDownCounter ,
4344 UpDownCounter ,
45+ _ProxyCounter ,
46+ _ProxyHistogram ,
47+ _ProxyInstrument ,
48+ _ProxyObservableCounter ,
49+ _ProxyObservableGauge ,
50+ _ProxyObservableUpDownCounter ,
51+ _ProxyUpDownCounter ,
4452)
53+ from opentelemetry .util ._once import Once
4554from opentelemetry .util ._providers import _load_provider
4655
4756_logger = getLogger (__name__ )
@@ -70,18 +79,33 @@ def get_meter(
7079 return _DefaultMeter (name , version = version , schema_url = schema_url )
7180
7281
73- class ProxyMeterProvider (MeterProvider ):
82+ class _ProxyMeterProvider (MeterProvider ):
83+ def __init__ (self ) -> None :
84+ self ._lock = Lock ()
85+ self ._meters : List [_ProxyMeter ] = []
86+ self ._real_meter_provider : Optional [MeterProvider ] = None
87+
7488 def get_meter (
7589 self ,
7690 name ,
7791 version = None ,
7892 schema_url = None ,
7993 ) -> "Meter" :
80- if _METER_PROVIDER :
81- return _METER_PROVIDER .get_meter (
82- name , version = version , schema_url = schema_url
83- )
84- return ProxyMeter (name , version = version , schema_url = schema_url )
94+ with self ._lock :
95+ if self ._real_meter_provider is not None :
96+ return self ._real_meter_provider .get_meter (
97+ name , version , schema_url
98+ )
99+
100+ meter = _ProxyMeter (name , version = version , schema_url = schema_url )
101+ self ._meters .append (meter )
102+ return meter
103+
104+ def on_set_meter_provider (self , meter_provider : MeterProvider ) -> None :
105+ with self ._lock :
106+ self ._real_meter_provider = meter_provider
107+ for meter in self ._meters :
108+ meter .on_set_meter_provider (meter_provider )
85109
86110
87111class Meter (ABC ):
@@ -225,51 +249,109 @@ def create_observable_up_down_counter(
225249 self ._secure_instrument_name (name )
226250
227251
228- class ProxyMeter (Meter ):
252+ class _ProxyMeter (Meter ):
229253 def __init__ (
230254 self ,
231255 name ,
232256 version = None ,
233257 schema_url = None ,
234258 ):
235259 super ().__init__ (name , version = version , schema_url = schema_url )
260+ self ._lock = Lock ()
261+ self ._instruments : List [_ProxyInstrument ] = []
236262 self ._real_meter : Optional [Meter ] = None
237- self ._noop_meter = _DefaultMeter (
238- name , version = version , schema_url = schema_url
263+
264+ def on_set_meter_provider (self , meter_provider : MeterProvider ) -> None :
265+ """Called when a real meter provider is set on the creating _ProxyMeterProvider
266+
267+ Creates a real backing meter for this instance and notifies all created
268+ instruments so they can create real backing instruments.
269+ """
270+ real_meter = meter_provider .get_meter (
271+ self ._name , self ._version , self ._schema_url
239272 )
240273
241- @property
242- def _meter (self ) -> Meter :
243- if self ._real_meter is not None :
244- return self ._real_meter
245-
246- if _METER_PROVIDER :
247- self ._real_meter = _METER_PROVIDER .get_meter (
248- self ._name ,
249- self ._version ,
250- )
251- return self ._real_meter
252- return self ._noop_meter
274+ with self ._lock :
275+ self ._real_meter = real_meter
276+ # notify all proxy instruments of the new meter so they can create
277+ # real instruments to back themselves
278+ for instrument in self ._instruments :
279+ instrument .on_meter_set (real_meter )
253280
254- def create_counter (self , * args , ** kwargs ) -> Counter :
255- return self ._meter .create_counter (* args , ** kwargs )
281+ def create_counter (self , name , unit = "" , description = "" ) -> Counter :
282+ with self ._lock :
283+ if self ._real_meter :
284+ return self ._real_meter .create_counter (name , unit , description )
285+ proxy = _ProxyCounter (name , unit , description )
286+ self ._instruments .append (proxy )
287+ return proxy
256288
257- def create_up_down_counter (self , * args , ** kwargs ) -> UpDownCounter :
258- return self ._meter .create_up_down_counter (* args , ** kwargs )
289+ def create_up_down_counter (
290+ self , name , unit = "" , description = ""
291+ ) -> UpDownCounter :
292+ with self ._lock :
293+ if self ._real_meter :
294+ return self ._real_meter .create_up_down_counter (
295+ name , unit , description
296+ )
297+ proxy = _ProxyUpDownCounter (name , unit , description )
298+ self ._instruments .append (proxy )
299+ return proxy
259300
260- def create_observable_counter (self , * args , ** kwargs ) -> ObservableCounter :
261- return self ._meter .create_observable_counter (* args , ** kwargs )
301+ def create_observable_counter (
302+ self , name , callback , unit = "" , description = ""
303+ ) -> ObservableCounter :
304+ with self ._lock :
305+ if self ._real_meter :
306+ return self ._real_meter .create_observable_counter (
307+ name , callback , unit , description
308+ )
309+ proxy = _ProxyObservableCounter (
310+ name , callback , unit = unit , description = description
311+ )
312+ self ._instruments .append (proxy )
313+ return proxy
262314
263- def create_histogram (self , * args , ** kwargs ) -> Histogram :
264- return self ._meter .create_histogram (* args , ** kwargs )
315+ def create_histogram (self , name , unit = "" , description = "" ) -> Histogram :
316+ with self ._lock :
317+ if self ._real_meter :
318+ return self ._real_meter .create_histogram (
319+ name , unit , description
320+ )
321+ proxy = _ProxyHistogram (name , unit , description )
322+ self ._instruments .append (proxy )
323+ return proxy
265324
266- def create_observable_gauge (self , * args , ** kwargs ) -> ObservableGauge :
267- return self ._meter .create_observable_gauge (* args , ** kwargs )
325+ def create_observable_gauge (
326+ self , name , callback , unit = "" , description = ""
327+ ) -> ObservableGauge :
328+ with self ._lock :
329+ if self ._real_meter :
330+ return self ._real_meter .create_observable_gauge (
331+ name , callback , unit , description
332+ )
333+ proxy = _ProxyObservableGauge (
334+ name , callback , unit = unit , description = description
335+ )
336+ self ._instruments .append (proxy )
337+ return proxy
268338
269339 def create_observable_up_down_counter (
270- self , * args , ** kwargs
340+ self , name , callback , unit = "" , description = ""
271341 ) -> ObservableUpDownCounter :
272- return self ._meter .create_observable_up_down_counter (* args , ** kwargs )
342+ with self ._lock :
343+ if self ._real_meter :
344+ return self ._real_meter .create_observable_up_down_counter (
345+ name ,
346+ callback ,
347+ unit ,
348+ description ,
349+ )
350+ proxy = _ProxyObservableUpDownCounter (
351+ name , callback , unit = unit , description = description
352+ )
353+ self ._instruments .append (proxy )
354+ return proxy
273355
274356
275357class _DefaultMeter (Meter ):
@@ -329,8 +411,9 @@ def create_observable_up_down_counter(
329411 )
330412
331413
332- _METER_PROVIDER = None
333- _PROXY_METER_PROVIDER = None
414+ _METER_PROVIDER_SET_ONCE = Once ()
415+ _METER_PROVIDER : Optional [MeterProvider ] = None
416+ _PROXY_METER_PROVIDER = _ProxyMeterProvider ()
334417
335418
336419def get_meter (
@@ -350,35 +433,40 @@ def get_meter(
350433 return meter_provider .get_meter (name , version )
351434
352435
436+ def _set_meter_provider (meter_provider : MeterProvider , log : bool ) -> None :
437+ def set_mp () -> None :
438+ global _METER_PROVIDER # pylint: disable=global-statement
439+ _METER_PROVIDER = meter_provider
440+
441+ # gives all proxies real instruments off the newly set meter provider
442+ _PROXY_METER_PROVIDER .on_set_meter_provider (meter_provider )
443+
444+ did_set = _METER_PROVIDER_SET_ONCE .do_once (set_mp )
445+
446+ if log and not did_set :
447+ _logger .warning ("Overriding of current MeterProvider is not allowed" )
448+
449+
353450def set_meter_provider (meter_provider : MeterProvider ) -> None :
354451 """Sets the current global :class:`~.MeterProvider` object.
355452
356453 This can only be done once, a warning will be logged if any furter attempt
357454 is made.
358455 """
359- global _METER_PROVIDER # pylint: disable=global-statement
360-
361- if _METER_PROVIDER is not None :
362- _logger .warning ("Overriding of current MeterProvider is not allowed" )
363- return
364-
365- _METER_PROVIDER = meter_provider
456+ _set_meter_provider (meter_provider , log = True )
366457
367458
368459def get_meter_provider () -> MeterProvider :
369460 """Gets the current global :class:`~.MeterProvider` object."""
370- # pylint: disable=global-statement
371- global _METER_PROVIDER
372- global _PROXY_METER_PROVIDER
373461
374462 if _METER_PROVIDER is None :
375463 if OTEL_PYTHON_METER_PROVIDER not in environ .keys ():
376- if _PROXY_METER_PROVIDER is None :
377- _PROXY_METER_PROVIDER = ProxyMeterProvider ()
378464 return _PROXY_METER_PROVIDER
379465
380- _METER_PROVIDER = cast (
381- "MeterProvider" ,
382- _load_provider (OTEL_PYTHON_METER_PROVIDER , "meter_provider" ),
466+ meter_provider : MeterProvider = _load_provider (
467+ OTEL_PYTHON_METER_PROVIDER , "meter_provider"
383468 )
384- return _METER_PROVIDER
469+ _set_meter_provider (meter_provider , log = False )
470+
471+ # _METER_PROVIDER will have been set by one thread
472+ return cast ("MeterProvider" , _METER_PROVIDER )
0 commit comments