Rename metrics in Dropwizard MetricsRegistry in a Spring Boot application

86 Views Asked by At

in my Spring Boot application, I am using a metrics library which is a wrapper over: io.dropwizard.metrics. So I am getting all the API and system metrics from MetricsRegistry.

As a result, while I am hitting the /metrics actuator endpoint, I am able to see all these metrics.

Now, the problem is: all the metric names in the format a.b.c, which I would like to rename as: a_b_c.

But I could not find any way to achieve the same.

Could anyone please help with a few pointers? Thanks in advance.

1

There are 1 best solutions below

4
VonC On BEST ANSWER

Looking at the source code de MetricRegistry.java, in theory you could iterate over the existing metrics in the MetricsRegistry and, for each metric, remove the metric from the registry and re-add it with the new name.

However, note that directly manipulating the MetricsRegistry like this might introduce issues or limitations, especially with metrics that are automatically managed or updated by other components within your application.

import com.codahale.metrics.MetricRegistry;
import java.util.Map;
import java.util.HashMap;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.PostConstruct;

@Component
public class MetricRenamer {

    @Autowired
    private MetricRegistry metricRegistry;

    @PostConstruct
    public void renameMetrics() {
        // Temporary map to hold the new metrics
        Map<String, Metric> renamedMetrics = new HashMap<>();

        metricRegistry.getMetrics().forEach((name, metric) -> {
            // Generate the new name
            String newName = name.replace('.', '_');
            
            // Add to the temporary map
            renamedMetrics.put(newName, metric);
        });

        // Remove the old metrics and add the renamed ones
        renamedMetrics.forEach((newName, metric) -> {
            metricRegistry.remove(newName.replace('_', '.'));
            metricRegistry.register(newName, metric);
        });
    }
}

Before renaming, consider keeping a backup of original metric names and their references, for making sure you can revert changes if needed. If your application updates metrics in a highly concurrent environment, consider the implications of modifying the MetricsRegistry at runtime. You may need to implement additional synchronization or use a MetricRegistryListener to manage changes dynamically.

Again, directly manipulating the MetricsRegistry might not be suitable for all applications, especially those that rely on automatic metric registration and updates.


manipulating the MetricsRegistry might not be suitable for all applications, especially those that rely on automatic metric registration and updates, what do you suggest in such a scenario for changing the names of the metrics?

You could try indeed to extend MetricRegistry and override the method responsible for registering metrics. That custom registry can then replace the default one in your Spring Boot application context.

import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Metric;

public class CustomMetricRegistry extends MetricRegistry {
    @Override
    public <T extends Metric> T register(String name, T metric) throws IllegalArgumentException {
        // Modify the metric name here before it is registered
        String modifiedName = name.replace('.', '_');
        return super.register(modifiedName, metric);
    }
}

To use your custom registry, you need to make sure it is the one picked up by Spring Boot's autoconfiguration. That often involves defining it as a bean before the default configuration kicks in:

@Configuration
public class MetricsConfig {

    @Bean
    public MetricRegistry metricRegistry() {
        return new CustomMetricRegistry();
    }
}

If you are dealing with a more complex scenario where metrics are being registered through a MetricSet or via a MetricBuilder, consider wrapping these constructs to modify metric names at the point of creation.

import com.codahale.metrics.*;

public class CustomMetricSet implements MetricSet {
    private final MetricSet original;

    public CustomMetricSet(MetricSet original) {
        this.original = original;
    }

    @Override
    public Map<String, Metric> getMetrics() {
        // Transform metric names in the original set
        return original.getMetrics().entrySet().stream()
            .collect(Collectors.toMap(
                entry -> entry.getKey().replace('.', '_'),
                Map.Entry::getValue
            ));
    }
}

Then, when you register your MetricSet:

metricRegistry.registerAll(new CustomMetricSet(yourOriginalMetricSet));

My one doubt here is: where should I register the custom metric set. Should I do it on ApplicationReadyEvent?

Yes, registering your custom metric set on the ApplicationReadyEvent should work in a Spring Boot application: it makes sure your custom metric set is registered after all the autoconfiguration is complete but before the application starts serving traffic: your metrics are in place and ready to be used as soon as your application is up.

import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.beans.factory.annotation.Autowired;
import com.codahale.metrics.MetricRegistry;

@Component
public class CustomMetricSetRegistrar implements ApplicationListener<ApplicationReadyEvent> {

    @Autowired
    private MetricRegistry metricRegistry;

    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        // Assuming you have an existing MetricSet that you wish to wrap
        MetricSet yourOriginalMetricSet = ...; // Your existing MetricSet

        // Wrap and register the MetricSet with transformed metric names
        metricRegistry.registerAll(new CustomMetricSet(yourOriginalMetricSet));
    }
}

CustomMetricSet is a wrapper around your original MetricSet that you have designed to modify the metric names according to your naming convention (e.g., replacing dots with underscores). When the application is fully ready, this listener triggers, wrapping and registering your metric set with the modified names.


This is working fine for system metrics emitting from Scone-based applications like JVM, GC metrics, etc.

However, one issue I am facing is as follows: I have registered a few API metrics by extending HandlerInterceptorAdapter, but I cannot find any of the API metrics in CustomMetricRegistry.

If you have extended HandlerInterceptorAdapter to register API metrics and they are not appearing in your CustomMetricRegistry, check if:

  • the API metrics registration is happening before your custom metric registry is fully set up or recognized by the application context.
    Make sure your custom metric registry is declared and initialized early. Using a @Bean method in a @Configuration class, as shown in your setup, is usually sufficient. Consider marking the configuration class with @Priority or using @DependsOn if there are specific beans that must be initialized first (see "@Order in Spring").

  • your interceptor is referencing a different instance of MetricRegistry than the one you have customized and registered as a bean. That can happen in a Spring Boot application due to the way beans are auto-configured and instantiated.
    In your HandlerInterceptorAdapter implementation, explicitly autowire your custom metric registry to make sure it is using the correct instance. For example:

    @Autowired
    private CustomMetricRegistry metricRegistry;
    

    If your custom registry extends MetricRegistry, make sure you are injecting the right type. If necessary, qualify the autowiring with @Qualifier to make sure the correct bean is used.

  • your custom metric registry is getting overridden or not used where you expect (if there are multiple beans of type MetricRegistry or if auto-configuration creates a default one).
    Use Spring Boot's actuator endpoint /beans or the Spring Boot Admin interface to inspect the beans in your application context. Look for multiple instances of MetricRegistry and see which one is being injected into your interceptor. You might need to use @Primary on your custom metric registry bean to make sure it is the one being picked up by default.

If API metrics are still not showing up, consider registering them explicitly after your application is fully started, similar to how you handled system metrics with the ApplicationReadyEvent. That makes sure the registration happens after all configurations and auto-configurations are complete.

@Component
public class ApiMetricsRegistrar implements ApplicationListener<ApplicationReadyEvent> {

    @Autowired
    private CustomMetricRegistry metricRegistry;

    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        // Register your API metrics here to use the custom metric registry
    }
}

That way, your API metrics are registered with the correct MetricRegistry instance and at the right time in the application lifecycle.