Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit cb60b54

Browse files
ocelotlsrikanthccvAlex Boten
authored
Add check for duplicate instrument names (open-telemetry#2409)
* Add check for duplicate instrument names Fixes open-telemetry#2142 * Add repeated instrument names for SDK as well * Update opentelemetry-api/src/opentelemetry/_metrics/__init__.py Co-authored-by: Srikanth Chekuri <[email protected]> * Add instrument names lock * Replace raised exception for a warning * Added some fiexes * Add double check * Add type annotations * Update opentelemetry-api/src/opentelemetry/_metrics/__init__.py Co-authored-by: Srikanth Chekuri <[email protected]> * Move check invocation to SDK * Fix lint * Separate instrument checking from warning logging * Use consistent class * Fix typo in example (open-telemetry#2597) * Fix docs Co-authored-by: Srikanth Chekuri <[email protected]> Co-authored-by: Alex Boten <[email protected]>
1 parent e113d54 commit cb60b54

File tree

4 files changed

+249
-16
lines changed

4 files changed

+249
-16
lines changed

opentelemetry-api/src/opentelemetry/_metrics/__init__.py

Lines changed: 88 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from logging import getLogger
2727
from os import environ
2828
from threading import Lock
29-
from typing import List, Optional, cast
29+
from typing import List, Optional, Set, cast
3030

3131
from opentelemetry._metrics.instrument import (
3232
Counter,
@@ -118,7 +118,8 @@ def __init__(self, name, version=None, schema_url=None):
118118
self._name = name
119119
self._version = version
120120
self._schema_url = schema_url
121-
self._instrument_names = set()
121+
self._instrument_ids: Set[str] = set()
122+
self._instrument_ids_lock = Lock()
122123

123124
@property
124125
def name(self):
@@ -132,7 +133,27 @@ def version(self):
132133
def schema_url(self):
133134
return self._schema_url
134135

135-
# FIXME check that the instrument name has not been used already
136+
def _check_instrument_id(
137+
self, name: str, type_: type, unit: str, description: str
138+
) -> bool:
139+
"""
140+
Check if an instrument with the same name, type, unit and description
141+
has been registered already.
142+
"""
143+
144+
result = False
145+
146+
instrument_id = ",".join(
147+
[name.strip().lower(), type_.__name__, unit, description]
148+
)
149+
150+
with self._instrument_ids_lock:
151+
if instrument_id in self._instrument_ids:
152+
result = True
153+
else:
154+
self._instrument_ids.add(instrument_id)
155+
156+
return result
136157

137158
@abstractmethod
138159
def create_counter(self, name, unit="", description="") -> Counter:
@@ -401,6 +422,15 @@ class NoOpMeter(Meter):
401422
def create_counter(self, name, unit="", description="") -> Counter:
402423
"""Returns a no-op Counter."""
403424
super().create_counter(name, unit=unit, description=description)
425+
if self._check_instrument_id(name, DefaultCounter, unit, description):
426+
_logger.warning(
427+
"An instrument with name %s, type %s, unit %s and "
428+
"description %s has been created already.",
429+
name,
430+
Counter.__name__,
431+
unit,
432+
description,
433+
)
404434
return DefaultCounter(name, unit=unit, description=description)
405435

406436
def create_up_down_counter(
@@ -410,6 +440,17 @@ def create_up_down_counter(
410440
super().create_up_down_counter(
411441
name, unit=unit, description=description
412442
)
443+
if self._check_instrument_id(
444+
name, DefaultUpDownCounter, unit, description
445+
):
446+
_logger.warning(
447+
"An instrument with name %s, type %s, unit %s and "
448+
"description %s has been created already.",
449+
name,
450+
UpDownCounter.__name__,
451+
unit,
452+
description,
453+
)
413454
return DefaultUpDownCounter(name, unit=unit, description=description)
414455

415456
def create_observable_counter(
@@ -419,6 +460,17 @@ def create_observable_counter(
419460
super().create_observable_counter(
420461
name, callback, unit=unit, description=description
421462
)
463+
if self._check_instrument_id(
464+
name, DefaultObservableCounter, unit, description
465+
):
466+
_logger.warning(
467+
"An instrument with name %s, type %s, unit %s and "
468+
"description %s has been created already.",
469+
name,
470+
ObservableCounter.__name__,
471+
unit,
472+
description,
473+
)
422474
return DefaultObservableCounter(
423475
name,
424476
callback,
@@ -429,6 +481,17 @@ def create_observable_counter(
429481
def create_histogram(self, name, unit="", description="") -> Histogram:
430482
"""Returns a no-op Histogram."""
431483
super().create_histogram(name, unit=unit, description=description)
484+
if self._check_instrument_id(
485+
name, DefaultHistogram, unit, description
486+
):
487+
_logger.warning(
488+
"An instrument with name %s, type %s, unit %s and "
489+
"description %s has been created already.",
490+
name,
491+
Histogram.__name__,
492+
unit,
493+
description,
494+
)
432495
return DefaultHistogram(name, unit=unit, description=description)
433496

434497
def create_observable_gauge(
@@ -438,6 +501,17 @@ def create_observable_gauge(
438501
super().create_observable_gauge(
439502
name, callback, unit=unit, description=description
440503
)
504+
if self._check_instrument_id(
505+
name, DefaultObservableGauge, unit, description
506+
):
507+
_logger.warning(
508+
"An instrument with name %s, type %s, unit %s and "
509+
"description %s has been created already.",
510+
name,
511+
ObservableGauge.__name__,
512+
unit,
513+
description,
514+
)
441515
return DefaultObservableGauge(
442516
name,
443517
callback,
@@ -452,6 +526,17 @@ def create_observable_up_down_counter(
452526
super().create_observable_up_down_counter(
453527
name, callback, unit=unit, description=description
454528
)
529+
if self._check_instrument_id(
530+
name, DefaultObservableUpDownCounter, unit, description
531+
):
532+
_logger.warning(
533+
"An instrument with name %s, type %s, unit %s and "
534+
"description %s has been created already.",
535+
name,
536+
ObservableUpDownCounter.__name__,
537+
unit,
538+
description,
539+
)
455540
return DefaultObservableUpDownCounter(
456541
name,
457542
callback,

opentelemetry-api/tests/metrics/test_meter.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@
1313
# limitations under the License.
1414
# type: ignore
1515

16+
from logging import WARNING
1617
from unittest import TestCase
18+
from unittest.mock import Mock
1719

18-
from opentelemetry._metrics import Meter
20+
from opentelemetry._metrics import Meter, NoOpMeter
1921

2022
# FIXME Test that the meter methods can be called concurrently safely.
2123

@@ -53,6 +55,42 @@ def create_observable_up_down_counter(
5355

5456

5557
class TestMeter(TestCase):
58+
def test_repeated_instrument_names(self):
59+
60+
try:
61+
test_meter = NoOpMeter("name")
62+
63+
test_meter.create_counter("counter")
64+
test_meter.create_up_down_counter("up_down_counter")
65+
test_meter.create_observable_counter("observable_counter", Mock())
66+
test_meter.create_histogram("histogram")
67+
test_meter.create_observable_gauge("observable_gauge", Mock())
68+
test_meter.create_observable_up_down_counter(
69+
"observable_up_down_counter", Mock()
70+
)
71+
except Exception as error:
72+
self.fail(f"Unexpected exception raised {error}")
73+
74+
for instrument_name in [
75+
"counter",
76+
"up_down_counter",
77+
"histogram",
78+
]:
79+
with self.assertLogs(level=WARNING):
80+
getattr(test_meter, f"create_{instrument_name}")(
81+
instrument_name
82+
)
83+
84+
for instrument_name in [
85+
"observable_counter",
86+
"observable_gauge",
87+
"observable_up_down_counter",
88+
]:
89+
with self.assertLogs(level=WARNING):
90+
getattr(test_meter, f"create_{instrument_name}")(
91+
instrument_name, Mock()
92+
)
93+
5694
def test_create_counter(self):
5795
"""
5896
Test that the meter provides a function to create a new Counter

opentelemetry-sdk/src/opentelemetry/sdk/_metrics/__init__.py

Lines changed: 86 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,20 @@ def __init__(
6464
self._instrumentation_info = instrumentation_info
6565
self._measurement_consumer = measurement_consumer
6666

67-
def create_counter(self, name, unit=None, description=None) -> APICounter:
67+
def create_counter(self, name, unit="", description="") -> APICounter:
68+
if self._check_instrument_id(name, Counter, unit, description):
69+
# FIXME #2558 go through all views here and check if this
70+
# instrument registration conflict can be fixed. If it can be, do
71+
# not log the following warning.
72+
_logger.warning(
73+
"An instrument with name %s, type %s, unit %s and "
74+
"description %s has been created already.",
75+
name,
76+
APICounter.__name__,
77+
unit,
78+
description,
79+
)
80+
6881
return Counter(
6982
name,
7083
self._instrumentation_info,
@@ -74,8 +87,21 @@ def create_counter(self, name, unit=None, description=None) -> APICounter:
7487
)
7588

7689
def create_up_down_counter(
77-
self, name, unit=None, description=None
90+
self, name, unit="", description=""
7891
) -> APIUpDownCounter:
92+
if self._check_instrument_id(name, UpDownCounter, unit, description):
93+
# FIXME #2558 go through all views here and check if this
94+
# instrument registration conflict can be fixed. If it can be, do
95+
# not log the following warning.
96+
_logger.warning(
97+
"An instrument with name %s, type %s, unit %s and "
98+
"description %s has been created already.",
99+
name,
100+
APIUpDownCounter.__name__,
101+
unit,
102+
description,
103+
)
104+
79105
return UpDownCounter(
80106
name,
81107
self._instrumentation_info,
@@ -85,9 +111,22 @@ def create_up_down_counter(
85111
)
86112

87113
def create_observable_counter(
88-
self, name, callback, unit=None, description=None
114+
self, name, callback, unit="", description=""
89115
) -> APIObservableCounter:
90-
116+
if self._check_instrument_id(
117+
name, ObservableCounter, unit, description
118+
):
119+
# FIXME #2558 go through all views here and check if this
120+
# instrument registration conflict can be fixed. If it can be, do
121+
# not log the following warning.
122+
_logger.warning(
123+
"An instrument with name %s, type %s, unit %s and "
124+
"description %s has been created already.",
125+
name,
126+
APIObservableCounter.__name__,
127+
unit,
128+
description,
129+
)
91130
instrument = ObservableCounter(
92131
name,
93132
self._instrumentation_info,
@@ -101,9 +140,19 @@ def create_observable_counter(
101140

102141
return instrument
103142

104-
def create_histogram(
105-
self, name, unit=None, description=None
106-
) -> APIHistogram:
143+
def create_histogram(self, name, unit="", description="") -> APIHistogram:
144+
if self._check_instrument_id(name, Histogram, unit, description):
145+
# FIXME #2558 go through all views here and check if this
146+
# instrument registration conflict can be fixed. If it can be, do
147+
# not log the following warning.
148+
_logger.warning(
149+
"An instrument with name %s, type %s, unit %s and "
150+
"description %s has been created already.",
151+
name,
152+
APIHistogram.__name__,
153+
unit,
154+
description,
155+
)
107156
return Histogram(
108157
name,
109158
self._instrumentation_info,
@@ -113,8 +162,20 @@ def create_histogram(
113162
)
114163

115164
def create_observable_gauge(
116-
self, name, callback, unit=None, description=None
165+
self, name, callback, unit="", description=""
117166
) -> APIObservableGauge:
167+
if self._check_instrument_id(name, ObservableGauge, unit, description):
168+
# FIXME #2558 go through all views here and check if this
169+
# instrument registration conflict can be fixed. If it can be, do
170+
# not log the following warning.
171+
_logger.warning(
172+
"An instrument with name %s, type %s, unit %s and "
173+
"description %s has been created already.",
174+
name,
175+
APIObservableGauge.__name__,
176+
unit,
177+
description,
178+
)
118179

119180
instrument = ObservableGauge(
120181
name,
@@ -130,8 +191,22 @@ def create_observable_gauge(
130191
return instrument
131192

132193
def create_observable_up_down_counter(
133-
self, name, callback, unit=None, description=None
194+
self, name, callback, unit="", description=""
134195
) -> APIObservableUpDownCounter:
196+
if self._check_instrument_id(
197+
name, ObservableUpDownCounter, unit, description
198+
):
199+
# FIXME #2558 go through all views here and check if this
200+
# instrument registration conflict can be fixed. If it can be, do
201+
# not log the following warning.
202+
_logger.warning(
203+
"An instrument with name %s, type %s, unit %s and "
204+
"description %s has been created already.",
205+
name,
206+
APIObservableUpDownCounter.__name__,
207+
unit,
208+
description,
209+
)
135210

136211
instrument = ObservableUpDownCounter(
137212
name,
@@ -280,6 +355,8 @@ def get_meter(
280355
info = InstrumentationInfo(name, version, schema_url)
281356
with self._meter_lock:
282357
if not self._meters.get(info):
358+
# FIXME #2558 pass SDKConfig object to meter so that the meter
359+
# has access to views.
283360
self._meters[info] = Meter(
284361
info,
285362
self._measurement_consumer,

0 commit comments

Comments
 (0)