Skip to content

Commit afd5b3a

Browse files
committed
Fix operator precedence allowing exemplars on any metric type
_is_valid_exemplar_metric() guarded histograms with if metric.type in ('histogram') and sample.name.endswith('_bucket') or sample.name == metric.name: Because 'and' binds tighter than 'or', this parsed as (metric.type in ('histogram') and sample.name.endswith('_bucket')) or (sample.name == metric.name) so the trailing 'sample.name == metric.name' clause fired for every metric type. As a result the OpenMetrics writer emitted exemplars on gauges, info, stateset, summary and untyped metrics whenever a sample name equalled the metric name -- output that the library's own OpenMetrics parser rejects (only histogram/gaugehistogram buckets, counter _total, and native histograms may carry exemplars). Group the histogram condition correctly so the same-name clause (which exists to allow native-histogram exemplars) only applies to histograms. While here, replace the 'metric.type in (...)' single-string membership checks with '==' -- they were doing substring matching, not the intended equality. Add test_gauge_exemplar, which asserts a gauge sample carrying an exemplar raises ValueError, matching the existing untyped/non-bucket exemplar tests. Signed-off-by: Sean Kim <skim8705@gmail.com>
1 parent a96f6f4 commit afd5b3a

2 files changed

Lines changed: 17 additions & 2 deletions

File tree

prometheus_client/openmetrics/exposition.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@
2525
def _is_valid_exemplar_metric(metric, sample):
2626
if metric.type == 'counter' and sample.name.endswith('_total'):
2727
return True
28-
if metric.type in ('gaugehistogram') and sample.name.endswith('_bucket'):
28+
if metric.type == 'gaugehistogram' and sample.name.endswith('_bucket'):
2929
return True
30-
if metric.type in ('histogram') and sample.name.endswith('_bucket') or sample.name == metric.name:
30+
if metric.type == 'histogram' and (sample.name.endswith('_bucket') or sample.name == metric.name):
3131
return True
3232
return False
3333

tests/openmetrics/test_exposition.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,21 @@ def collect(self):
347347
with self.assertRaises(ValueError):
348348
generate_latest(self.registry)
349349

350+
def test_gauge_exemplar(self) -> None:
351+
class MyCollector:
352+
def collect(self):
353+
metric = Metric("gg", "A gauge", 'gauge')
354+
# A sample whose name equals the metric name must not be
355+
# treated as exemplar-eligible just because it matches;
356+
# only histogram/gaugehistogram buckets, counter _total, and
357+
# native histograms may carry exemplars.
358+
metric.add_sample("gg", {}, 1, None, Exemplar({'a': 'b'}, 0.5))
359+
yield metric
360+
361+
self.registry.register(MyCollector())
362+
with self.assertRaises(ValueError):
363+
generate_latest(self.registry)
364+
350365
def test_gaugehistogram(self) -> None:
351366
self.custom_collector(
352367
GaugeHistogramMetricFamily('gh', 'help', buckets=[('1.0', 4), ('+Inf', (5))], gsum_value=7))

0 commit comments

Comments
 (0)