15 KiB
Metrics with Spring Boot Actuator
Spring Boot Actuator provides dependency management and auto-configuration for Micrometer, an application metrics facade that supports numerous monitoring systems, including:
- AppOptics
- Atlas
- Datadog
- Dynatrace
- Elastic
- Ganglia
- Graphite
- Humio
- InfluxDB
- JMX
- KairosDB
- New Relic
- OpenTelemetry Protocol (OTLP)
- Prometheus
- Simple (in-memory)
- Google Cloud Monitoring (Stackdriver)
- StatsD
- Wavefront
Tip
To learn more about Micrometer's capabilities, see its reference documentation, in particular the concepts section.
Getting Started
Spring Boot auto-configures a composite MeterRegistry and adds a registry to the composite for each of the supported implementations that it finds on the classpath. Having a dependency on micrometer-registry-{system} in your runtime classpath is enough for Spring Boot to configure the registry.
Most registries share common features. For instance, you can disable a particular registry even if the Micrometer registry implementation is on the classpath. The following example disables Datadog:
management:
datadog:
metrics:
export:
enabled: false
You can also disable all registries unless stated otherwise by the registry-specific property, as the following example shows:
management:
defaults:
metrics:
export:
enabled: false
Spring Boot also adds any auto-configured registries to the global static composite registry on the Metrics class, unless you explicitly tell it not to:
management:
metrics:
use-global-registry: false
You can register any number of MeterRegistryCustomizer beans to further configure the registry, such as applying common tags, before any meters are registered with the registry:
@Component
public class MyMeterRegistryConfiguration {
@Bean
public MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
return registry -> registry.config().commonTags("region", "us-east-1");
}
}
You can apply customizations to particular registry implementations by being more specific about the generic type:
@Component
public class MyMeterRegistryConfiguration {
@Bean
public MeterRegistryCustomizer<GraphiteMeterRegistry> graphiteMetricsNamingConvention() {
return registry -> registry.config().namingConvention(this::toGraphiteConvention);
}
private String toGraphiteConvention(String name, Meter.Type type, String baseUnit) {
return name.toLowerCase().replace(".", "_");
}
}
Spring Boot also configures built-in instrumentation that you can control through configuration or dedicated annotation markers.
Supported Metrics
Spring Boot provides automatic meter registration for a wide variety of technologies. In most situations, the defaults provide sensible metrics that can be published to any of the supported monitoring systems.
JVM Metrics
JVM metrics are published under the jvm. meter name. The following JVM metrics are provided:
- Memory and buffer pools
- Statistics related to garbage collection
- Thread utilization
- Number of classes loaded/unloaded
System Metrics
System metrics are published under the system., process., and disk. meter names. The following system metrics are provided:
- CPU metrics
- File descriptor metrics
- Uptime metrics
- Disk space metrics
Application Startup Metrics
Application startup metrics are published under the application.started.time meter name. The following startup metrics are provided:
- Application startup time
- Application ready time
HTTP Request Metrics
HTTP request metrics are automatically recorded for all HTTP requests. Metrics are published under the http.server.requests meter name.
Tags added to HTTP server request metrics:
method: The request's HTTP method (e.g.,GETorPOST)uri: The request's URI template prior to variable substitution (e.g.,/api/person/{id})status: The response's HTTP status code (e.g.,200or500)outcome: The request's outcome based on the status code (SUCCESS,REDIRECTION,CLIENT_ERROR,SERVER_ERROR, orUNKNOWN)
To customize the tags, provide a @Bean that implements WebMvcTagsContributor:
@Component
public class MyWebMvcTagsContributor implements WebMvcTagsContributor {
@Override
public Iterable<Tag> getTags(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Throwable exception) {
return Tags.of("custom.tag", "custom-value");
}
@Override
public Iterable<Tag> getLongRequestTags(HttpServletRequest request,
Object handler) {
return Tags.of("custom.tag", "custom-value");
}
}
WebFlux Metrics
WebFlux metrics are automatically recorded for all WebFlux requests. Metrics are published under the http.server.requests meter name.
Tags added to WebFlux request metrics:
method: The request's HTTP methoduri: The request's URI templatestatus: The response's HTTP status codeoutcome: The request's outcome
Data Source Metrics
Auto-configuration enables the instrumentation of all available DataSource objects with metrics prefixed with hikaricp., tomcat.datasource., or dbcp2..
Connection pool metrics are published under the following meter names:
hikaricp.connections(HikariCP)tomcat.datasource.connections(Tomcat)dbcp2.connections(Apache DBCP2)
Cache Metrics
Auto-configuration enables the instrumentation of all available Cache managers on startup with metrics prefixed with cache.. The cache instrumentation is standardized for a basic set of metrics.
Cache metrics include:
- Size
- Hit ratio
- Evictions
- Puts and misses
Task Execution and Scheduling Metrics
Auto-configuration enables the instrumentation of all available ThreadPoolTaskExecutor and ThreadPoolTaskScheduler beans with metrics prefixed with executor. and scheduler. respectively.
Executor metrics include:
- Active threads
- Pool size
- Queue size
- Task completion
Custom Metrics
To record your own metrics, inject MeterRegistry into your component:
@Component
public class MyService {
private final Counter counter;
private final Timer timer;
private final Gauge gauge;
public MyService(MeterRegistry meterRegistry) {
this.counter = Counter.builder("my.counter")
.description("A simple counter")
.register(meterRegistry);
this.timer = Timer.builder("my.timer")
.description("A simple timer")
.register(meterRegistry);
this.gauge = Gauge.builder("my.gauge")
.description("A simple gauge")
.register(meterRegistry, this, MyService::calculateGaugeValue);
}
public void doSomething() {
counter.increment();
Timer.Sample sample = Timer.start(meterRegistry);
// ... do work
sample.stop(timer);
}
private double calculateGaugeValue(MyService self) {
return Math.random();
}
}
Using @Timed Annotation
You can use the @Timed annotation to time method executions:
@Component
public class MyService {
@Timed(name = "my.method.time", description = "Time taken to execute my method")
public void timedMethod() {
// method body
}
}
For the @Timed annotation to work, you need to enable timing support:
@Configuration
@EnableConfigurationProperties
public class TimedConfiguration {
@Bean
public TimedAspect timedAspect(MeterRegistry registry) {
return new TimedAspect(registry);
}
}
Using @Counted Annotation
You can use the @Counted annotation to count method invocations:
@Component
public class MyService {
@Counted(name = "my.method.count", description = "Number of times my method is called")
public void countedMethod() {
// method body
}
}
For the @Counted annotation to work, you need to enable counting support:
@Configuration
public class CountedConfiguration {
@Bean
public CountedAspect countedAspect(MeterRegistry registry) {
return new CountedAspect(registry);
}
}
Meter Filters
You can register any number of MeterFilter beans to control how meters are registered:
@Configuration
public class MetricsConfiguration {
@Bean
public MeterFilter renameFilter() {
return MeterFilter.rename("old.metric.name", "new.metric.name");
}
@Bean
public MeterFilter denyFilter() {
return MeterFilter.deny(id -> id.getName().contains("unwanted"));
}
@Bean
public MeterFilter tagFilter() {
return MeterFilter.commonTags("application", "my-app");
}
}
Metrics Endpoint
The metrics endpoint provides access to all the metrics collected by the application. You can view the names of all available meters by visiting /actuator/metrics.
To view the value of a particular meter, specify its name as a path parameter:
GET /actuator/metrics/jvm.memory.used
The response contains the meter's measurements:
{
"name": "jvm.memory.used",
"description": "The amount of used memory",
"baseUnit": "bytes",
"measurements": [
{
"statistic": "VALUE",
"value": 8.73E8
}
],
"availableTags": [
{
"tag": "area",
"values": ["heap", "nonheap"]
},
{
"tag": "id",
"values": ["Compressed Class Space", "PS Eden Space", "PS Survivor Space"]
}
]
}
You can drill down to a particular meter by adding query parameters:
GET /actuator/metrics/jvm.memory.used?tag=area:heap&tag=id:PS%20Eden%20Space
Monitoring System Integration
Prometheus
To export metrics to Prometheus, add the following dependency:
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
This exposes a /actuator/prometheus endpoint that presents metrics in the format expected by a Prometheus server.
Configuration example:
management:
endpoints:
web:
exposure:
include: "prometheus"
metrics:
export:
prometheus:
enabled: true
step: 1m
descriptions: true
Datadog
To export metrics to Datadog, add the following dependency:
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-datadog</artifactId>
</dependency>
Configuration:
management:
metrics:
export:
datadog:
api-key: ${DATADOG_API_KEY}
application-key: ${DATADOG_APP_KEY}
uri: https://api.datadoghq.com
step: 1m
InfluxDB
To export metrics to InfluxDB, add the following dependency:
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-influx</artifactId>
</dependency>
Configuration:
management:
metrics:
export:
influx:
uri: http://localhost:8086
db: mydb
username: ${INFLUX_USERNAME}
password: ${INFLUX_PASSWORD}
step: 1m
New Relic
To export metrics to New Relic, add the following dependency:
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-newrelic</artifactId>
</dependency>
Configuration:
management:
metrics:
export:
newrelic:
api-key: ${NEW_RELIC_API_KEY}
account-id: ${NEW_RELIC_ACCOUNT_ID}
step: 1m
Simple Registry (In-Memory)
The simple registry is automatically configured if no other registry is found on the classpath. It stores metrics in memory and is useful for development and testing:
management:
metrics:
export:
simple:
enabled: true
step: 1m
Performance Considerations
Meter Cardinality
Be mindful of meter cardinality when adding tags. High-cardinality tags (like user IDs) can lead to performance issues:
// Bad - high cardinality
Timer.builder("user.request.time")
.tag("user.id", userId) // Could be millions of different values
.register(registry);
// Good - low cardinality
Timer.builder("user.request.time")
.tag("user.type", userType) // Limited number of values
.register(registry);
Sampling
For high-throughput applications, consider using sampling to reduce overhead:
@Bean
public MeterFilter samplingFilter() {
return MeterFilter.maximumExpectedValue("http.server.requests",
Duration.ofMillis(500));
}
Meter Registry Configuration
Configure appropriate publishing intervals to balance between timeliness and performance:
management:
metrics:
export:
prometheus:
step: 30s # Adjust based on your needs
Security Considerations
Sensitive Data
Be careful not to include sensitive information in metric tags or names:
// Bad - exposes sensitive data
Counter.builder("login.attempts")
.tag("username", username) // Could expose usernames
.register(registry);
// Good - uses hashed or anonymized data
Counter.builder("login.attempts")
.tag("outcome", successful ? "success" : "failure")
.register(registry);
Endpoint Security
Secure the metrics endpoint in production:
management:
endpoints:
web:
exposure:
include: "metrics"
endpoint:
metrics:
access: restricted
Or using Spring Security:
@Configuration
public class ActuatorSecurity {
@Bean
public SecurityFilterChain actuatorSecurityFilterChain(HttpSecurity http) throws Exception {
return http
.requestMatcher(EndpointRequest.toAnyEndpoint())
.authorizeHttpRequests(requests ->
requests.anyRequest().hasRole("ACTUATOR"))
.httpBasic(withDefaults())
.build();
}
}
Best Practices
- Use meaningful meter names: Follow naming conventions specific to your monitoring system
- Add appropriate tags: Use tags to add dimensions but avoid high cardinality
- Monitor meter cardinality: High cardinality can impact performance
- Use meter filters: Filter out unwanted metrics or rename meters
- Configure appropriate publishing intervals: Balance between timeliness and performance
- Secure sensitive endpoints: Protect metrics endpoints in production
- Test metrics in development: Verify metrics are collected correctly before deploying
- Document custom metrics: Maintain documentation for custom metrics and their purposes