diff --git a/prometheus_client/metrics.py b/prometheus_client/metrics.py index 28359930..062fecff 100644 --- a/prometheus_client/metrics.py +++ b/prometheus_client/metrics.py @@ -258,6 +258,7 @@ def _metric_init(self): def inc(self, amount=1): """Increment counter by the given amount.""" + self._raise_if_not_observable() if amount < 0: raise ValueError('Counters can only be incremented by non-negative amounts.') self._value.inc(amount) @@ -353,14 +354,17 @@ def _metric_init(self): def inc(self, amount=1): """Increment gauge by the given amount.""" + self._raise_if_not_observable() self._value.inc(amount) def dec(self, amount=1): """Decrement gauge by the given amount.""" + self._raise_if_not_observable() self._value.inc(-amount) def set(self, value): """Set gauge to the given value.""" + self._raise_if_not_observable() self._value.set(float(value)) def set_to_current_time(self): @@ -392,6 +396,8 @@ def set_function(self, f): multiple threads. All other methods of the Gauge become NOOPs. """ + self._raise_if_not_observable() + def samples(self): return (('', {}, float(f())),) @@ -450,6 +456,7 @@ def observe(self, amount): https://prometheus.io/docs/practices/histograms/#count-and-sum-of-observations for details. """ + self._raise_if_not_observable() self._count.inc(1) self._sum.inc(amount) @@ -567,6 +574,7 @@ def observe(self, amount): https://prometheus.io/docs/practices/histograms/#count-and-sum-of-observations for details. """ + self._raise_if_not_observable() self._sum.inc(amount) for i, bound in enumerate(self._upper_bounds): if amount <= bound: diff --git a/tests/test_core.py b/tests/test_core.py index 55c88b76..dbb27033 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -19,6 +19,21 @@ import unittest +def assert_not_observable(fn, *args, **kwargs): + """ + Assert that a function call falls with a ValueError exception containing + 'missing label values' + """ + + try: + fn(*args, **kwargs) + except ValueError as e: + assert 'missing label values' in str(e) + return + + assert False, "Did not raise a 'missing label values' exception" + + class TestCounter(unittest.TestCase): def setUp(self): self.registry = CollectorRegistry() @@ -36,7 +51,6 @@ def test_repr(self): def test_negative_increment_raises(self): self.assertRaises(ValueError, self.counter.inc, -1) - def test_function_decorator(self): @self.counter.count_exceptions(ValueError) @@ -76,18 +90,21 @@ def test_block_decorator(self): def test_count_exceptions_not_observable(self): counter = Counter('counter', 'help', labelnames=('label',), registry=self.registry) + assert_not_observable(counter.count_exceptions) - try: - counter.count_exceptions() - except ValueError as e: - self.assertIn('missing label values', str(e)) + def test_inc_not_observable(self): + """.inc() must fail if the counter is not observable.""" + + counter = Counter('counter', 'help', labelnames=('label',), registry=self.registry) + assert_not_observable(counter.inc) class TestGauge(unittest.TestCase): def setUp(self): self.registry = CollectorRegistry() self.gauge = Gauge('g', 'help', registry=self.registry) - + self.gauge_with_label = Gauge('g2', 'help', labelnames=("label1",), registry=self.registry) + def test_repr(self): self.assertEqual(repr(self.gauge), "prometheus_client.metrics.Gauge(g)") @@ -100,6 +117,21 @@ def test_gauge(self): self.gauge.set(9) self.assertEqual(9, self.registry.get_sample_value('g')) + def test_inc_not_observable(self): + """.inc() must fail if the gauge is not observable.""" + + assert_not_observable(self.gauge_with_label.inc) + + def test_dec_not_observable(self): + """.dec() must fail if the gauge is not observable.""" + + assert_not_observable(self.gauge_with_label.dec) + + def test_set_not_observable(self): + """.set() must fail if the gauge is not observable.""" + + assert_not_observable(self.gauge_with_label.set, 1) + def test_inprogress_function_decorator(self): self.assertEqual(0, self.registry.get_sample_value('g')) @@ -127,6 +159,11 @@ def test_gauge_function(self): x['a'] = None self.assertEqual(1, self.registry.get_sample_value('g')) + def test_set_function_not_observable(self): + """.set_function() must fail if the gauge is not observable.""" + + assert_not_observable(self.gauge_with_label.set_function, lambda: 1) + def test_time_function_decorator(self): self.assertEqual(0, self.registry.get_sample_value('g')) @@ -167,25 +204,18 @@ def test_time_block_decorator(self): 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)) + assert_not_observable(g.track_inprogress) 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)) + assert_not_observable(g.time) class TestSummary(unittest.TestCase): def setUp(self): self.registry = CollectorRegistry() self.summary = Summary('s', 'help', registry=self.registry) + self.summary_with_labels = Summary('s_with_labels', 'help', labelnames=("label1",), registry=self.registry) def test_repr(self): self.assertEqual(repr(self.summary), "prometheus_client.metrics.Summary(s)") @@ -197,6 +227,10 @@ def test_summary(self): self.assertEqual(1, self.registry.get_sample_value('s_count')) self.assertEqual(10, self.registry.get_sample_value('s_sum')) + def test_summary_not_observable(self): + """.observe() must fail if the Summary is not observable.""" + assert_not_observable(self.summary_with_labels.observe, 1) + def test_function_decorator(self): self.assertEqual(0, self.registry.get_sample_value('s_count')) @@ -267,10 +301,7 @@ def test_block_decorator(self): 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)) + assert_not_observable(s.time) class TestHistogram(unittest.TestCase): @@ -315,6 +346,10 @@ def test_histogram(self): self.assertEqual(3, self.registry.get_sample_value('h_count')) self.assertEqual(float("inf"), self.registry.get_sample_value('h_sum')) + def test_histogram_not_observable(self): + """.observe() must fail if the Summary is not observable.""" + assert_not_observable(self.labels.observe, 1) + def test_setting_buckets(self): h = Histogram('h', 'help', registry=None, buckets=[0, 1, 2]) self.assertEqual([0.0, 1.0, 2.0, float("inf")], h._upper_bounds)