You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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 AtomicReference to track the current day safely across threads, avoiding race conditions.
  • The resetMetrics method 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 logMetrics method'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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 07:59:55