Skip to content

Commit 4e02693

Browse files
committed
Reject non-string Info keys and values in info()
Info.info() accepted any key or value except None. A non-string such as {'build_number': 42} was stored, and every later scrape of the registry then failed with AttributeError ("'int' object has no attribute 'replace'"), so one bad call hid all metrics on the endpoint. Check the types where the value is set, as was done for None in #1012, so the caller gets a ValueError and the registry stays scrapeable. The docs already say both keys and values must be strings. Refs #1046 Signed-off-by: Sonica-B <shreyaa.boyane@gmail.com>
1 parent e2f2bf9 commit 4e02693

2 files changed

Lines changed: 17 additions & 0 deletions

File tree

‎prometheus_client/metrics.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,9 @@ def info(self, val: Dict[str, str]) -> None:
749749
self._labelnames, val))
750750
if any(i is None for i in val.values()):
751751
raise ValueError('Label value cannot be None')
752+
for k, v in val.items():
753+
if not isinstance(k, str) or not isinstance(v, str):
754+
raise ValueError(f'Info keys and values must be strings, got {k!r}: {v!r}')
752755
with self._lock:
753756
self._value = dict(val)
754757

‎tests/test_core.py‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
from prometheus_client.decorator import getargspec
1818
from prometheus_client.exposition import generate_latest
1919
from prometheus_client.metrics import _get_use_created
20+
from prometheus_client.openmetrics.exposition import (
21+
generate_latest as openmetrics_generate_latest,
22+
)
2023
from prometheus_client.validation import (
2124
disable_legacy_validation, enable_legacy_validation,
2225
)
@@ -587,6 +590,17 @@ def test_labels(self):
587590
self.labels.labels('a').info({'foo': 'bar'})
588591
self.assertEqual(1, self.registry.get_sample_value('il_info', {'l': 'a', 'foo': 'bar'}))
589592

593+
def test_non_string_keys_and_values(self):
594+
self.info.info({'version': '1.2.3'})
595+
for val in ({'version': 1}, {'temperature': 49.0}, {1: 'x'}, {'tags': ['a', 'b']}):
596+
with self.subTest(val=val):
597+
self.assertRaises(ValueError, self.info.info, val)
598+
self.assertRaises(ValueError, self.labels.labels('a').info, val)
599+
# The rejected calls keep the previous value and the registry stays scrapeable.
600+
self.assertEqual(1, self.registry.get_sample_value('i_info', {'version': '1.2.3'}))
601+
self.assertIn(b'i_info{version="1.2.3"} 1.0', generate_latest(self.registry))
602+
self.assertIn(b'i_info{version="1.2.3"} 1.0', openmetrics_generate_latest(self.registry))
603+
590604

591605
class TestEnum(unittest.TestCase):
592606
def setUp(self):

0 commit comments

Comments
 (0)