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) {
public class ObservationConfiguration {
static final String OBSERVATION = "foobar";
static final String OBSERVATION_QUERY = OBSERVATION + ".query";
static final String OBSERVATION_PUBLISH = OBSERVATION + ".publish";
@Bean
Observation queryObservation(ObservationRegistry observationRegistry) {
return Observation.createNotStarted(OBSERVATION_QUERY, observationRegistry);
}
@Bean
Observation publishObservation(ObservationRegistry observationRegistry) {
return Observation.createNotStarted(OBSERVATION_PUBLISH, observationRegistry);
}
}
Then use Observation
beans to either call observe
:
Observation observation = publishObservation.lowCardinalityKeyValues(createPublishTags(payload));
return observation.observe(() -> producer.sendMessage(payload));
or observeChecked
:
Observation observation = queryObservation.highCardinalityKeyValues(createQueryTags(offset));
List<Data> objects = observation.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
expression
usage