Skip to content

Instantly share code, notes, and snippets.

@pavelfomin
Last active March 21, 2025 18:13
Show Gist options
  • Save pavelfomin/c6034d012de7f71fdad8d5b5c35670ea to your computer and use it in GitHub Desktop.
Save pavelfomin/c6034d012de7f71fdad8d5b5c35670ea to your computer and use it in GitHub Desktop.
How to use @timed annotation

Using @Timed and Observation API

https://micrometer.io/docs/concepts#_the_timed_annotation

@Configuration
public class TimedConfiguration {
   @Bean
   public TimedAspect timedAspect(MeterRegistry registry) {
      return new TimedAspect(registry);
   }
}

Usage:

import io.micrometer.core.annotation.Timed;
@Timed(value = "tps.consumer", extraTags = {"consumer", "NotificationConsumer"})
public process() {
}

Using @MeterTag

@Configuration
public class TimedConfiguration {

    @Bean
    TimedAspect timedAspect(MeterRegistry registry, ValueResolverProvider valueResolverProvider, ValueExpressionResolverProvider valueExpressionResolverProvider) {

        TimedAspect timedAspect = new TimedAspect(registry);

        timedAspect.setMeterTagAnnotationHandler(
            new MeterTagAnnotationHandler(valueResolverProvider, valueExpressionResolverProvider));

        return timedAspect;
    }

    /**
     * Value expressions are not supported.
     */
    @Component
    @RequiredArgsConstructor
    static class ValueExpressionResolverProvider implements Function<Class<? extends ValueExpressionResolver>, ValueExpressionResolver> {

        @Override
        public ValueExpressionResolver apply(Class<? extends ValueExpressionResolver> aClass) {

            throw new IllegalArgumentException("Value expressions are not supported");
        }
    }
    
    /**
     * All custom ValueResolvers should be registered as singleton Beans for performance reason
     * to avoid their instantiation per @Timed call.
     */
    @Component
    @RequiredArgsConstructor
    static class ValueResolverProvider implements Function<Class<? extends ValueResolver>, ValueResolver> {

        private final NotificationMethodValueResolver notificationMethodValueResolver;

        private Map<Class, ValueResolver> valueResolvers = new HashMap<>();

        @PostConstruct
        public void postConstruct() {

            valueResolvers.put(notificationMethodValueResolver.getClass(), notificationMethodValueResolver);
        }

        @Override
        public ValueResolver apply(Class<? extends ValueResolver> aClass) {

            ValueResolver valueResolver = valueResolvers.get(aClass);

            if (valueResolver != null) {
                return valueResolver;
            } else {
                throw new IllegalArgumentException("Custom ValueResolver class " + aClass + " must be registered");
            }
        }
    }

    @Component
    public static class NotificationMethodValueResolver implements ValueResolver {

        @Override
        public String resolve(Object parameter) {
            return ((Notification) parameter).getNotificationMethod().toString();
        }
    }
}

Usage:

    @Timed(value = "tps.processor", extraTags = {"processor", "NotificationProcessor"})
    public NotificationEntity process(
        @MeterTag(value = "notificationMethod", resolver = TimedConfiguration.NotificationMethodValueResolver.class) Notification notification,
        // MeterTagAnnotationHandler will use .toString() by default 
        @MeterTag("metadata") KafkaEventMetadata metadata) {

Using programmatic API

Inject ObservationRegistry

    private final ObservationRegistry observationRegistry;

and then use Observation API to either call observe:

        return Observation.createNotStarted("foo.bar.publish", observationRegistry)
            .lowCardinalityKeyValue("tag1", payload.getTag1())
            .lowCardinalityKeyValue("tag2", payload.getTag2())
            .highCardinalityKeyValue("args", payload.getArgs()) // won't create a tag
            .observe(() -> producer.sendMessage(payload));

or observeChecked:

        return Observation.createNotStarted("foo.bar.query", observationRegistry)
            .highCardinalityKeyValue("offset", offset) // won't create a tag
            .observeChecked(() -> service.queryData(offset));

Limitations of @MeterTag:

  • only a single tag can be derived from a complex parameter
  • lack of low vs high cardinality support
  • not sure about additional per call performance implications, especially with the expression usage
@pavelfomin
Copy link
Author

See also ObservedAspectObservationDocumentation and how it is used by @Observe in ObservedAspect

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment