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() {
}@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) {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));- 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
expressionusage
See also ObservedAspectObservationDocumentation and how it is used by
@Observein ObservedAspect