1414
1515import weakref
1616from atexit import register , unregister
17+ from dataclasses import dataclass
1718from logging import getLogger
1819from os import environ
1920from threading import Lock
2021from time import time_ns
21- from typing import Optional , Sequence
22+ from typing import Callable , Optional , Sequence
2223
2324# This kind of import is needed to avoid Sphinx errors.
2425import opentelemetry .sdk .metrics
7172_logger = getLogger (__name__ )
7273
7374
75+ @dataclass
76+ class _MeterConfig :
77+ is_enabled : bool = True
78+
79+ @classmethod
80+ def default (cls ) -> "_MeterConfig" :
81+ return _MeterConfig ()
82+
83+
84+ class _ProxyMeterConfig :
85+ def __init__ (self , config : _MeterConfig ):
86+ self ._config = config
87+
88+ @property
89+ def is_enabled (self ) -> bool :
90+ return self ._config .is_enabled
91+
92+ def update (self , config : _MeterConfig ) -> None :
93+ self ._config = config
94+
95+
7496class Meter (APIMeter ):
7597 """See `opentelemetry.metrics.Meter`."""
7698
7799 def __init__ (
78100 self ,
79101 instrumentation_scope : InstrumentationScope ,
80102 measurement_consumer : MeasurementConsumer ,
103+ * ,
104+ _meter_config : Optional [_MeterConfig ] = None ,
81105 ):
82106 super ().__init__ (
83107 name = instrumentation_scope .name ,
@@ -88,6 +112,15 @@ def __init__(
88112 self ._measurement_consumer = measurement_consumer
89113 self ._instrument_id_instrument = {}
90114 self ._instrument_registration_lock = Lock ()
115+ self ._meter_config = _ProxyMeterConfig (
116+ _meter_config or _MeterConfig .default ()
117+ )
118+
119+ def _is_enabled (self ) -> bool :
120+ return self ._meter_config .is_enabled
121+
122+ def _set_meter_config (self , meter_config : _MeterConfig ) -> None :
123+ self ._meter_config .update (meter_config )
91124
92125 def create_counter (self , name , unit = "" , description = "" ) -> APICounter :
93126 with self ._instrument_registration_lock :
@@ -102,6 +135,7 @@ def create_counter(self, name, unit="", description="") -> APICounter:
102135 self ._measurement_consumer ,
103136 unit ,
104137 description ,
138+ _meter_config = self ._meter_config ,
105139 )
106140 )
107141 instrument = self ._instrument_id_instrument [status .instrument_id ]
@@ -134,6 +168,7 @@ def create_up_down_counter(
134168 self ._measurement_consumer ,
135169 unit ,
136170 description ,
171+ _meter_config = self ._meter_config ,
137172 )
138173 )
139174 instrument = self ._instrument_id_instrument [status .instrument_id ]
@@ -171,6 +206,7 @@ def create_observable_counter(
171206 callbacks ,
172207 unit ,
173208 description ,
209+ _meter_config = self ._meter_config ,
174210 )
175211 )
176212 instrument = self ._instrument_id_instrument [status .instrument_id ]
@@ -239,6 +275,7 @@ def create_histogram(
239275 unit ,
240276 description ,
241277 explicit_bucket_boundaries_advisory ,
278+ _meter_config = self ._meter_config ,
242279 )
243280 )
244281 instrument = self ._instrument_id_instrument [status .instrument_id ]
@@ -266,6 +303,7 @@ def create_gauge(self, name, unit="", description="") -> APIGauge:
266303 self ._measurement_consumer ,
267304 unit ,
268305 description ,
306+ _meter_config = self ._meter_config ,
269307 )
270308 instrument = self ._instrument_id_instrument [status .instrument_id ]
271309
@@ -298,6 +336,7 @@ def create_observable_gauge(
298336 callbacks ,
299337 unit ,
300338 description ,
339+ _meter_config = self ._meter_config ,
301340 )
302341 )
303342 instrument = self ._instrument_id_instrument [status .instrument_id ]
@@ -336,6 +375,7 @@ def create_observable_up_down_counter(
336375 callbacks ,
337376 unit ,
338377 description ,
378+ _meter_config = self ._meter_config ,
339379 )
340380 )
341381 instrument = self ._instrument_id_instrument [status .instrument_id ]
@@ -370,6 +410,43 @@ def _get_exemplar_filter(exemplar_filter: str) -> ExemplarFilter:
370410 raise ValueError (msg )
371411
372412
413+ _MeterConfiguratorT = Callable [[InstrumentationScope ], _MeterConfig ]
414+ _InstrumentationScopePredicateT = Callable [[InstrumentationScope ], bool ]
415+ _MeterConfiguratorRulesT = Sequence [
416+ tuple [_InstrumentationScopePredicateT , _MeterConfig ]
417+ ]
418+
419+
420+ def _default_meter_configurator (
421+ _meter_scope : InstrumentationScope ,
422+ ) -> _MeterConfig :
423+ return _MeterConfig .default ()
424+
425+
426+ def _disable_meter_configurator (
427+ _meter_scope : InstrumentationScope ,
428+ ) -> _MeterConfig :
429+ return _MeterConfig (is_enabled = False )
430+
431+
432+ class _RuleBasedMeterConfigurator :
433+ def __init__ (
434+ self ,
435+ * ,
436+ rules : _MeterConfiguratorRulesT ,
437+ default_config : _MeterConfig ,
438+ ):
439+ self ._rules = rules
440+ self ._default_config = default_config
441+
442+ def __call__ (self , meter_scope : InstrumentationScope ) -> _MeterConfig :
443+ for predicate , meter_config in self ._rules :
444+ if predicate (meter_scope ):
445+ return meter_config
446+ # by default return default config
447+ return self ._default_config
448+
449+
373450class MeterProvider (APIMeterProvider ):
374451 r"""See `opentelemetry.metrics.MeterProvider`.
375452
@@ -414,6 +491,8 @@ def __init__(
414491 exemplar_filter : Optional [ExemplarFilter ] = None ,
415492 shutdown_on_exit : bool = True ,
416493 views : Sequence ["opentelemetry.sdk.metrics.view.View" ] = (),
494+ * ,
495+ _meter_configurator : Optional [_MeterConfiguratorT ] = None ,
417496 ):
418497 self ._lock = Lock ()
419498 self ._meter_lock = Lock ()
@@ -443,6 +522,9 @@ def __init__(
443522 self ._meters = {}
444523 self ._shutdown_once = Once ()
445524 self ._shutdown = False
525+ self ._meter_configurator = (
526+ _meter_configurator or _default_meter_configurator
527+ )
446528
447529 for metric_reader in self ._sdk_config .metric_readers :
448530 with self ._all_metric_readers_lock :
@@ -459,6 +541,22 @@ def __init__(
459541 self ._measurement_consumer .collect
460542 )
461543
544+ def _set_meter_configurator (
545+ self , * , meter_configurator : _MeterConfiguratorT
546+ ):
547+ """Set a new MeterConfigurator for this MeterProvider.
548+
549+ Setting a new MeterConfigurator will result in the configurator being called
550+ for each outstanding Meter and for any newly created meters thereafter.
551+ Therefore, it is important that the provided function returns quickly.
552+ """
553+ self ._meter_configurator = meter_configurator
554+ with self ._meter_lock :
555+ for info , meter in self ._meters .items ():
556+ if not isinstance (meter , Meter ):
557+ continue
558+ meter ._set_meter_config (self ._meter_configurator (info ))
559+
462560 def force_flush (self , timeout_millis : float = 10_000 ) -> bool :
463561 deadline_ns = time_ns () + timeout_millis * 10 ** 6
464562
@@ -574,5 +672,6 @@ def get_meter(
574672 self ._meters [info ] = Meter (
575673 info ,
576674 self ._measurement_consumer ,
675+ _meter_config = self ._meter_configurator (info ),
577676 )
578677 return self ._meters [info ]
0 commit comments