Creating an interceptor to report a micrometer metric in Quarkus

Julio Santana
2 min readMar 14, 2023

When you start to measure things in your application, your level of awareness of how your systems works step to a higher level. A lot of facts of how people uses your application, how bad was that algorithm that you thought would increase the performance crazily when receving thousands of requests, everything, becomes observable and measurable.

Then you become addicted to measure, and want to get insights of how every little part of your systems is performing while looking at your cool graph drawn in your current visualisation tool, sometimes Grafana, sometimes Datadog.

One cool thing that the guys from micrometer came with was to create @Timed and @Counted annotations to make metrics creation easier. But then arrives this moment when they are not enough, because you need to measure something else, that doesn’t fit into that annotations, and you have to go through all documentation once again to remember how to report a metric declaratively from your code.

But wait, that shouldn’t be difficult and shouldn’t make your code uglier by including cross cutting concerns inside it, right? They told us that there was some tool for this kind of tasks, micrometer guys know it, too, that’s why they created @Timed

So armed with Quarkus and its AOP interpretation in forms of Interceptors (taken from Java standards ) we can isolate our metrics into aspects. Let’s create an aspect to report the size of Collection returned by a method.

@SizeCollector
@Interceptor
@Priority(Interceptor.Priority.PLATFORM_AFTER)
public class SizeCollectorInterceptor {

private final MeterRegistry meterRegistry;

private static final String METRIC_NAME = "returnedCollection.size";

public SizeCollectorInterceptor(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}

@AroundInvoke
Object registerCollectionSizeGauge(InvocationContext context) throws Exception {
Class<?> returnType = context.getMethod().getReturnType();
String methodName = context.getMethod().getName();

if(Collection.class.isAssignableFrom(returnType)){
var result = (Collection)context.proceed();

meterRegistry.summary(METRIC_NAME, Tags.of("method", methodName)).record(result.size());

return result;
}

throw new RuntimeException("SizeCollector shouldn't be used on a method that doesn't return a collection");
}

}

This will be associated to the annotation @SizeCollector and will wrap a method that returns a collection. After the invocation, this will store the collection size on a summary method and report it to micrometer so that it can be scraped and made available on your metric visualization systems. The annotations is also defined in a simple way, like this:

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@InterceptorBinding
public @interface SizeCollector {

}
@SizeCollector
public List<Dto> getDtos(Long key){...}

And that’s it. After this you are ready to start annotating your methods and collecting new metrics about your collections (or whatever object you want to observe in a very simple way)

--

--