diff --git a/prometheus_client/metrics.py b/prometheus_client/metrics.py index 79c3438c..46cddd55 100644 --- a/prometheus_client/metrics.py +++ b/prometheus_client/metrics.py @@ -749,6 +749,9 @@ def info(self, val: Dict[str, str]) -> None: self._labelnames, val)) if any(i is None for i in val.values()): raise ValueError('Label value cannot be None') + for k, v in val.items(): + if not isinstance(k, str) or not isinstance(v, str): + raise ValueError(f'Info keys and values must be strings, got {k!r}: {v!r}') with self._lock: self._value = dict(val) diff --git a/tests/test_core.py b/tests/test_core.py index 2ed2da90..3e1b12d0 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -17,6 +17,9 @@ from prometheus_client.decorator import getargspec from prometheus_client.exposition import generate_latest from prometheus_client.metrics import _get_use_created +from prometheus_client.openmetrics.exposition import ( + generate_latest as openmetrics_generate_latest, +) from prometheus_client.validation import ( disable_legacy_validation, enable_legacy_validation, ) @@ -587,6 +590,17 @@ def test_labels(self): self.labels.labels('a').info({'foo': 'bar'}) self.assertEqual(1, self.registry.get_sample_value('il_info', {'l': 'a', 'foo': 'bar'})) + def test_non_string_keys_and_values(self): + self.info.info({'version': '1.2.3'}) + for val in ({'version': 1}, {'temperature': 49.0}, {1: 'x'}, {'tags': ['a', 'b']}): + with self.subTest(val=val): + self.assertRaises(ValueError, self.info.info, val) + self.assertRaises(ValueError, self.labels.labels('a').info, val) + # The rejected calls keep the previous value and the registry stays scrapeable. + self.assertEqual(1, self.registry.get_sample_value('i_info', {'version': '1.2.3'})) + self.assertIn(b'i_info{version="1.2.3"} 1.0', generate_latest(self.registry)) + self.assertIn(b'i_info{version="1.2.3"} 1.0', openmetrics_generate_latest(self.registry)) + class TestEnum(unittest.TestCase): def setUp(self):