0
# Reporting Framework
1
2
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.
3
4
## Reporter Interfaces
5
6
### Base Reporter Interface
7
8
```java { .api }
9
public interface Reporter extends Closeable {
10
// Tag interface - no methods
11
// Closeable provides: void close() throws IOException;
12
}
13
```
14
15
### ScheduledReporter Base Class
16
17
`ScheduledReporter` is the abstract base class for all scheduled reporters, providing common functionality for periodic metric reporting.
18
19
```java { .api }
20
public abstract class ScheduledReporter implements Closeable, Reporter {
21
// Lifecycle management
22
public void start(long period, TimeUnit unit);
23
public void start(long initialDelay, long period, TimeUnit unit);
24
public void stop();
25
public void close();
26
27
// Manual reporting
28
public void report();
29
30
// Abstract method for concrete implementations
31
protected abstract void report(
32
SortedMap<String, Gauge> gauges,
33
SortedMap<String, Counter> counters,
34
SortedMap<String, Histogram> histograms,
35
SortedMap<String, Meter> meters,
36
SortedMap<String, Timer> timers);
37
38
// Protected utility methods for subclasses
39
protected String getRateUnit();
40
protected String getDurationUnit();
41
protected double convertDuration(double duration);
42
protected double convertRate(double rate);
43
}
44
```
45
46
## ConsoleReporter
47
48
`ConsoleReporter` outputs formatted metrics to a `PrintStream` (typically `System.out`), making it ideal for development, debugging, and simple production monitoring.
49
50
```java { .api }
51
public class ConsoleReporter extends ScheduledReporter {
52
// Factory method
53
public static Builder forRegistry(MetricRegistry registry);
54
55
// Builder class for configuration
56
public static class Builder {
57
// Output configuration
58
public Builder outputTo(PrintStream output);
59
public Builder formattedFor(Locale locale);
60
public Builder formattedFor(TimeZone timeZone);
61
public Builder withClock(Clock clock);
62
63
// Unit conversion
64
public Builder convertRatesTo(TimeUnit rateUnit);
65
public Builder convertDurationsTo(TimeUnit durationUnit);
66
67
// Filtering and scheduling
68
public Builder filter(MetricFilter filter);
69
public Builder scheduleOn(ScheduledExecutorService executor);
70
public Builder shutdownExecutorOnStop(boolean shutdownExecutorOnStop);
71
72
// Attribute selection
73
public Builder disabledMetricAttributes(Set<MetricAttribute> disabledMetricAttributes);
74
75
// Build final reporter
76
public ConsoleReporter build();
77
}
78
}
79
```
80
81
### Usage Examples
82
83
**Basic Console Reporting:**
84
```java
85
MetricRegistry registry = new MetricRegistry();
86
87
// Simple console reporter with default settings
88
ConsoleReporter reporter = ConsoleReporter.forRegistry(registry)
89
.convertRatesTo(TimeUnit.SECONDS)
90
.convertDurationsTo(TimeUnit.MILLISECONDS)
91
.build();
92
93
// Start reporting every 30 seconds
94
reporter.start(30, TimeUnit.SECONDS);
95
96
// Report once immediately
97
reporter.report();
98
```
99
100
**Advanced Console Configuration:**
101
```java
102
// Customized console reporter
103
ConsoleReporter customReporter = ConsoleReporter.forRegistry(registry)
104
.outputTo(System.err) // Output to stderr instead of stdout
105
.convertRatesTo(TimeUnit.MINUTES) // Show rates per minute
106
.convertDurationsTo(TimeUnit.MICROSECONDS) // Show durations in microseconds
107
.filter(MetricFilter.startsWith("http")) // Only report HTTP metrics
108
.formattedFor(Locale.FRANCE) // French number formatting
109
.formattedFor(TimeZone.getTimeZone("UTC")) // UTC timestamps
110
.disabledMetricAttributes(EnumSet.of( // Exclude certain attributes
111
MetricAttribute.P95,
112
MetricAttribute.P98))
113
.build();
114
115
// Start with initial delay
116
customReporter.start(10, 60, TimeUnit.SECONDS); // Wait 10s, then every 60s
117
```
118
119
**Console Output Example:**
120
```
121
2023-05-15 14:30:15 ===============================================================
122
123
-- Counters --------------------------------------------------------------------
124
http.requests.count 1247
125
http.errors.count 23
126
127
-- Gauges ----------------------------------------------------------------------
128
memory.heap.usage 67108864
129
queue.size 142
130
131
-- Histograms ------------------------------------------------------------------
132
response.sizes
133
count = 1247
134
min = 128
135
max = 65536
136
mean = 2048.32
137
stddev = 512.18
138
median = 1024.00
139
75% <= 2560.00
140
95% <= 4096.00
141
98% <= 8192.00
142
99% <= 16384.00
143
99.9% <= 32768.00
144
145
-- Meters ----------------------------------------------------------------------
146
request.rate
147
count = 1247
148
mean rate = 4.17 events/second
149
1-minute rate = 5.23 events/second
150
5-minute rate = 4.89 events/second
151
15-minute rate = 4.76 events/second
152
153
-- Timers ----------------------------------------------------------------------
154
request.duration
155
count = 1247
156
mean rate = 4.17 calls/second
157
1-minute rate = 5.23 calls/second
158
5-minute rate = 4.89 calls/second
159
15-minute rate = 4.76 calls/second
160
min = 12.34 milliseconds
161
max = 987.65 milliseconds
162
mean = 145.67 milliseconds
163
stddev = 89.23 milliseconds
164
median = 123.45 milliseconds
165
75% <= 189.12 milliseconds
166
95% <= 345.67 milliseconds
167
98% <= 456.78 milliseconds
168
99% <= 567.89 milliseconds
169
99.9% <= 789.01 milliseconds
170
```
171
172
**Filtered Console Reporting:**
173
```java
174
// Report only error-related metrics
175
ConsoleReporter errorReporter = ConsoleReporter.forRegistry(registry)
176
.filter(MetricFilter.contains("error"))
177
.build();
178
179
// Report only specific metric types
180
MetricFilter timerAndHistogramFilter = new MetricFilter() {
181
@Override
182
public boolean matches(String name, Metric metric) {
183
return metric instanceof Timer || metric instanceof Histogram;
184
}
185
};
186
187
ConsoleReporter timingReporter = ConsoleReporter.forRegistry(registry)
188
.filter(timerAndHistogramFilter)
189
.build();
190
```
191
192
## CsvReporter
193
194
`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.
195
196
```java { .api }
197
public class CsvReporter extends ScheduledReporter {
198
// Factory method
199
public static Builder forRegistry(MetricRegistry registry);
200
201
// Builder class for configuration
202
public static class Builder {
203
// Unit conversion
204
public Builder convertRatesTo(TimeUnit rateUnit);
205
public Builder convertDurationsTo(TimeUnit durationUnit);
206
207
// Filtering and scheduling
208
public Builder filter(MetricFilter filter);
209
public Builder scheduleOn(ScheduledExecutorService executor);
210
public Builder shutdownExecutorOnStop(boolean shutdownExecutorOnStop);
211
212
// CSV-specific configuration
213
public Builder withClock(Clock clock);
214
public Builder formatFor(Locale locale);
215
216
// Build final reporter - requires output directory
217
public CsvReporter build(File directory);
218
}
219
}
220
```
221
222
### CSV File Provider Interface
223
224
```java { .api }
225
public interface CsvFileProvider {
226
File getFile(File directory, String metricName);
227
}
228
229
public class FixedNameCsvFileProvider implements CsvFileProvider {
230
public File getFile(File directory, String metricName);
231
}
232
```
233
234
### Usage Examples
235
236
**Basic CSV Reporting:**
237
```java
238
File csvDirectory = new File("/var/metrics/csv");
239
csvDirectory.mkdirs();
240
241
CsvReporter csvReporter = CsvReporter.forRegistry(registry)
242
.convertRatesTo(TimeUnit.SECONDS)
243
.convertDurationsTo(TimeUnit.MILLISECONDS)
244
.build(csvDirectory);
245
246
// Write metrics to CSV files every 5 minutes
247
csvReporter.start(5, TimeUnit.MINUTES);
248
```
249
250
**CSV File Structure:**
251
```
252
/var/metrics/csv/
253
├── http.requests.count.csv
254
├── http.response.time.csv
255
├── memory.heap.usage.csv
256
└── queue.size.csv
257
```
258
259
**Sample CSV Content (http.response.time.csv):**
260
```csv
261
t,count,max,mean,min,stddev,p50,p75,p95,p98,p99,p999,mean_rate,m1_rate,m5_rate,m15_rate,rate_unit,duration_unit
262
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
263
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
264
```
265
266
**Filtered CSV Reporting:**
267
```java
268
// Report only performance metrics to CSV
269
CsvReporter performanceReporter = CsvReporter.forRegistry(registry)
270
.filter(MetricFilter.endsWith(".time"))
271
.convertDurationsTo(TimeUnit.MICROSECONDS)
272
.build(new File("/var/metrics/performance"));
273
274
// Report only business metrics to CSV
275
CsvReporter businessReporter = CsvReporter.forRegistry(registry)
276
.filter(MetricFilter.startsWith("business"))
277
.build(new File("/var/metrics/business"));
278
```
279
280
## Slf4jReporter
281
282
`Slf4jReporter` outputs metrics through the SLF4J logging framework, integrating metrics reporting with your application's existing logging infrastructure.
283
284
```java { .api }
285
public class Slf4jReporter extends ScheduledReporter {
286
// Factory method
287
public static Builder forRegistry(MetricRegistry registry);
288
289
// Logging levels
290
public enum LoggingLevel {
291
TRACE, DEBUG, INFO, WARN, ERROR
292
}
293
294
// Builder class for configuration
295
public static class Builder {
296
// Logging configuration
297
public Builder outputTo(Logger logger);
298
public Builder outputTo(String loggerName);
299
public Builder markWith(Marker marker);
300
public Builder withLoggingLevel(LoggingLevel loggingLevel);
301
302
// Unit conversion
303
public Builder convertRatesTo(TimeUnit rateUnit);
304
public Builder convertDurationsTo(TimeUnit durationUnit);
305
306
// Filtering and scheduling
307
public Builder filter(MetricFilter filter);
308
public Builder scheduleOn(ScheduledExecutorService executor);
309
public Builder shutdownExecutorOnStop(boolean shutdownExecutorOnStop);
310
311
// Build final reporter
312
public Slf4jReporter build();
313
}
314
}
315
```
316
317
### Usage Examples
318
319
**Basic SLF4J Reporting:**
320
```java
321
import org.slf4j.LoggerFactory;
322
323
Slf4jReporter slf4jReporter = Slf4jReporter.forRegistry(registry)
324
.outputTo(LoggerFactory.getLogger("metrics"))
325
.convertRatesTo(TimeUnit.SECONDS)
326
.convertDurationsTo(TimeUnit.MILLISECONDS)
327
.build();
328
329
// Log metrics every 1 minute at INFO level
330
slf4jReporter.start(1, TimeUnit.MINUTES);
331
```
332
333
**Advanced SLF4J Configuration:**
334
```java
335
import org.slf4j.MarkerFactory;
336
337
Slf4jReporter advancedSlf4jReporter = Slf4jReporter.forRegistry(registry)
338
.outputTo("com.example.metrics.performance") // Logger name
339
.markWith(MarkerFactory.getMarker("METRICS")) // Log marker
340
.withLoggingLevel(Slf4jReporter.LoggingLevel.DEBUG) // Debug level
341
.filter(MetricFilter.startsWith("critical")) // Only critical metrics
342
.convertRatesTo(TimeUnit.MINUTES) // Rates per minute
343
.build();
344
```
345
346
**Sample Log Output:**
347
```
348
2023-05-15 14:30:15.123 INFO metrics - type=COUNTER, name=http.requests.count, count=1247
349
2023-05-15 14:30:15.124 INFO metrics - type=GAUGE, name=memory.heap.usage, value=67108864
350
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
351
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
352
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
353
```
354
355
**Multiple Logger Configuration:**
356
```java
357
// Performance metrics to performance logger
358
Slf4jReporter performanceReporter = Slf4jReporter.forRegistry(registry)
359
.outputTo("performance")
360
.filter(MetricFilter.contains("time"))
361
.withLoggingLevel(Slf4jReporter.LoggingLevel.INFO)
362
.build();
363
364
// Error metrics to error logger
365
Slf4jReporter errorReporter = Slf4jReporter.forRegistry(registry)
366
.outputTo("errors")
367
.filter(MetricFilter.contains("error"))
368
.withLoggingLevel(Slf4jReporter.LoggingLevel.WARN)
369
.build();
370
```
371
372
## Multiple Reporter Setup
373
374
You can run multiple reporters simultaneously to output metrics to different destinations:
375
376
```java
377
MetricRegistry registry = new MetricRegistry();
378
379
// Console reporter for development
380
ConsoleReporter consoleReporter = ConsoleReporter.forRegistry(registry)
381
.convertRatesTo(TimeUnit.SECONDS)
382
.convertDurationsTo(TimeUnit.MILLISECONDS)
383
.build();
384
385
// CSV reporter for analysis
386
CsvReporter csvReporter = CsvReporter.forRegistry(registry)
387
.build(new File("/var/metrics"));
388
389
// SLF4J reporter for production logging
390
Slf4jReporter slf4jReporter = Slf4jReporter.forRegistry(registry)
391
.outputTo("metrics")
392
.build();
393
394
// Start all reporters with different schedules
395
consoleReporter.start(30, TimeUnit.SECONDS); // Console every 30s
396
csvReporter.start(5, TimeUnit.MINUTES); // CSV every 5 minutes
397
slf4jReporter.start(1, TimeUnit.MINUTES); // Logs every 1 minute
398
399
// Proper shutdown
400
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
401
consoleReporter.stop();
402
csvReporter.stop();
403
slf4jReporter.stop();
404
}));
405
```
406
407
## Custom Reporter Implementation
408
409
You can create custom reporters by extending `ScheduledReporter`:
410
411
```java
412
public class CustomReporter extends ScheduledReporter {
413
414
protected CustomReporter(MetricRegistry registry,
415
String name,
416
MetricFilter filter,
417
TimeUnit rateUnit,
418
TimeUnit durationUnit) {
419
super(registry, name, filter, rateUnit, durationUnit);
420
}
421
422
@Override
423
protected void report(SortedMap<String, Gauge> gauges,
424
SortedMap<String, Counter> counters,
425
SortedMap<String, Histogram> histograms,
426
SortedMap<String, Meter> meters,
427
SortedMap<String, Timer> timers) {
428
429
// Custom reporting logic
430
for (Map.Entry<String, Counter> entry : counters.entrySet()) {
431
String name = entry.getKey();
432
Counter counter = entry.getValue();
433
sendToCustomDestination(name, "counter", counter.getCount());
434
}
435
436
for (Map.Entry<String, Histogram> entry : histograms.entrySet()) {
437
String name = entry.getKey();
438
Histogram histogram = entry.getValue();
439
Snapshot snapshot = histogram.getSnapshot();
440
441
sendToCustomDestination(name, "histogram", Map.of(
442
"count", histogram.getCount(),
443
"mean", snapshot.getMean(),
444
"p95", snapshot.get95thPercentile(),
445
"p99", snapshot.get99thPercentile()
446
));
447
}
448
449
// Handle other metric types...
450
}
451
452
private void sendToCustomDestination(String name, String type, Object value) {
453
// Custom implementation: send to database, web service, message queue, etc.
454
}
455
456
public static Builder forRegistry(MetricRegistry registry) {
457
return new Builder(registry);
458
}
459
460
public static class Builder {
461
private final MetricRegistry registry;
462
private MetricFilter filter = MetricFilter.ALL;
463
private TimeUnit rateUnit = TimeUnit.SECONDS;
464
private TimeUnit durationUnit = TimeUnit.MILLISECONDS;
465
466
private Builder(MetricRegistry registry) {
467
this.registry = registry;
468
}
469
470
public Builder filter(MetricFilter filter) {
471
this.filter = filter;
472
return this;
473
}
474
475
public Builder convertRatesTo(TimeUnit rateUnit) {
476
this.rateUnit = rateUnit;
477
return this;
478
}
479
480
public Builder convertDurationsTo(TimeUnit durationUnit) {
481
this.durationUnit = durationUnit;
482
return this;
483
}
484
485
public CustomReporter build() {
486
return new CustomReporter(registry, "custom-reporter", filter, rateUnit, durationUnit);
487
}
488
}
489
}
490
```
491
492
### Usage of Custom Reporter:
493
494
```java
495
CustomReporter customReporter = CustomReporter.forRegistry(registry)
496
.filter(MetricFilter.startsWith("api"))
497
.convertRatesTo(TimeUnit.MINUTES)
498
.build();
499
500
customReporter.start(2, TimeUnit.MINUTES);
501
```
502
503
## Metric Filtering
504
505
All reporters support filtering to control which metrics are reported:
506
507
```java
508
// Predefined filters
509
MetricFilter all = MetricFilter.ALL;
510
MetricFilter httpMetrics = MetricFilter.startsWith("http");
511
MetricFilter errorMetrics = MetricFilter.contains("error");
512
MetricFilter apiMetrics = MetricFilter.endsWith("api");
513
514
// Custom filter implementation
515
MetricFilter customFilter = new MetricFilter() {
516
@Override
517
public boolean matches(String name, Metric metric) {
518
// Only report timers and histograms with high activity
519
if (metric instanceof Timer) {
520
return ((Timer) metric).getCount() > 100;
521
}
522
if (metric instanceof Histogram) {
523
return ((Histogram) metric).getCount() > 50;
524
}
525
return false;
526
}
527
};
528
529
// Composite filters
530
MetricFilter compositeFilter = new MetricFilter() {
531
@Override
532
public boolean matches(String name, Metric metric) {
533
return MetricFilter.startsWith("critical").matches(name, metric) ||
534
MetricFilter.contains("error").matches(name, metric);
535
}
536
};
537
```
538
539
## Best Practices
540
541
### Reporter Configuration
542
- Use console reporters for development and debugging
543
- Use CSV reporters for detailed analysis and data visualization
544
- Use SLF4J reporters for production logging and integration with log aggregation systems
545
- Configure appropriate reporting intervals based on metric update frequency and monitoring requirements
546
547
### Performance Considerations
548
- Reporting operations can be expensive for large numbers of metrics
549
- Use filtering to limit the number of metrics reported
550
- Consider separate reporters for different metric categories with different reporting intervals
551
- Monitor reporter performance and adjust intervals if necessary
552
553
### Resource Management
554
- Always stop reporters during application shutdown
555
- Use `shutdownExecutorOnStop(true)` unless managing executors externally
556
- Monitor disk space when using CSV reporters with high-frequency reporting
557
- Be aware of log file rotation when using SLF4J reporters
558
559
### Production Deployment
560
- Start with longer reporting intervals in production and adjust based on monitoring needs
561
- Use multiple reporters to send different metrics to different destinations
562
- Implement custom reporters for integration with specific monitoring systems
563
- Monitor reporter error logs and implement fallback strategies for critical metrics
564
565
### Testing Strategies
566
- Use manual `report()` calls for testing reporter output
567
- Test filtering logic with known metric sets
568
- Verify unit conversions in reporter output
569
- Test reporter lifecycle (start, stop, restart) in integration tests