Skip to content

Commit 508f06e

Browse files
committed
Check instrument names and units
Fixes open-telemetry#2647
1 parent a4b4c45 commit 508f06e

File tree

5 files changed

+74
-14
lines changed

5 files changed

+74
-14
lines changed

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

+20-4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
from abc import ABC, abstractmethod
1919
from logging import getLogger
20+
from re import ASCII
21+
from re import compile as re_compile
2022
from typing import (
2123
Callable,
2224
Generator,
@@ -42,6 +44,9 @@
4244

4345
_logger = getLogger(__name__)
4446

47+
_name_regex = re_compile(r"[a-zA-Z][-.\w]{0,62}", ASCII)
48+
_unit_regex = re_compile(r"\w{0,63}", ASCII)
49+
4550

4651
class Instrument(ABC):
4752
"""Abstract class that serves as base for all instruments."""
@@ -55,9 +60,21 @@ def __init__(
5560
) -> None:
5661
pass
5762

58-
# FIXME check that the instrument name is valid
59-
# FIXME check that the unit is 63 characters or shorter
60-
# FIXME check that the unit contains only ASCII characters
63+
# pylint: disable=no-self-use
64+
def _check_name_and_unit(self, name: str, unit: str) -> tuple:
65+
"""
66+
Checks the following instrument name and unit for compliance with the
67+
spec.
68+
69+
Returns a tuple of boolean value, the first one will be `True` if the
70+
name is valid, `False` otherwise. The second value will be `True` if
71+
the unit is valid, `False` otherwise.
72+
"""
73+
74+
return (
75+
_name_regex.fullmatch(name) is not None,
76+
_unit_regex.fullmatch(unit) is not None,
77+
)
6178

6279

6380
class _ProxyInstrument(ABC, Generic[InstrumentT]):
@@ -140,7 +157,6 @@ def add(
140157
amount: Union[int, float],
141158
attributes: Optional[Attributes] = None,
142159
) -> None:
143-
# FIXME check that the amount is non negative
144160
pass
145161

146162

opentelemetry-api/tests/metrics/test_instruments.py

+22
Original file line numberDiff line numberDiff line change
@@ -561,3 +561,25 @@ def test_observable_up_down_counter_callback(self):
561561
].default,
562562
Signature.empty,
563563
)
564+
565+
def test_name_regex(self):
566+
567+
instrument = ChildInstrument("name")
568+
569+
self.assertTrue(instrument._check_name_and_unit("a" * 63, "unit")[0])
570+
self.assertTrue(instrument._check_name_and_unit("a.", "unit")[0])
571+
self.assertTrue(instrument._check_name_and_unit("a-", "unit")[0])
572+
self.assertTrue(instrument._check_name_and_unit("a_", "unit")[0])
573+
self.assertFalse(instrument._check_name_and_unit("a" * 64, "unit")[0])
574+
self.assertFalse(instrument._check_name_and_unit("Ñ", "unit")[0])
575+
self.assertFalse(instrument._check_name_and_unit("_a", "unit")[0])
576+
self.assertFalse(instrument._check_name_and_unit("1a", "unit")[0])
577+
self.assertFalse(instrument._check_name_and_unit("", "unit")[0])
578+
579+
def test_unit_regex(self):
580+
581+
instrument = ChildInstrument("name")
582+
583+
self.assertTrue(instrument._check_name_and_unit("name", "a" * 63)[1])
584+
self.assertFalse(instrument._check_name_and_unit("name", "a" * 64)[1])
585+
self.assertFalse(instrument._check_name_and_unit("name", "Ñ")[1])

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

+18-2
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,15 @@ def __init__(
5151
unit: str = "",
5252
description: str = "",
5353
):
54-
self.name = name
54+
# pylint: disable=no-member
55+
is_name_valid, is_unit_valid = self._check_name_and_unit(name, unit)
56+
57+
if not is_name_valid:
58+
raise Exception(f"Invalid name received {name}")
59+
60+
if not is_unit_valid:
61+
raise Exception(f"Invalid unit received {unit}")
62+
self.name = name.lower()
5563
self.unit = unit
5664
self.description = description
5765
self.instrumentation_scope = instrumentation_scope
@@ -69,7 +77,15 @@ def __init__(
6977
unit: str = "",
7078
description: str = "",
7179
):
72-
self.name = name
80+
# pylint: disable=no-member
81+
is_name_valid, is_unit_valid = self._check_name_and_unit(name, unit)
82+
83+
if not is_name_valid:
84+
raise Exception(f"Invalid name received {name}")
85+
86+
if not is_unit_valid:
87+
raise Exception(f"Invalid unit received {unit}")
88+
self.name = name.lower()
7389
self.unit = unit
7490
self.description = description
7591
self.instrumentation_scope = instrumentation_scope

opentelemetry-sdk/tests/metrics/test_aggregation.py

+6-8
Original file line numberDiff line numberDiff line change
@@ -875,7 +875,7 @@ def setUpClass(cls):
875875
def test_counter(self):
876876

877877
aggregation = self.default_aggregation._create_aggregation(
878-
Counter(Mock(), Mock(), Mock())
878+
Counter("name", Mock(), Mock())
879879
)
880880
self.assertIsInstance(aggregation, _SumAggregation)
881881
self.assertTrue(aggregation._instrument_is_monotonic)
@@ -886,7 +886,7 @@ def test_counter(self):
886886
def test_up_down_counter(self):
887887

888888
aggregation = self.default_aggregation._create_aggregation(
889-
UpDownCounter(Mock(), Mock(), Mock())
889+
UpDownCounter("name", Mock(), Mock())
890890
)
891891
self.assertIsInstance(aggregation, _SumAggregation)
892892
self.assertFalse(aggregation._instrument_is_monotonic)
@@ -897,7 +897,7 @@ def test_up_down_counter(self):
897897
def test_observable_counter(self):
898898

899899
aggregation = self.default_aggregation._create_aggregation(
900-
ObservableCounter(Mock(), Mock(), Mock(), callbacks=[Mock()])
900+
ObservableCounter("name", Mock(), Mock(), callbacks=[Mock()])
901901
)
902902
self.assertIsInstance(aggregation, _SumAggregation)
903903
self.assertTrue(aggregation._instrument_is_monotonic)
@@ -909,7 +909,7 @@ def test_observable_counter(self):
909909
def test_observable_up_down_counter(self):
910910

911911
aggregation = self.default_aggregation._create_aggregation(
912-
ObservableUpDownCounter(Mock(), Mock(), Mock(), callbacks=[Mock()])
912+
ObservableUpDownCounter("name", Mock(), Mock(), callbacks=[Mock()])
913913
)
914914
self.assertIsInstance(aggregation, _SumAggregation)
915915
self.assertFalse(aggregation._instrument_is_monotonic)
@@ -922,9 +922,7 @@ def test_histogram(self):
922922

923923
aggregation = self.default_aggregation._create_aggregation(
924924
Histogram(
925-
Mock(),
926-
Mock(),
927-
Mock(),
925+
"name",
928926
Mock(),
929927
Mock(),
930928
)
@@ -935,7 +933,7 @@ def test_observable_gauge(self):
935933

936934
aggregation = self.default_aggregation._create_aggregation(
937935
ObservableGauge(
938-
Mock(),
936+
"name",
939937
Mock(),
940938
Mock(),
941939
callbacks=[Mock()],

opentelemetry-sdk/tests/metrics/test_instrument.py

+8
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@
2828

2929

3030
class TestCounter(TestCase):
31+
def testname(self):
32+
self.assertEqual(Counter("name", Mock(), Mock()).name, "name")
33+
self.assertEqual(Counter("Name", Mock(), Mock()).name, "name")
34+
3135
def test_add(self):
3236
mc = Mock()
3337
counter = Counter("name", Mock(), mc)
@@ -91,6 +95,10 @@ def generator_callback_1():
9195

9296

9397
class TestObservableGauge(TestCase):
98+
def testname(self):
99+
self.assertEqual(ObservableGauge("name", Mock(), Mock()).name, "name")
100+
self.assertEqual(ObservableGauge("Name", Mock(), Mock()).name, "name")
101+
94102
def test_callable_callback_0(self):
95103
observable_gauge = ObservableGauge(
96104
"name", Mock(), Mock(), [callable_callback_0]

0 commit comments

Comments
 (0)