4242
4343import warnings
4444from abc import ABC , abstractmethod
45+ from dataclasses import dataclass
4546from logging import getLogger
4647from os import environ
4748from threading import Lock
48- from typing import List , Optional , Sequence , Set , Tuple , Union , cast
49+ from typing import Dict , List , Optional , Sequence , Union , cast
4950
5051from opentelemetry .environment_variables import OTEL_PYTHON_METER_PROVIDER
5152from opentelemetry .metrics ._internal .instrument import (
6465 ObservableGauge ,
6566 ObservableUpDownCounter ,
6667 UpDownCounter ,
68+ _MetricsHistogramAdvisory ,
6769 _ProxyCounter ,
6870 _ProxyGauge ,
6971 _ProxyHistogram ,
7476)
7577from opentelemetry .util ._once import Once
7678from opentelemetry .util ._providers import _load_provider
77- from opentelemetry .util .types import Attributes
79+ from opentelemetry .util .types import (
80+ Attributes ,
81+ )
7882
7983_logger = getLogger (__name__ )
8084
@@ -177,6 +181,14 @@ def on_set_meter_provider(self, meter_provider: MeterProvider) -> None:
177181 meter .on_set_meter_provider (meter_provider )
178182
179183
184+ @dataclass
185+ class _InstrumentRegistrationStatus :
186+ instrument_id : str
187+ already_registered : bool
188+ conflict : bool
189+ current_advisory : Optional [_MetricsHistogramAdvisory ]
190+
191+
180192class Meter (ABC ):
181193 """Handles instrument creation.
182194
@@ -194,7 +206,9 @@ def __init__(
194206 self ._name = name
195207 self ._version = version
196208 self ._schema_url = schema_url
197- self ._instrument_ids : Set [str ] = set ()
209+ self ._instrument_ids : Dict [
210+ str , Optional [_MetricsHistogramAdvisory ]
211+ ] = {}
198212 self ._instrument_ids_lock = Lock ()
199213
200214 @property
@@ -218,31 +232,68 @@ def schema_url(self) -> Optional[str]:
218232 """
219233 return self ._schema_url
220234
221- def _is_instrument_registered (
222- self , name : str , type_ : type , unit : str , description : str
223- ) -> Tuple [bool , str ]:
235+ def _register_instrument (
236+ self ,
237+ name : str ,
238+ type_ : type ,
239+ unit : str ,
240+ description : str ,
241+ advisory : Optional [_MetricsHistogramAdvisory ] = None ,
242+ ) -> _InstrumentRegistrationStatus :
224243 """
225- Check if an instrument with the same name, type, unit and description
226- has been registered already.
227-
228- Returns a tuple. The first value is `True` if the instrument has been
229- registered already, `False` otherwise. The second value is the
230- instrument id.
244+ Register an instrument with the name, type, unit and description as
245+ identifying keys and the advisory as value.
246+
247+ Returns a tuple. The first value is the instrument id.
248+ The second value is an `_InstrumentRegistrationStatus` where
249+ `already_registered` is `True` if the instrument has been registered
250+ already.
251+ If `conflict` is set to True the `current_advisory` attribute contains
252+ the registered instrument advisory.
231253 """
232254
233255 instrument_id = "," .join (
234256 [name .strip ().lower (), type_ .__name__ , unit , description ]
235257 )
236258
237- result = False
259+ already_registered = False
260+ conflict = False
261+ current_advisory = None
238262
239263 with self ._instrument_ids_lock :
240- if instrument_id in self ._instrument_ids :
241- result = True
264+ # we are not using get because None is a valid value
265+ already_registered = instrument_id in self ._instrument_ids
266+ if already_registered :
267+ current_advisory = self ._instrument_ids [instrument_id ]
268+ conflict = current_advisory != advisory
242269 else :
243- self ._instrument_ids . add ( instrument_id )
270+ self ._instrument_ids [ instrument_id ] = advisory
244271
245- return (result , instrument_id )
272+ return _InstrumentRegistrationStatus (
273+ instrument_id = instrument_id ,
274+ already_registered = already_registered ,
275+ conflict = conflict ,
276+ current_advisory = current_advisory ,
277+ )
278+
279+ @staticmethod
280+ def _log_instrument_registration_conflict (
281+ name : str ,
282+ instrumentation_type : str ,
283+ unit : str ,
284+ description : str ,
285+ status : _InstrumentRegistrationStatus ,
286+ ) -> None :
287+ _logger .warning (
288+ "An instrument with name %s, type %s, unit %s and "
289+ "description %s has been created already with a "
290+ "different advisory value %s and will be used instead." ,
291+ name ,
292+ instrumentation_type ,
293+ unit ,
294+ description ,
295+ status .current_advisory ,
296+ )
246297
247298 @abstractmethod
248299 def create_counter (
@@ -379,6 +430,8 @@ def create_histogram(
379430 name : str ,
380431 unit : str = "" ,
381432 description : str = "" ,
433+ * ,
434+ explicit_bucket_boundaries_advisory : Optional [Sequence [float ]] = None ,
382435 ) -> Histogram :
383436 """Creates a :class:`~opentelemetry.metrics.Histogram` instrument
384437
@@ -526,13 +579,20 @@ def create_histogram(
526579 name : str ,
527580 unit : str = "" ,
528581 description : str = "" ,
582+ * ,
583+ explicit_bucket_boundaries_advisory : Optional [Sequence [float ]] = None ,
529584 ) -> Histogram :
530585 with self ._lock :
531586 if self ._real_meter :
532587 return self ._real_meter .create_histogram (
533- name , unit , description
588+ name ,
589+ unit ,
590+ description ,
591+ explicit_bucket_boundaries_advisory = explicit_bucket_boundaries_advisory ,
534592 )
535- proxy = _ProxyHistogram (name , unit , description )
593+ proxy = _ProxyHistogram (
594+ name , unit , description , explicit_bucket_boundaries_advisory
595+ )
536596 self ._instruments .append (proxy )
537597 return proxy
538598
@@ -602,17 +662,18 @@ def create_counter(
602662 description : str = "" ,
603663 ) -> Counter :
604664 """Returns a no-op Counter."""
605- if self ._is_instrument_registered (
665+ status = self ._register_instrument (
606666 name , NoOpCounter , unit , description
607- )[0 ]:
608- _logger .warning (
609- "An instrument with name %s, type %s, unit %s and "
610- "description %s has been created already." ,
667+ )
668+ if status .conflict :
669+ self ._log_instrument_registration_conflict (
611670 name ,
612671 Counter .__name__ ,
613672 unit ,
614673 description ,
674+ status ,
615675 )
676+
616677 return NoOpCounter (name , unit = unit , description = description )
617678
618679 def create_gauge (
@@ -622,16 +683,14 @@ def create_gauge(
622683 description : str = "" ,
623684 ) -> Gauge :
624685 """Returns a no-op Gauge."""
625- if self ._is_instrument_registered (name , NoOpGauge , unit , description )[
626- 0
627- ]:
628- _logger .warning (
629- "An instrument with name %s, type %s, unit %s and "
630- "description %s has been created already." ,
686+ status = self ._register_instrument (name , NoOpGauge , unit , description )
687+ if status .conflict :
688+ self ._log_instrument_registration_conflict (
631689 name ,
632690 Gauge .__name__ ,
633691 unit ,
634692 description ,
693+ status ,
635694 )
636695 return NoOpGauge (name , unit = unit , description = description )
637696
@@ -642,16 +701,16 @@ def create_up_down_counter(
642701 description : str = "" ,
643702 ) -> UpDownCounter :
644703 """Returns a no-op UpDownCounter."""
645- if self ._is_instrument_registered (
704+ status = self ._register_instrument (
646705 name , NoOpUpDownCounter , unit , description
647- )[0 ]:
648- _logger .warning (
649- "An instrument with name %s, type %s, unit %s and "
650- "description %s has been created already." ,
706+ )
707+ if status .conflict :
708+ self ._log_instrument_registration_conflict (
651709 name ,
652710 UpDownCounter .__name__ ,
653711 unit ,
654712 description ,
713+ status ,
655714 )
656715 return NoOpUpDownCounter (name , unit = unit , description = description )
657716
@@ -663,16 +722,16 @@ def create_observable_counter(
663722 description : str = "" ,
664723 ) -> ObservableCounter :
665724 """Returns a no-op ObservableCounter."""
666- if self ._is_instrument_registered (
725+ status = self ._register_instrument (
667726 name , NoOpObservableCounter , unit , description
668- )[0 ]:
669- _logger .warning (
670- "An instrument with name %s, type %s, unit %s and "
671- "description %s has been created already." ,
727+ )
728+ if status .conflict :
729+ self ._log_instrument_registration_conflict (
672730 name ,
673731 ObservableCounter .__name__ ,
674732 unit ,
675733 description ,
734+ status ,
676735 )
677736 return NoOpObservableCounter (
678737 name ,
@@ -686,20 +745,33 @@ def create_histogram(
686745 name : str ,
687746 unit : str = "" ,
688747 description : str = "" ,
748+ * ,
749+ explicit_bucket_boundaries_advisory : Optional [Sequence [float ]] = None ,
689750 ) -> Histogram :
690751 """Returns a no-op Histogram."""
691- if self ._is_instrument_registered (
692- name , NoOpHistogram , unit , description
693- )[0 ]:
694- _logger .warning (
695- "An instrument with name %s, type %s, unit %s and "
696- "description %s has been created already." ,
752+ status = self ._register_instrument (
753+ name ,
754+ NoOpHistogram ,
755+ unit ,
756+ description ,
757+ _MetricsHistogramAdvisory (
758+ explicit_bucket_boundaries = explicit_bucket_boundaries_advisory
759+ ),
760+ )
761+ if status .conflict :
762+ self ._log_instrument_registration_conflict (
697763 name ,
698764 Histogram .__name__ ,
699765 unit ,
700766 description ,
767+ status ,
701768 )
702- return NoOpHistogram (name , unit = unit , description = description )
769+ return NoOpHistogram (
770+ name ,
771+ unit = unit ,
772+ description = description ,
773+ explicit_bucket_boundaries_advisory = explicit_bucket_boundaries_advisory ,
774+ )
703775
704776 def create_observable_gauge (
705777 self ,
@@ -709,16 +781,16 @@ def create_observable_gauge(
709781 description : str = "" ,
710782 ) -> ObservableGauge :
711783 """Returns a no-op ObservableGauge."""
712- if self ._is_instrument_registered (
784+ status = self ._register_instrument (
713785 name , NoOpObservableGauge , unit , description
714- )[0 ]:
715- _logger .warning (
716- "An instrument with name %s, type %s, unit %s and "
717- "description %s has been created already." ,
786+ )
787+ if status .conflict :
788+ self ._log_instrument_registration_conflict (
718789 name ,
719790 ObservableGauge .__name__ ,
720791 unit ,
721792 description ,
793+ status ,
722794 )
723795 return NoOpObservableGauge (
724796 name ,
@@ -735,16 +807,16 @@ def create_observable_up_down_counter(
735807 description : str = "" ,
736808 ) -> ObservableUpDownCounter :
737809 """Returns a no-op ObservableUpDownCounter."""
738- if self ._is_instrument_registered (
810+ status = self ._register_instrument (
739811 name , NoOpObservableUpDownCounter , unit , description
740- )[0 ]:
741- _logger .warning (
742- "An instrument with name %s, type %s, unit %s and "
743- "description %s has been created already." ,
812+ )
813+ if status .conflict :
814+ self ._log_instrument_registration_conflict (
744815 name ,
745816 ObservableUpDownCounter .__name__ ,
746817 unit ,
747818 description ,
819+ status ,
748820 )
749821 return NoOpObservableUpDownCounter (
750822 name ,
0 commit comments