基于log4j实现dropwizard-metrics自定义指标上报器需求咨询
Alright, let's walk through how to build this custom Log4j reporter for Dropwizard Metrics to meet your exact needs—hourly metric logging, daily resets with log rolling, and capturing that final daily metric snapshot. Since there's no out-of-the-box Log4j reporter, we'll extend Dropwizard's ScheduledReporter to handle scheduling, logging, and daily reset logic.
1. Build the Custom Log4j Metric Reporter
We'll create a reporter that runs hourly, checks for date changes to trigger daily resets, and logs metrics via Log4j. Here's the core implementation:
import com.codahale.metrics.*; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.time.ZonedDateTime; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; public class Log4jMetricsReporter extends ScheduledReporter { private static final Logger logger = LogManager.getLogger(Log4jMetricsReporter.class); private final AtomicReference<ZonedDateTime> currentDate = new AtomicReference<>(ZonedDateTime.now().truncatedTo(java.time.temporal.ChronoUnit.DAYS)); private final MetricRegistry registry; protected Log4jMetricsReporter(MetricRegistry registry, String name, MetricFilter filter, TimeUnit rateUnit, TimeUnit durationUnit) { super(registry, name, filter, rateUnit, durationUnit); this.registry = registry; } public static Log4jMetricsReporter forRegistry(MetricRegistry registry) { return new Log4jMetricsReporter(registry, "log4j-reporter", MetricFilter.ALL, TimeUnit.SECONDS, TimeUnit.MILLISECONDS); } @Override public void report(Map<String, Gauge> gauges, Map<String, Counter> counters, Map<String, Histogram> histograms, Map<String, Meter> meters, Map<String, Timer> timers) { ZonedDateTime now = ZonedDateTime.now().truncatedTo(java.time.temporal.ChronoUnit.DAYS); ZonedDateTime lastDate = currentDate.get(); // Check if we've crossed into a new day if (!now.equals(lastDate)) { // Log final metrics for the previous day logger.info("[DAILY_FINAL] Final metrics for {}:", lastDate.toLocalDate()); logMetrics(gauges, counters, histograms, meters, timers); // Reset all metrics in the registry resetMetrics(); // Update the current date reference currentDate.set(now); logger.info("[DAILY_RESET] Metrics reset and log rolled for new day: {}", now.toLocalDate()); } // Log hourly snapshot logger.info("[HOURLY_SNAPSHOT] Metrics at {}:", ZonedDateTime.now()); logMetrics(gauges, counters, histograms, meters, timers); } private void logMetrics(Map<String, Gauge> gauges, Map<String, Counter> counters, Map<String, Histogram> histograms, Map<String, Meter> meters, Map<String, Timer> timers) { // Log gauges gauges.forEach((name, gauge) -> logger.info("Gauge | {} | Value: {}", name, gauge.getValue())); // Log counters counters.forEach((name, counter) -> logger.info("Counter | {} | Count: {}", name, counter.getCount())); // Log histograms histograms.forEach((name, histogram) -> { Snapshot snapshot = histogram.getSnapshot(); logger.info("Histogram | {} | Count: {}, Min: {}, Max: {}, Mean: {:.2f}, Median: {:.2f}, 95th: {:.2f}", name, histogram.getCount(), snapshot.getMin(), snapshot.getMax(), snapshot.getMean(), snapshot.getMedian(), snapshot.get95thPercentile()); }); // Log meters meters.forEach((name, meter) -> logger.info("Meter | {} | Count: {}, Rate (1min): {:.2f}, Rate (5min): {:.2f}, Rate (15min): {:.2f}", name, meter.getCount(), meter.getOneMinuteRate(), meter.getFiveMinuteRate(), meter.getFifteenMinuteRate())); // Log timers timers.forEach((name, timer) -> { Snapshot snapshot = timer.getSnapshot(); logger.info("Timer | {} | Count: {}, Min: {}ms, Max: {}ms, Mean: {:.2f}ms, Median: {:.2f}ms, 95th: {:.2f}ms, Rate (1min): {:.2f}", name, timer.getCount(), snapshot.getMin(), snapshot.getMax(), snapshot.getMean(), snapshot.getMedian(), snapshot.get95thPercentile(), timer.getOneMinuteRate()); }); } private void resetMetrics() { // Iterate over all metrics and reset them (thread-safe approach) registry.getMetrics().forEach((name, metric) -> { if (metric instanceof Counter) { ((Counter) metric).dec(((Counter) metric).getCount()); } else if (metric instanceof Histogram) { // Dropwizard's Histogram lacks a native reset—remove and re-register or use a custom implementation registry.remove(name); registry.histogram(name); } else if (metric instanceof Meter) { ((Meter) metric).mark(-((Meter) metric).getCount()); } else if (metric instanceof Timer) { // Reset the meter component; snapshot resets require a custom Timer implementation ((Timer) metric).mark(-((Timer) metric).getCount()); } // Gauges are typically read-only—skip unless your custom gauge supports resetting }); } }
Key Notes:
- We use an
AtomicReferenceto track the current day safely across threads, avoiding race conditions. - The
resetMetricsmethod handles different metric types—note that some Dropwizard metrics (like Histogram) don't have a native reset, so adjust this logic based on your specific metric usage (e.g., use custom metric implementations if full reset support is needed). - Clear markers (
[HOURLY_SNAPSHOT],[DAILY_FINAL]) make it easy to distinguish log entry types.
2. Configure Log4j for Daily Log Rolling
To sync log rolling with your daily metric reset, configure Log4j2 to roll logs at midnight. Here's a sample log4j2.xml configuration:
<?xml version="1.0" encoding="UTF-8"?> <Configuration status="WARN"> <Appenders> <RollingFile name="MetricsRollingFile" fileName="logs/metrics.log" filePattern="logs/metrics-%d{yyyy-MM-dd}.log.gz"> <PatternLayout pattern="%d{ISO8601} [%t] %-5level %logger{36} - %msg%n"/> <Policies> <TimeBasedTriggeringPolicy interval="1" modulate="true"/> </Policies> <DefaultRolloverStrategy max="30"/> <!-- Retain logs for 30 days --> </RollingFile> </Appenders> <Loggers> <Logger name="com.yourpackage.Log4jMetricsReporter" level="info" additivity="false"> <AppenderRef ref="MetricsRollingFile"/> </Logger> <Root level="error"> <AppenderRef ref="MetricsRollingFile"/> </Root> </Loggers> </Configuration>
The TimeBasedTriggeringPolicy with interval="1" and modulate="true" ensures logs roll at midnight every day, perfectly aligning with our daily metric reset.
3. Integrate the Reporter into Your Application
In your Dropwizard application's run method, initialize and start the reporter to run hourly:
import com.codahale.metrics.MetricRegistry; import io.dropwizard.Application; import io.dropwizard.setup.Bootstrap; import io.dropwizard.setup.Environment; import java.util.concurrent.TimeUnit; public class YourBackendApp extends Application<YourConfiguration> { private final MetricRegistry metricRegistry = new MetricRegistry(); @Override public void initialize(Bootstrap<YourConfiguration> bootstrap) { bootstrap.setMetricRegistry(metricRegistry); } @Override public void run(YourConfiguration configuration, Environment environment) { // Initialize and start the custom Log4j reporter Log4jMetricsReporter reporter = Log4jMetricsReporter.forRegistry(metricRegistry); reporter.start(1, TimeUnit.HOURS); // Trigger hourly metric logging // Register your web services and components with the metric registry // ... } public static void main(String[] args) throws Exception { new YourBackendApp().run(args); } }
4. Important Considerations
- Thread Safety: The reset logic uses thread-safe constructs to avoid race conditions when checking date changes and modifying metrics.
- Custom Metric Types: For full reset support for Histograms/Timers, consider extending Dropwizard's implementations to add a
reset()method instead of removing/re-registering metrics. - Log Formatting: Adjust the
logMetricsmethod's output to match your team's standards—you could switch to JSON format for easier parsing with log aggregation tools. - Testing: Validate the daily reset logic by manually advancing the system time to ensure metrics are reset and final daily logs are captured correctly.
内容的提问来源于stack exchange,提问作者Shadov

