Metrics
Interesting metrics in a message centric system come in several forms and flavors, like count, capacity, and latency for example.
Axon Framework allows you to retrieve such measurements through the use of the extension modules axon-metrics-dropwizard and axon-metrics-micrometer.
With these modules you can register a number of MessageMonitor implementations to your messaging components, like the CommandBus, EventBus, QueryBus and EventProcessors.
As the name suggests, the axon-metrics-dropwizard module uses Dropwizard Metrics for registering the measurements of your infrastructure components.
Hence, the MessageMonitors provided by this module are registered against the Dropwizard MetricRegistry.
Furthermore, the axon-metrics-micrometer module uses Micrometer for measurements.
This is a dimensional-first metrics collection facade, whose aim is to allow you to time, count, and gauge your code with a vendor neutral API.
Furthermore, the MessageMonitors provided by this module are registered against the Micrometer MeterRegistry.
The following monitor implementations are available through both modules:
-
CapacityMonitor- Measures message capacity by keeping track of the total time spent on message handling compared to total time it is active. This returns a number between 0 and n number of threads. Thus, if there are 4 threads working, the maximum capacity is 4 if every thread is active 100% of the time. -
EventProcessorLatencyMonitor- Measures the difference between an event’s timestamp and the current time, showing how far behind an event processor is. Note that triggering a reset will impact this metric! -
MessageCountingMonitor- Counts the number of ingested, successful, failed, ignored and processed messages. -
MessageTimerMonitor- Keeps a timer for all successful, failed and ignored messages, as well as an overall timer for all three combined.
The axon-metrics-dropwizard module has one unique MessageMonitor, called the PayloadTypeMessageMonitorWrapper. This is a MessageMonitor implementation that allows setting a monitor per message type instead of per message publishing/handling component. This monitor is not mandatory for axon-metrics-micrometer due to Micrometer’s dimensional-first support. As a result, any measurement can be tagged with the payload type automatically.
You are free to configure any combination of MessageMonitors through constructors on your messaging components, simply by using the Configuration API.
The GlobalMetricRegistry contained in the axon-metrics-dropwizard and axon-metrics-micrometer modules provides a set of sensible defaults per type of messaging component.
The following example shows you how to configure default metrics for your message handling components:
Configuring message monitors
For configuring MessageMonitors in your application, please read the section for Dropwizard or Micrometer.
|
Spring Boot Property-based disabling
When using Spring Boot, you can disable metrics entirely by setting the following property:
This property will influence both Dropwizard and Micrometer metrics. |
Dropwizard
You need to include a dependency to axon-metrics-dropwizard to enable Dropwizard metrics:
-
Maven
-
Gradle
<dependency>
<groupId>org.axonframework.extensions.metrics</groupId>
<artifactId>axon-metrics-dropwizard</artifactId>
<version>${axon.version}</version>
</dependency>
implementation 'org.axonframework.extensions.metrics:axon-metrics-dropwizard:${axonVersion}'
With the dependency in place the default MessageMonitors will be set for all CommandBus, EventSink, EventProcessor, and QueryBus implementations present in the application.
If you need to specify the MetricRegistry used, please check the following examples:
-
Configuration API
-
Spring Boot
import com.codahale.metrics.MetricRegistry;
import org.axonframework.extension.metrics.dropwizard.MetricsConfigurationEnhancer;
import org.axonframework.messaging.core.configuration.MessagingConfigurer;
class MetricsConfiguration {
public void configureDefaultMetrics(MessagingConfigurer configurer,
MetricRegistry metricRegistry) {
configurer.componentRegistry(componentRegistry -> componentRegistry.registerEnhancer(
new MetricsConfigurationEnhancer(metricRegistry)
));
}
}
import com.codahale.metrics.MetricRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
class MetricsConfiguration {
@Bean
public MetricRegistry metricRegistry() {
return new MetricRegistry();
}
}
Micrometer
You need to include a dependency to axon-metrics-micrometer to enable Micrometer metrics:
-
Maven
-
Gradle
<dependency>
<groupId>org.axonframework.extensions.metrics</groupId>
<artifactId>axon-metrics-micrometer</artifactId>
<version>${axon.version}</version>
</dependency>
implementation 'org.axonframework.extensions.metrics:axon-metrics-micrometer:${axonVersion}'
With the dependency in place the default MessageMonitors will be set for all CommandBus, EventSink, EventProcessor, and QueryBus implementations present in the application.
If you are using an automated configuration flow, as with Spring, be sure to set the following properties:
# Spring Boot metrics enabled
management.endpoint.metrics.enabled=true
# Spring Boot (Prometheus) endpoint (`/actuator/prometheus`) enabled and exposed
management.metrics.export.prometheus.enabled=true
management.endpoint.prometheus.enabled=true
Furthermore, Micrometer offers to option to enable dimensions to your measurements. This allows you to filter the measurements on, for example, the payload type of messages:
-
Declarative configuration
-
Automated configuration - Spring Boot
import io.micrometer.core.instrument.MeterRegistry;
import org.axonframework.extension.metrics.micrometer.MetricsConfigurationEnhancer;
import org.axonframework.messaging.core.configuration.MessagingConfigurer;
class MetricsConfiguration {
public void configureDefaultMetrics(MessagingConfigurer configurer,
MeterRegistry meterRegistry) {
boolean useDimensions = true;
configurer.componentRegistry(componentRegistry -> componentRegistry.registerEnhancer(
new MetricsConfigurationEnhancer(meterRegistry, useDimensions)
));
}
}
# The default value is `false`.
# By enabling this property you will have message (event, command, query)
# payload type set as a micrometer tag/dimension by default.
# Additionally, the processor name will be a tag/dimension instead of it being part of the metric name.
axon.metrics.micrometer.dimensional=true
Lastly, if you need to specify the MetricRegistry used, please check the following examples:
-
Declarative configuration
-
Automated configuration - Spring Boot
import io.micrometer.core.instrument.MeterRegistry;
import org.axonframework.extension.metrics.micrometer.MetricsConfigurationEnhancer;
import org.axonframework.messaging.core.configuration.MessagingConfigurer;
class MetricsConfiguration {
public void configureDefaultMetrics(MessagingConfigurer configurer,
MeterRegistry meterRegistry) {
configurer.componentRegistry(componentRegistry -> componentRegistry.registerEnhancer(
new MetricsConfigurationEnhancer(meterRegistry)
));
}
}
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
class MetricsConfiguration {
@Bean
public MeterRegistry meterRegistry() {
return new SimpleMeterRegistry();
}
}
Configuring (custom) message monitors
The scenario might occur that more fine-grained control over which MessageMonitor instances are defined is necessary.
The MessagingConfigurer provides dedicated methods to register monitors per message type:
-
registerMessageMonitor(…)- Registers a monitor for all message types. -
registerCommandMonitor(…)- Registers a monitor specifically for command messages. -
registerEventMonitor(…)- Registers a monitor specifically for event messages. -
registerQueryMonitor(…)- Registers a monitor specifically for query messages. -
registerSubscriptionQueryUpdateMonitor(…)- Registers a monitor for subscription query update messages.
Multiple monitors can be registered for the same message type.
When retrieving a monitor, all registered monitors are combined into a MultiMessageMonitor, which delegates message ingestion and callbacks to all of them.
The following snippet shows how to register custom message monitors using the MessagingConfigurer:
import org.axonframework.messaging.core.configuration.MessagingConfigurer;
import org.axonframework.messaging.monitoring.MessageMonitor;
class MetricsConfiguration {
public void configureCustomMonitors(MessagingConfigurer configurer) {
// Register a monitor for all message types
configurer.registerMessageMonitor(config -> new MyCustomMessageMonitor());
// Register monitors for specific message types
configurer.registerCommandMonitor(config -> new MyCommandMonitor());
configurer.registerEventMonitor(config -> new MyEventMonitor());
configurer.registerQueryMonitor(config -> new MyQueryMonitor());
}
}
If registering MessageMonitors per message type is not fine-grained enough, we recommend the registration of a component decorator. Axon’s decorator registration allows you to wrap infrastructure components with custom logic, including monitoring. It is actually how Axon Framework itself attaches the MessageMonitors as a special type of interceptor to the right infrastructure components.
The following example shows how to create register a decorator specifically for an EventSink to include a specific MessageMonitor:
import org.axonframework.messaging.core.configuration.MessagingConfigurer;
import org.axonframework.messaging.eventhandling.EventMessage;
import org.axonframework.messaging.eventhandling.EventSink;
import org.axonframework.messaging.eventhandling.InterceptingEventSink;
import org.axonframework.messaging.monitoring.MessageMonitor;
import org.axonframework.messaging.monitoring.interception.MonitoringEventDispatchInterceptor;
import java.util.List;
class AxonConfig {
public void registerMonitorDecorator(MessagingConfigurer configurer,
MessageMonitor<? super EventMessage> eventMonitor) {
configurer.componentRegistry(componentRegistry -> componentRegistry.registerDecorator(
EventSink.class,
0,
(config, name, delegate) -> new InterceptingEventSink(
delegate,
List.of(new MonitoringEventDispatchInterceptor(eventMonitor))
)
));
}
}