CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-dropwizard-metrics--metrics-core

Comprehensive metrics collection and monitoring library providing counters, gauges, histograms, meters, and timers for Java applications.

Pending
Overview
Eval results
Files

reporting.mddocs/

Reporting Framework

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.

Reporter Interfaces

Base Reporter Interface

public interface Reporter extends Closeable {
    // Tag interface - no methods
    // Closeable provides: void close() throws IOException;
}

ScheduledReporter Base Class

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

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();
    }
}

Usage Examples

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 60s

Console 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 milliseconds

Filtered 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

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);
    }
}

CSV File Provider Interface

public interface CsvFileProvider {
    File getFile(File directory, String metricName);
}

public class FixedNameCsvFileProvider implements CsvFileProvider {
    public File getFile(File directory, String metricName);
}

Usage Examples

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.csv

Sample 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,milliseconds

Filtered 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

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();
    }
}

Usage Examples

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=milliseconds

Multiple 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();

Multiple Reporter Setup

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();
}));

Custom Reporter Implementation

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);
        }
    }
}

Usage of Custom Reporter:

CustomReporter customReporter = CustomReporter.forRegistry(registry)
    .filter(MetricFilter.startsWith("api"))
    .convertRatesTo(TimeUnit.MINUTES)
    .build();

customReporter.start(2, TimeUnit.MINUTES);

Metric Filtering

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);
    }
};

Best Practices

Reporter Configuration

  • Use console reporters for development and debugging
  • Use CSV reporters for detailed analysis and data visualization
  • Use SLF4J reporters for production logging and integration with log aggregation systems
  • Configure appropriate reporting intervals based on metric update frequency and monitoring requirements

Performance Considerations

  • Reporting operations can be expensive for large numbers of metrics
  • Use filtering to limit the number of metrics reported
  • Consider separate reporters for different metric categories with different reporting intervals
  • Monitor reporter performance and adjust intervals if necessary

Resource Management

  • Always stop reporters during application shutdown
  • Use shutdownExecutorOnStop(true) unless managing executors externally
  • Monitor disk space when using CSV reporters with high-frequency reporting
  • Be aware of log file rotation when using SLF4J reporters

Production Deployment

  • Start with longer reporting intervals in production and adjust based on monitoring needs
  • Use multiple reporters to send different metrics to different destinations
  • Implement custom reporters for integration with specific monitoring systems
  • Monitor reporter error logs and implement fallback strategies for critical metrics

Testing Strategies

  • Use manual report() calls for testing reporter output
  • Test filtering logic with known metric sets
  • Verify unit conversions in reporter output
  • Test reporter lifecycle (start, stop, restart) in integration tests

Install with Tessl CLI

npx tessl i tessl/maven-io-dropwizard-metrics--metrics-core

docs

advanced-gauges.md

core-metrics.md

index.md

reporting.md

reservoirs-sampling.md

utilities.md

tile.json