diff --git a/prometheus_client/metrics.py b/prometheus_client/metrics.py index b5dfe952..7f9d9c0e 100644 --- a/prometheus_client/metrics.py +++ b/prometheus_client/metrics.py @@ -58,6 +58,13 @@ def _is_observable(self): # * the child of a labelled metric. return not self._labelnames or (self._labelnames and self._labelvalues) + def _raise_if_not_observable(self): + # Functions that mutate the state of the metric, for example incrementing + # a counter, will fail if the metric is not observable, because only if a + # metric is observable will the value be initialized. + if not self._is_observable(): + raise ValueError('%s metric is missing label values' % str(self._type)) + def _is_parent(self): return self._labelnames and not self._labelvalues @@ -250,6 +257,7 @@ def count_exceptions(self, exception=Exception): Increments the counter when an exception of the given type is raised up out of the code. """ + self._raise_if_not_observable() return ExceptionCounter(self, exception) def _child_samples(self): @@ -354,6 +362,7 @@ def track_inprogress(self): Increments the gauge when the code is entered, and decrements when it is exited. """ + self._raise_if_not_observable() return InprogressTracker(self) def time(self): @@ -361,6 +370,7 @@ def time(self): Can be used as a function decorator or context manager. """ + self._raise_if_not_observable() return Timer(self.set) def set_function(self, f): @@ -428,6 +438,7 @@ def time(self): Can be used as a function decorator or context manager. """ + self._raise_if_not_observable() return Timer(self.observe) def _child_samples(self): diff --git a/tests/test_core.py b/tests/test_core.py index 9ad947cd..83aabc0e 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -70,6 +70,14 @@ def test_block_decorator(self): self.assertTrue(raised) self.assertEqual(1, self.registry.get_sample_value('c_total')) + def test_count_exceptions_not_observable(self): + counter = Counter('counter', 'help', labelnames=('label',), registry=self.registry) + + try: + counter.count_exceptions() + except ValueError as e: + self.assertIn('missing label values', str(e)) + class TestGauge(unittest.TestCase): def setUp(self): @@ -150,6 +158,22 @@ def test_time_block_decorator(self): time.sleep(.001) self.assertNotEqual(0, self.registry.get_sample_value('g')) + def test_track_in_progress_not_observable(self): + g = Gauge('test', 'help', labelnames=('label',), registry=self.registry) + + try: + g.track_inprogress() + except ValueError as e: + self.assertIn('missing label values', str(e)) + + def test_timer_not_observable(self): + g = Gauge('test', 'help', labelnames=('label',), registry=self.registry) + + try: + g.time() + except ValueError as e: + self.assertIn('missing label values', str(e)) + class TestSummary(unittest.TestCase): def setUp(self): @@ -230,6 +254,14 @@ def test_block_decorator(self): pass self.assertEqual(1, self.registry.get_sample_value('s_count')) + def test_timer_not_observable(self): + s = Summary('test', 'help', labelnames=('label',), registry=self.registry) + + try: + s.time() + except ValueError as e: + self.assertIn('missing label values', str(e)) + class TestHistogram(unittest.TestCase): def setUp(self):