Skip to content

Commit 292a1bc

Browse files
committed
Let DropwizardExports users deal with snapshot validation errors
In the current form, if e.g. a Counter contains invalid data the entire `collect` calls fails with InvalidArgumentException. In large applications, it may be preferable to gracefully degrade the available metrics and report the failure via e.g. logging. This change allows that by letting the user add a exception handler. This allows the user to inspect the exception, and the metric name it was associated with, and decide if the exception should be suppressed or not. Metrics for which exceptions were suppressed will be absent from the collection. Signed-off-by: Anders Månsson <[email protected]>
1 parent cf29905 commit 292a1bc

File tree

5 files changed

+223
-54
lines changed

5 files changed

+223
-54
lines changed

prometheus-metrics-instrumentation-dropwizard/src/main/java/io/prometheus/metrics/instrumentation/dropwizard/DropwizardExports.java

Lines changed: 55 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import com.codahale.metrics.MetricRegistry;
1010
import com.codahale.metrics.Snapshot;
1111
import com.codahale.metrics.Timer;
12+
import io.prometheus.metrics.instrumentation.dropwizard5.InvalidMetricHandler;
1213
import io.prometheus.metrics.instrumentation.dropwizard5.labels.CustomLabelMapper;
1314
import io.prometheus.metrics.model.registry.MultiCollector;
1415
import io.prometheus.metrics.model.registry.PrometheusRegistry;
@@ -21,8 +22,10 @@
2122
import io.prometheus.metrics.model.snapshots.Quantiles;
2223
import io.prometheus.metrics.model.snapshots.SummarySnapshot;
2324
import java.util.Collections;
25+
import java.util.Map;
2426
import java.util.Optional;
2527
import java.util.concurrent.TimeUnit;
28+
import java.util.function.BiFunction;
2629
import java.util.logging.Level;
2730
import java.util.logging.Logger;
2831

@@ -32,6 +35,7 @@ public class DropwizardExports implements MultiCollector {
3235
private final MetricRegistry registry;
3336
private final MetricFilter metricFilter;
3437
private final Optional<CustomLabelMapper> labelMapper;
38+
private final InvalidMetricHandler invalidMetricHandler;
3539

3640
/**
3741
* Creates a new DropwizardExports and {@link MetricFilter#ALL}.
@@ -43,6 +47,7 @@ public DropwizardExports(MetricRegistry registry) {
4347
this.registry = registry;
4448
this.metricFilter = MetricFilter.ALL;
4549
this.labelMapper = Optional.empty();
50+
this.invalidMetricHandler = InvalidMetricHandler.ALWAYS_THROW;
4651
}
4752

4853
/**
@@ -55,6 +60,7 @@ public DropwizardExports(MetricRegistry registry, MetricFilter metricFilter) {
5560
this.registry = registry;
5661
this.metricFilter = metricFilter;
5762
this.labelMapper = Optional.empty();
63+
this.invalidMetricHandler = InvalidMetricHandler.ALWAYS_THROW;
5864
}
5965

6066
/**
@@ -67,6 +73,23 @@ public DropwizardExports(
6773
this.registry = registry;
6874
this.metricFilter = metricFilter;
6975
this.labelMapper = Optional.ofNullable(labelMapper);
76+
this.invalidMetricHandler = InvalidMetricHandler.ALWAYS_THROW;
77+
}
78+
79+
/**
80+
* @param registry a metric registry to export in prometheus.
81+
* @param metricFilter a custom metric filter.
82+
* @param labelMapper a labelMapper to use to map labels.
83+
*/
84+
public DropwizardExports(
85+
MetricRegistry registry,
86+
MetricFilter metricFilter,
87+
CustomLabelMapper labelMapper,
88+
InvalidMetricHandler invalidMetricHandler) {
89+
this.registry = registry;
90+
this.metricFilter = metricFilter;
91+
this.labelMapper = Optional.ofNullable(labelMapper);
92+
this.invalidMetricHandler = invalidMetricHandler;
7093
}
7194

7295
private static String getHelpMessage(String metricName, Metric metric) {
@@ -194,34 +217,33 @@ MetricSnapshot fromMeter(String dropwizardName, Meter meter) {
194217
@Override
195218
public MetricSnapshots collect() {
196219
MetricSnapshots.Builder metricSnapshots = MetricSnapshots.builder();
197-
198-
registry
199-
.getGauges(metricFilter)
200-
.forEach(
201-
(name, gauge) -> {
202-
MetricSnapshot snapshot = fromGauge(name, gauge);
203-
if (snapshot != null) {
204-
metricSnapshots.metricSnapshot(snapshot);
205-
}
206-
});
207-
208-
registry
209-
.getCounters(metricFilter)
210-
.forEach((name, counter) -> metricSnapshots.metricSnapshot(fromCounter(name, counter)));
211-
registry
212-
.getHistograms(metricFilter)
213-
.forEach(
214-
(name, histogram) -> metricSnapshots.metricSnapshot(fromHistogram(name, histogram)));
215-
registry
216-
.getTimers(metricFilter)
217-
.forEach((name, timer) -> metricSnapshots.metricSnapshot(fromTimer(name, timer)));
218-
registry
219-
.getMeters(metricFilter)
220-
.forEach((name, meter) -> metricSnapshots.metricSnapshot(fromMeter(name, meter)));
221-
220+
collectMetricKind(metricSnapshots, registry.getGauges(metricFilter), this::fromGauge);
221+
collectMetricKind(metricSnapshots, registry.getCounters(metricFilter), this::fromCounter);
222+
collectMetricKind(metricSnapshots, registry.getHistograms(metricFilter), this::fromHistogram);
223+
collectMetricKind(metricSnapshots, registry.getTimers(metricFilter), this::fromTimer);
224+
collectMetricKind(metricSnapshots, registry.getMeters(metricFilter), this::fromMeter);
222225
return metricSnapshots.build();
223226
}
224227

228+
private <T> void collectMetricKind(
229+
MetricSnapshots.Builder builder,
230+
Map<String, T> metric,
231+
BiFunction<String, T, MetricSnapshot> toSnapshot) {
232+
for (Map.Entry<String, T> entry : metric.entrySet()) {
233+
String metricName = entry.getKey();
234+
try {
235+
MetricSnapshot snapshot = toSnapshot.apply(metricName, entry.getValue());
236+
if (snapshot != null) {
237+
builder.metricSnapshot(snapshot);
238+
}
239+
} catch (Exception e) {
240+
if (!invalidMetricHandler.suppressException(metricName, e)) {
241+
throw e;
242+
}
243+
}
244+
}
245+
}
246+
225247
public static Builder builder() {
226248
return new Builder();
227249
}
@@ -231,9 +253,11 @@ public static class Builder {
231253
private MetricRegistry registry;
232254
private MetricFilter metricFilter;
233255
private CustomLabelMapper labelMapper;
256+
private InvalidMetricHandler invalidMetricHandler;
234257

235258
private Builder() {
236259
this.metricFilter = MetricFilter.ALL;
260+
this.invalidMetricHandler = InvalidMetricHandler.ALWAYS_THROW;
237261
}
238262

239263
public Builder dropwizardRegistry(MetricRegistry registry) {
@@ -251,15 +275,16 @@ public Builder customLabelMapper(CustomLabelMapper labelMapper) {
251275
return this;
252276
}
253277

278+
public Builder invalidMetricHandler(InvalidMetricHandler invalidMetricHandler) {
279+
this.invalidMetricHandler = invalidMetricHandler;
280+
return this;
281+
}
282+
254283
DropwizardExports build() {
255284
if (registry == null) {
256285
throw new IllegalArgumentException("MetricRegistry must be set");
257286
}
258-
if (labelMapper == null) {
259-
return new DropwizardExports(registry, metricFilter);
260-
} else {
261-
return new DropwizardExports(registry, metricFilter, labelMapper);
262-
}
287+
return new DropwizardExports(registry, metricFilter, labelMapper, invalidMetricHandler);
263288
}
264289

265290
public void register() {

prometheus-metrics-instrumentation-dropwizard/src/test/java/io/prometheus/metrics/instrumentation/dropwizard/DropwizardExportsTest.java

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import com.codahale.metrics.*;
88
import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter;
9+
import io.prometheus.metrics.instrumentation.dropwizard5.InvalidMetricHandler;
910
import io.prometheus.metrics.model.registry.PrometheusRegistry;
1011
import io.prometheus.metrics.model.snapshots.SummarySnapshot;
1112
import java.io.ByteArrayOutputStream;
@@ -278,6 +279,56 @@ public void testThatMetricHelpUsesOriginalDropwizardName() {
278279
assertThat(convertToOpenMetricsFormat()).isEqualTo(expected);
279280
}
280281

282+
@Test
283+
void responseWhenRegistryIsEmpty() {
284+
var registry = new PrometheusRegistry();
285+
registry.register(DropwizardExports.builder().dropwizardRegistry(metricRegistry).build());
286+
assertThat(convertToOpenMetricsFormat(registry))
287+
.isEqualTo(
288+
"""
289+
# EOF
290+
""");
291+
}
292+
293+
@Test
294+
void collectInvalidMetricFails() {
295+
metricRegistry.counter("my.application.namedCounter1").inc(-10);
296+
metricRegistry.counter("my.application.namedCounter2").inc(10);
297+
var registry = new PrometheusRegistry();
298+
DropwizardExports.builder().dropwizardRegistry(metricRegistry).register(registry);
299+
assertThatThrownBy(() -> convertToOpenMetricsFormat(registry))
300+
.isInstanceOf(IllegalArgumentException.class);
301+
}
302+
303+
@Test
304+
void collectInvalidMetricPassesWhenExceptionIsIgnored() {
305+
metricRegistry.counter("my.application.namedCounter1").inc(-10);
306+
metricRegistry.counter("my.application.namedCounter2").inc(10);
307+
var registry = new PrometheusRegistry();
308+
309+
final StringBuilder buf = new StringBuilder();
310+
InvalidMetricHandler invalidMetricHandler =
311+
(name, exc) -> {
312+
buf.append("%s: %s%n".formatted(name, exc.getMessage()));
313+
return true;
314+
};
315+
316+
DropwizardExports.builder()
317+
.dropwizardRegistry(metricRegistry)
318+
.invalidMetricHandler(invalidMetricHandler)
319+
.register(registry);
320+
assertThat(convertToOpenMetricsFormat(registry))
321+
.isEqualTo(
322+
"""
323+
# TYPE my_application_namedCounter2 counter
324+
# HELP my_application_namedCounter2 Generated from Dropwizard metric import (metric=my.application.namedCounter2, type=com.codahale.metrics.Counter)
325+
my_application_namedCounter2_total 10.0
326+
# EOF
327+
""");
328+
assertThat(buf.toString())
329+
.contains("my.application.namedCounter1: -10.0: counters cannot have a negative value");
330+
}
331+
281332
private static class ExampleDoubleGauge implements Gauge<Double> {
282333
@Override
283334
public Double getValue() {

prometheus-metrics-instrumentation-dropwizard5/src/main/java/io/prometheus/metrics/instrumentation/dropwizard5/DropwizardExports.java

Lines changed: 53 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@
2424
import java.util.Collections;
2525
import java.util.Map;
2626
import java.util.Optional;
27-
import java.util.Set;
2827
import java.util.concurrent.TimeUnit;
28+
import java.util.function.BiFunction;
2929
import java.util.logging.Level;
3030
import java.util.logging.Logger;
3131

@@ -35,6 +35,7 @@ public class DropwizardExports implements MultiCollector {
3535
private final MetricRegistry registry;
3636
private final MetricFilter metricFilter;
3737
private final Optional<CustomLabelMapper> labelMapper;
38+
private final InvalidMetricHandler invalidMetricHandler;
3839

3940
/**
4041
* Creates a new DropwizardExports and {@link MetricFilter#ALL}.
@@ -46,6 +47,7 @@ public DropwizardExports(MetricRegistry registry) {
4647
this.registry = registry;
4748
this.metricFilter = MetricFilter.ALL;
4849
this.labelMapper = Optional.empty();
50+
this.invalidMetricHandler = InvalidMetricHandler.ALWAYS_THROW;
4951
}
5052

5153
/**
@@ -58,6 +60,7 @@ public DropwizardExports(MetricRegistry registry, MetricFilter metricFilter) {
5860
this.registry = registry;
5961
this.metricFilter = metricFilter;
6062
this.labelMapper = Optional.empty();
63+
this.invalidMetricHandler = InvalidMetricHandler.ALWAYS_THROW;
6164
}
6265

6366
/**
@@ -70,6 +73,23 @@ public DropwizardExports(
7073
this.registry = registry;
7174
this.metricFilter = metricFilter;
7275
this.labelMapper = Optional.ofNullable(labelMapper);
76+
this.invalidMetricHandler = InvalidMetricHandler.ALWAYS_THROW;
77+
}
78+
79+
/**
80+
* @param registry a metric registry to export in prometheus.
81+
* @param metricFilter a custom metric filter.
82+
* @param labelMapper a labelMapper to use to map labels.
83+
*/
84+
public DropwizardExports(
85+
MetricRegistry registry,
86+
MetricFilter metricFilter,
87+
CustomLabelMapper labelMapper,
88+
InvalidMetricHandler invalidMetricHandler) {
89+
this.registry = registry;
90+
this.metricFilter = metricFilter;
91+
this.labelMapper = Optional.ofNullable(labelMapper);
92+
this.invalidMetricHandler = invalidMetricHandler;
7393
}
7494

7595
private static String getHelpMessage(String metricName, Metric metric) {
@@ -197,27 +217,33 @@ MetricSnapshot fromMeter(String dropwizardName, Meter meter) {
197217
@Override
198218
public MetricSnapshots collect() {
199219
MetricSnapshots.Builder metricSnapshots = MetricSnapshots.builder();
200-
@SuppressWarnings("rawtypes")
201-
Set<Map.Entry<MetricName, Gauge>> entries = registry.getGauges(metricFilter).entrySet();
202-
for (@SuppressWarnings("rawtypes") Map.Entry<MetricName, Gauge> entry : entries) {
203-
Optional.ofNullable(fromGauge(entry.getKey().getKey(), entry.getValue()))
204-
.ifPresent(metricSnapshots::metricSnapshot);
205-
}
206-
for (Map.Entry<MetricName, Counter> entry : registry.getCounters(metricFilter).entrySet()) {
207-
metricSnapshots.metricSnapshot(fromCounter(entry.getKey().getKey(), entry.getValue()));
208-
}
209-
for (Map.Entry<MetricName, Histogram> entry : registry.getHistograms(metricFilter).entrySet()) {
210-
metricSnapshots.metricSnapshot(fromHistogram(entry.getKey().getKey(), entry.getValue()));
211-
}
212-
for (Map.Entry<MetricName, Timer> entry : registry.getTimers(metricFilter).entrySet()) {
213-
metricSnapshots.metricSnapshot(fromTimer(entry.getKey().getKey(), entry.getValue()));
214-
}
215-
for (Map.Entry<MetricName, Meter> entry : registry.getMeters(metricFilter).entrySet()) {
216-
metricSnapshots.metricSnapshot(fromMeter(entry.getKey().getKey(), entry.getValue()));
217-
}
220+
collectMetricKind(metricSnapshots, registry.getGauges(metricFilter), this::fromGauge);
221+
collectMetricKind(metricSnapshots, registry.getCounters(metricFilter), this::fromCounter);
222+
collectMetricKind(metricSnapshots, registry.getHistograms(metricFilter), this::fromHistogram);
223+
collectMetricKind(metricSnapshots, registry.getTimers(metricFilter), this::fromTimer);
224+
collectMetricKind(metricSnapshots, registry.getMeters(metricFilter), this::fromMeter);
218225
return metricSnapshots.build();
219226
}
220227

228+
private <T> void collectMetricKind(
229+
MetricSnapshots.Builder builder,
230+
Map<MetricName, T> metric,
231+
BiFunction<String, T, MetricSnapshot> toSnapshot) {
232+
for (Map.Entry<MetricName, T> entry : metric.entrySet()) {
233+
String metricName = entry.getKey().getKey();
234+
try {
235+
MetricSnapshot snapshot = toSnapshot.apply(metricName, entry.getValue());
236+
if (snapshot != null) {
237+
builder.metricSnapshot(snapshot);
238+
}
239+
} catch (Exception e) {
240+
if (!invalidMetricHandler.suppressException(metricName, e)) {
241+
throw e;
242+
}
243+
}
244+
}
245+
}
246+
221247
public static Builder builder() {
222248
return new Builder();
223249
}
@@ -227,9 +253,11 @@ public static class Builder {
227253
private MetricRegistry registry;
228254
private MetricFilter metricFilter;
229255
private CustomLabelMapper labelMapper;
256+
private InvalidMetricHandler invalidMetricHandler;
230257

231258
private Builder() {
232259
this.metricFilter = MetricFilter.ALL;
260+
this.invalidMetricHandler = InvalidMetricHandler.ALWAYS_THROW;
233261
}
234262

235263
public Builder dropwizardRegistry(MetricRegistry registry) {
@@ -247,15 +275,16 @@ public Builder customLabelMapper(CustomLabelMapper labelMapper) {
247275
return this;
248276
}
249277

278+
public Builder invalidMetricHandler(InvalidMetricHandler invalidMetricHandler) {
279+
this.invalidMetricHandler = invalidMetricHandler;
280+
return this;
281+
}
282+
250283
DropwizardExports build() {
251284
if (registry == null) {
252285
throw new IllegalArgumentException("MetricRegistry must be set");
253286
}
254-
if (labelMapper == null) {
255-
return new DropwizardExports(registry, metricFilter);
256-
} else {
257-
return new DropwizardExports(registry, metricFilter, labelMapper);
258-
}
287+
return new DropwizardExports(registry, metricFilter, labelMapper, invalidMetricHandler);
259288
}
260289

261290
public void register() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package io.prometheus.metrics.instrumentation.dropwizard5;
2+
3+
@FunctionalInterface
4+
public interface InvalidMetricHandler {
5+
InvalidMetricHandler ALWAYS_THROW = (metricName, exc) -> false;
6+
7+
/**
8+
* @param metricName the name of the metric that was collected.
9+
* @param exc The exception that was thrown when producing the metric snapshot.
10+
* @return true if the exception should be suppressed.
11+
*/
12+
boolean suppressException(String metricName, Exception exc);
13+
}

0 commit comments

Comments
 (0)