Comprehensive metrics collection and monitoring library providing counters, gauges, histograms, meters, and timers for Java applications.
—
The reporting framework provides flexible mechanisms for outputting metrics data to various destinations on configurable schedules. It includes built-in reporters for console output, CSV files, and SLF4J logging, along with an extensible base class for custom reporter implementations.
public interface Reporter extends Closeable {
// Tag interface - no methods
// Closeable provides: void close() throws IOException;
}ScheduledReporter is the abstract base class for all scheduled reporters, providing common functionality for periodic metric reporting.
public abstract class ScheduledReporter implements Closeable, Reporter {
// Lifecycle management
public void start(long period, TimeUnit unit);
public void start(long initialDelay, long period, TimeUnit unit);
public void stop();
public void close();
// Manual reporting
public void report();
// Abstract method for concrete implementations
protected abstract void report(
SortedMap<String, Gauge> gauges,
SortedMap<String, Counter> counters,
SortedMap<String, Histogram> histograms,
SortedMap<String, Meter> meters,
SortedMap<String, Timer> timers);
// Protected utility methods for subclasses
protected String getRateUnit();
protected String getDurationUnit();
protected double convertDuration(double duration);
protected double convertRate(double rate);
}ConsoleReporter outputs formatted metrics to a PrintStream (typically System.out), making it ideal for development, debugging, and simple production monitoring.
public class ConsoleReporter extends ScheduledReporter {
// Factory method
public static Builder forRegistry(MetricRegistry registry);
// Builder class for configuration
public static class Builder {
// Output configuration
public Builder outputTo(PrintStream output);
public Builder formattedFor(Locale locale);
public Builder formattedFor(TimeZone timeZone);
public Builder withClock(Clock clock);
// Unit conversion
public Builder convertRatesTo(TimeUnit rateUnit);
public Builder convertDurationsTo(TimeUnit durationUnit);
// Filtering and scheduling
public Builder filter(MetricFilter filter);
public Builder scheduleOn(ScheduledExecutorService executor);
public Builder shutdownExecutorOnStop(boolean shutdownExecutorOnStop);
// Attribute selection
public Builder disabledMetricAttributes(Set<MetricAttribute> disabledMetricAttributes);
// Build final reporter
public ConsoleReporter build();
}
}Basic Console Reporting:
MetricRegistry registry = new MetricRegistry();
// Simple console reporter with default settings
ConsoleReporter reporter = ConsoleReporter.forRegistry(registry)
.convertRatesTo(TimeUnit.SECONDS)
.convertDurationsTo(TimeUnit.MILLISECONDS)
.build();
// Start reporting every 30 seconds
reporter.start(30, TimeUnit.SECONDS);
// Report once immediately
reporter.report();Advanced Console Configuration:
// Customized console reporter
ConsoleReporter customReporter = ConsoleReporter.forRegistry(registry)
.outputTo(System.err) // Output to stderr instead of stdout
.convertRatesTo(TimeUnit.MINUTES) // Show rates per minute
.convertDurationsTo(TimeUnit.MICROSECONDS) // Show durations in microseconds
.filter(MetricFilter.startsWith("http")) // Only report HTTP metrics
.formattedFor(Locale.FRANCE) // French number formatting
.formattedFor(TimeZone.getTimeZone("UTC")) // UTC timestamps
.disabledMetricAttributes(EnumSet.of( // Exclude certain attributes
MetricAttribute.P95,
MetricAttribute.P98))
.build();
// Start with initial delay
customReporter.start(10, 60, TimeUnit.SECONDS); // Wait 10s, then every 60sConsole Output Example:
2023-05-15 14:30:15 ===============================================================
-- Counters --------------------------------------------------------------------
http.requests.count 1247
http.errors.count 23
-- Gauges ----------------------------------------------------------------------
memory.heap.usage 67108864
queue.size 142
-- Histograms ------------------------------------------------------------------
response.sizes
count = 1247
min = 128
max = 65536
mean = 2048.32
stddev = 512.18
median = 1024.00
75% <= 2560.00
95% <= 4096.00
98% <= 8192.00
99% <= 16384.00
99.9% <= 32768.00
-- Meters ----------------------------------------------------------------------
request.rate
count = 1247
mean rate = 4.17 events/second
1-minute rate = 5.23 events/second
5-minute rate = 4.89 events/second
15-minute rate = 4.76 events/second
-- Timers ----------------------------------------------------------------------
request.duration
count = 1247
mean rate = 4.17 calls/second
1-minute rate = 5.23 calls/second
5-minute rate = 4.89 calls/second
15-minute rate = 4.76 calls/second
min = 12.34 milliseconds
max = 987.65 milliseconds
mean = 145.67 milliseconds
stddev = 89.23 milliseconds
median = 123.45 milliseconds
75% <= 189.12 milliseconds
95% <= 345.67 milliseconds
98% <= 456.78 milliseconds
99% <= 567.89 milliseconds
99.9% <= 789.01 millisecondsFiltered Console Reporting:
// Report only error-related metrics
ConsoleReporter errorReporter = ConsoleReporter.forRegistry(registry)
.filter(MetricFilter.contains("error"))
.build();
// Report only specific metric types
MetricFilter timerAndHistogramFilter = new MetricFilter() {
@Override
public boolean matches(String name, Metric metric) {
return metric instanceof Timer || metric instanceof Histogram;
}
};
ConsoleReporter timingReporter = ConsoleReporter.forRegistry(registry)
.filter(timerAndHistogramFilter)
.build();CsvReporter writes metrics to CSV files in a specified directory, creating separate files for each metric. This format is ideal for data analysis, visualization tools, and long-term trend analysis.
public class CsvReporter extends ScheduledReporter {
// Factory method
public static Builder forRegistry(MetricRegistry registry);
// Builder class for configuration
public static class Builder {
// Unit conversion
public Builder convertRatesTo(TimeUnit rateUnit);
public Builder convertDurationsTo(TimeUnit durationUnit);
// Filtering and scheduling
public Builder filter(MetricFilter filter);
public Builder scheduleOn(ScheduledExecutorService executor);
public Builder shutdownExecutorOnStop(boolean shutdownExecutorOnStop);
// CSV-specific configuration
public Builder withClock(Clock clock);
public Builder formatFor(Locale locale);
// Build final reporter - requires output directory
public CsvReporter build(File directory);
}
}public interface CsvFileProvider {
File getFile(File directory, String metricName);
}
public class FixedNameCsvFileProvider implements CsvFileProvider {
public File getFile(File directory, String metricName);
}Basic CSV Reporting:
File csvDirectory = new File("/var/metrics/csv");
csvDirectory.mkdirs();
CsvReporter csvReporter = CsvReporter.forRegistry(registry)
.convertRatesTo(TimeUnit.SECONDS)
.convertDurationsTo(TimeUnit.MILLISECONDS)
.build(csvDirectory);
// Write metrics to CSV files every 5 minutes
csvReporter.start(5, TimeUnit.MINUTES);CSV File Structure:
/var/metrics/csv/
├── http.requests.count.csv
├── http.response.time.csv
├── memory.heap.usage.csv
└── queue.size.csvSample CSV Content (http.response.time.csv):
t,count,max,mean,min,stddev,p50,p75,p95,p98,p99,p999,mean_rate,m1_rate,m5_rate,m15_rate,rate_unit,duration_unit
1684155615000,1247,987.65,145.67,12.34,89.23,123.45,189.12,345.67,456.78,567.89,789.01,4.17,5.23,4.89,4.76,calls/second,milliseconds
1684155915000,1389,1023.45,148.92,11.23,91.56,125.67,192.34,352.11,463.22,574.33,795.44,4.23,5.31,4.91,4.78,calls/second,millisecondsFiltered CSV Reporting:
// Report only performance metrics to CSV
CsvReporter performanceReporter = CsvReporter.forRegistry(registry)
.filter(MetricFilter.endsWith(".time"))
.convertDurationsTo(TimeUnit.MICROSECONDS)
.build(new File("/var/metrics/performance"));
// Report only business metrics to CSV
CsvReporter businessReporter = CsvReporter.forRegistry(registry)
.filter(MetricFilter.startsWith("business"))
.build(new File("/var/metrics/business"));Slf4jReporter outputs metrics through the SLF4J logging framework, integrating metrics reporting with your application's existing logging infrastructure.
public class Slf4jReporter extends ScheduledReporter {
// Factory method
public static Builder forRegistry(MetricRegistry registry);
// Logging levels
public enum LoggingLevel {
TRACE, DEBUG, INFO, WARN, ERROR
}
// Builder class for configuration
public static class Builder {
// Logging configuration
public Builder outputTo(Logger logger);
public Builder outputTo(String loggerName);
public Builder markWith(Marker marker);
public Builder withLoggingLevel(LoggingLevel loggingLevel);
// Unit conversion
public Builder convertRatesTo(TimeUnit rateUnit);
public Builder convertDurationsTo(TimeUnit durationUnit);
// Filtering and scheduling
public Builder filter(MetricFilter filter);
public Builder scheduleOn(ScheduledExecutorService executor);
public Builder shutdownExecutorOnStop(boolean shutdownExecutorOnStop);
// Build final reporter
public Slf4jReporter build();
}
}Basic SLF4J Reporting:
import org.slf4j.LoggerFactory;
Slf4jReporter slf4jReporter = Slf4jReporter.forRegistry(registry)
.outputTo(LoggerFactory.getLogger("metrics"))
.convertRatesTo(TimeUnit.SECONDS)
.convertDurationsTo(TimeUnit.MILLISECONDS)
.build();
// Log metrics every 1 minute at INFO level
slf4jReporter.start(1, TimeUnit.MINUTES);Advanced SLF4J Configuration:
import org.slf4j.MarkerFactory;
Slf4jReporter advancedSlf4jReporter = Slf4jReporter.forRegistry(registry)
.outputTo("com.example.metrics.performance") // Logger name
.markWith(MarkerFactory.getMarker("METRICS")) // Log marker
.withLoggingLevel(Slf4jReporter.LoggingLevel.DEBUG) // Debug level
.filter(MetricFilter.startsWith("critical")) // Only critical metrics
.convertRatesTo(TimeUnit.MINUTES) // Rates per minute
.build();Sample Log Output:
2023-05-15 14:30:15.123 INFO metrics - type=COUNTER, name=http.requests.count, count=1247
2023-05-15 14:30:15.124 INFO metrics - type=GAUGE, name=memory.heap.usage, value=67108864
2023-05-15 14:30:15.125 INFO metrics - type=HISTOGRAM, name=response.sizes, count=1247, min=128, max=65536, mean=2048.32, stddev=512.18, p50=1024.00, p75=2560.00, p95=4096.00, p98=8192.00, p99=16384.00, p999=32768.00
2023-05-15 14:30:15.126 INFO metrics - type=METER, name=request.rate, count=1247, mean_rate=4.17, m1_rate=5.23, m5_rate=4.89, m15_rate=4.76, rate_unit=events/second
2023-05-15 14:30:15.127 INFO metrics - type=TIMER, name=request.duration, count=1247, min=12.34, max=987.65, mean=145.67, stddev=89.23, p50=123.45, p75=189.12, p95=345.67, p98=456.78, p99=567.89, p999=789.01, mean_rate=4.17, m1_rate=5.23, m5_rate=4.89, m15_rate=4.76, rate_unit=calls/second, duration_unit=millisecondsMultiple Logger Configuration:
// Performance metrics to performance logger
Slf4jReporter performanceReporter = Slf4jReporter.forRegistry(registry)
.outputTo("performance")
.filter(MetricFilter.contains("time"))
.withLoggingLevel(Slf4jReporter.LoggingLevel.INFO)
.build();
// Error metrics to error logger
Slf4jReporter errorReporter = Slf4jReporter.forRegistry(registry)
.outputTo("errors")
.filter(MetricFilter.contains("error"))
.withLoggingLevel(Slf4jReporter.LoggingLevel.WARN)
.build();You can run multiple reporters simultaneously to output metrics to different destinations:
MetricRegistry registry = new MetricRegistry();
// Console reporter for development
ConsoleReporter consoleReporter = ConsoleReporter.forRegistry(registry)
.convertRatesTo(TimeUnit.SECONDS)
.convertDurationsTo(TimeUnit.MILLISECONDS)
.build();
// CSV reporter for analysis
CsvReporter csvReporter = CsvReporter.forRegistry(registry)
.build(new File("/var/metrics"));
// SLF4J reporter for production logging
Slf4jReporter slf4jReporter = Slf4jReporter.forRegistry(registry)
.outputTo("metrics")
.build();
// Start all reporters with different schedules
consoleReporter.start(30, TimeUnit.SECONDS); // Console every 30s
csvReporter.start(5, TimeUnit.MINUTES); // CSV every 5 minutes
slf4jReporter.start(1, TimeUnit.MINUTES); // Logs every 1 minute
// Proper shutdown
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
consoleReporter.stop();
csvReporter.stop();
slf4jReporter.stop();
}));You can create custom reporters by extending ScheduledReporter:
public class CustomReporter extends ScheduledReporter {
protected CustomReporter(MetricRegistry registry,
String name,
MetricFilter filter,
TimeUnit rateUnit,
TimeUnit durationUnit) {
super(registry, name, filter, rateUnit, durationUnit);
}
@Override
protected void report(SortedMap<String, Gauge> gauges,
SortedMap<String, Counter> counters,
SortedMap<String, Histogram> histograms,
SortedMap<String, Meter> meters,
SortedMap<String, Timer> timers) {
// Custom reporting logic
for (Map.Entry<String, Counter> entry : counters.entrySet()) {
String name = entry.getKey();
Counter counter = entry.getValue();
sendToCustomDestination(name, "counter", counter.getCount());
}
for (Map.Entry<String, Histogram> entry : histograms.entrySet()) {
String name = entry.getKey();
Histogram histogram = entry.getValue();
Snapshot snapshot = histogram.getSnapshot();
sendToCustomDestination(name, "histogram", Map.of(
"count", histogram.getCount(),
"mean", snapshot.getMean(),
"p95", snapshot.get95thPercentile(),
"p99", snapshot.get99thPercentile()
));
}
// Handle other metric types...
}
private void sendToCustomDestination(String name, String type, Object value) {
// Custom implementation: send to database, web service, message queue, etc.
}
public static Builder forRegistry(MetricRegistry registry) {
return new Builder(registry);
}
public static class Builder {
private final MetricRegistry registry;
private MetricFilter filter = MetricFilter.ALL;
private TimeUnit rateUnit = TimeUnit.SECONDS;
private TimeUnit durationUnit = TimeUnit.MILLISECONDS;
private Builder(MetricRegistry registry) {
this.registry = registry;
}
public Builder filter(MetricFilter filter) {
this.filter = filter;
return this;
}
public Builder convertRatesTo(TimeUnit rateUnit) {
this.rateUnit = rateUnit;
return this;
}
public Builder convertDurationsTo(TimeUnit durationUnit) {
this.durationUnit = durationUnit;
return this;
}
public CustomReporter build() {
return new CustomReporter(registry, "custom-reporter", filter, rateUnit, durationUnit);
}
}
}CustomReporter customReporter = CustomReporter.forRegistry(registry)
.filter(MetricFilter.startsWith("api"))
.convertRatesTo(TimeUnit.MINUTES)
.build();
customReporter.start(2, TimeUnit.MINUTES);All reporters support filtering to control which metrics are reported:
// Predefined filters
MetricFilter all = MetricFilter.ALL;
MetricFilter httpMetrics = MetricFilter.startsWith("http");
MetricFilter errorMetrics = MetricFilter.contains("error");
MetricFilter apiMetrics = MetricFilter.endsWith("api");
// Custom filter implementation
MetricFilter customFilter = new MetricFilter() {
@Override
public boolean matches(String name, Metric metric) {
// Only report timers and histograms with high activity
if (metric instanceof Timer) {
return ((Timer) metric).getCount() > 100;
}
if (metric instanceof Histogram) {
return ((Histogram) metric).getCount() > 50;
}
return false;
}
};
// Composite filters
MetricFilter compositeFilter = new MetricFilter() {
@Override
public boolean matches(String name, Metric metric) {
return MetricFilter.startsWith("critical").matches(name, metric) ||
MetricFilter.contains("error").matches(name, metric);
}
};shutdownExecutorOnStop(true) unless managing executors externallyreport() calls for testing reporter outputInstall with Tessl CLI
npx tessl i tessl/maven-io-dropwizard-metrics--metrics-core