Boost Your Spring Boot App: Custom Metrics and Health Checks Made Easy
Aarav Joshi
Posted on November 23, 2024
Spring Boot Actuator is a powerful tool for monitoring and managing your applications. But what if you want to go beyond the basics? Let's explore how to create custom metrics and health indicators that give you deeper insights into your app's behavior.
First, let's talk about custom metrics. Micrometer, the metrics library used by Spring Boot, makes it easy to create your own. Here's a simple example:
@Component
public class OrderMetrics {
private final MeterRegistry meterRegistry;
public OrderMetrics(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
meterRegistry.gauge("orders.pending", this, OrderMetrics::getPendingOrders);
}
private int getPendingOrders() {
// Logic to get pending orders count
return 10;
}
}
This code creates a gauge metric that tracks pending orders. You can now see this metric in your actuator endpoints or send it to your monitoring system.
But what if you need something more complex? Let's say you want to track the processing time of orders. You can use a Timer for this:
@Service
public class OrderService {
private final Timer orderProcessingTimer;
public OrderService(MeterRegistry meterRegistry) {
this.orderProcessingTimer = meterRegistry.timer("order.processing");
}
public void processOrder(Order order) {
orderProcessingTimer.record(() -> {
// Order processing logic
});
}
}
This code will track how long it takes to process each order. You can then use this data to set up alerts or optimize your system.
Now, let's move on to health indicators. Spring Boot comes with several built-in health indicators, but you can create your own for specific needs. Here's an example of a custom health indicator that checks if a critical external service is up:
@Component
public class ExternalServiceHealthIndicator implements HealthIndicator {
@Override
public Health health() {
if (isExternalServiceUp()) {
return Health.up().build();
}
return Health.down().withDetail("reason", "External service is down").build();
}
private boolean isExternalServiceUp() {
// Logic to check external service
return true;
}
}
This health indicator will show up in your /actuator/health endpoint, giving you a quick way to check if this critical service is available.
But what if you have multiple services to check? You can create a composite health indicator:
@Component
public class CompositeExternalServicesHealthIndicator implements CompositeHealthContributor {
private final Map<String, HealthIndicator> indicators;
public CompositeExternalServicesHealthIndicator(
ServiceAHealthIndicator serviceA,
ServiceBHealthIndicator serviceB) {
indicators = Map.of(
"serviceA", serviceA,
"serviceB", serviceB
);
}
@Override
public HealthContributor getContributor(String name) {
return indicators.get(name);
}
@Override
public Iterator<NamedContributor<HealthContributor>> iterator() {
return indicators.entrySet().stream()
.map(entry -> NamedContributor.of(entry.getKey(), entry.getValue()))
.iterator();
}
}
This composite indicator combines multiple health checks into one, giving you a comprehensive view of your system's health.
Now, let's talk about custom endpoints. While Spring Boot provides many useful endpoints out of the box, you might need something specific to your application. Here's how you can create a custom endpoint:
@Component
@Endpoint(id = "customInfo")
public class CustomInfoEndpoint {
@ReadOperation
public Map<String, Object> info() {
Map<String, Object> info = new HashMap<>();
info.put("appName", "MyAwesomeApp");
info.put("version", "1.0.0");
info.put("environment", "production");
return info;
}
}
This endpoint will be available at /actuator/customInfo and return some basic information about your app.
But what about security? You don't want just anyone accessing your sensitive actuator endpoints. Spring Boot makes it easy to secure these endpoints:
@Configuration
public class ActuatorSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatcher(EndpointRequest.toAnyEndpoint())
.authorizeRequests()
.anyRequest().hasRole("ACTUATOR")
.and()
.httpBasic();
}
}
This configuration ensures that only users with the ACTUATOR role can access the actuator endpoints.
Let's dive deeper into metrics. Micrometer supports various monitoring systems like Prometheus, Graphite, and InfluxDB. Here's how you can set up Prometheus:
First, add the dependency:
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
Then, enable the Prometheus endpoint in your application.properties:
management.endpoints.web.exposure.include=prometheus
Now you can scrape metrics from /actuator/prometheus.
But what if you want to track something specific to your business logic? Let's say you want to track how many users are currently logged in:
@Service
public class UserService {
private final AtomicInteger loggedInUsers;
public UserService(MeterRegistry meterRegistry) {
this.loggedInUsers = meterRegistry.gauge("users.logged.in", new AtomicInteger(0));
}
public void userLoggedIn() {
loggedInUsers.incrementAndGet();
}
public void userLoggedOut() {
loggedInUsers.decrementAndGet();
}
}
This code creates a gauge that tracks the number of logged-in users. You can now monitor this metric and set up alerts if it drops below or exceeds certain thresholds.
Let's talk about dynamic logging configurations. Spring Boot Actuator allows you to change log levels at runtime. Here's how you can implement a custom endpoint to do this:
@Component
@Endpoint(id = "logging")
public class LoggingEndpoint {
@WriteOperation
public void configureLogger(@Selector String loggerName, @Nullable String logLevel) {
Logger logger = LoggerFactory.getLogger(loggerName);
if (logger instanceof ch.qos.logback.classic.Logger) {
ch.qos.logback.classic.Logger logbackLogger = (ch.qos.logback.classic.Logger) logger;
logbackLogger.setLevel(Level.toLevel(logLevel));
}
}
}
With this endpoint, you can change log levels on the fly by sending a POST request to /actuator/logging.
Now, let's address performance. In high-load scenarios, you might need to optimize your actuator endpoints. One way to do this is by using caching:
@Component
@Endpoint(id = "expensiveOperation")
public class ExpensiveOperationEndpoint {
private final Cache<String, Object> cache;
public ExpensiveOperationEndpoint() {
this.cache = Caffeine.newBuilder()
.expireAfterWrite(1, TimeUnit.MINUTES)
.build();
}
@ReadOperation
public Object getExpensiveData(@Selector String key) {
return cache.get(key, this::fetchExpensiveData);
}
private Object fetchExpensiveData(String key) {
// Expensive operation to fetch data
return "Expensive data for " + key;
}
}
This endpoint uses Caffeine cache to store the results of expensive operations for a minute, reducing the load on your system.
Let's wrap up with a complex example that brings together several concepts we've discussed. Imagine you're building a web store and want to monitor various aspects of it:
@Configuration
public class WebStoreMonitoring {
private final MeterRegistry meterRegistry;
public WebStoreMonitoring(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
@Bean
public Counter orderCounter() {
return Counter.builder("orders.total")
.description("Total number of orders")
.register(meterRegistry);
}
@Bean
public Timer orderProcessingTimer() {
return Timer.builder("orders.processing.time")
.description("Time taken to process orders")
.register(meterRegistry);
}
@Bean
public Gauge inventoryGauge(InventoryService inventoryService) {
return Gauge.builder("inventory.total", inventoryService::getTotalItems)
.description("Total items in inventory")
.register(meterRegistry);
}
@Bean
public HealthIndicator inventoryHealthIndicator(InventoryService inventoryService) {
return () -> {
if (inventoryService.getTotalItems() > 0) {
return Health.up().build();
}
return Health.down().withDetail("reason", "Out of stock").build();
};
}
@Bean
public Timer paymentProcessingTimer() {
return Timer.builder("payments.processing.time")
.description("Time taken to process payments")
.register(meterRegistry);
}
}
This configuration sets up various metrics and health indicators for a web store. It tracks the total number of orders, time taken to process orders, total items in inventory, and time taken to process payments. It also includes a health indicator that checks if there are items in stock.
You can use these metrics to monitor your web store's performance and health. For example, you could set up alerts if the order processing time exceeds a certain threshold, or if the inventory drops below a certain level.
Remember, the key to effective monitoring is to track metrics that are meaningful to your specific application. Don't just track everything because you can. Think about what data would be most useful for understanding your application's behavior and health.
In conclusion, Spring Boot Actuator and Micrometer provide powerful tools for monitoring and managing your applications. By creating custom metrics, health indicators, and endpoints, you can gain deep insights into your application's behavior and health. With the techniques we've discussed, you can create a comprehensive, application-specific monitoring and management solution that goes far beyond the basics provided out of the box.
Our Creations
Be sure to check out our creations:
Investor Central | Smart Living | Epochs & Echoes | Puzzling Mysteries | Hindutva | Elite Dev | JS Schools
We are on Medium
Tech Koala Insights | Epochs & Echoes World | Investor Central Medium | Puzzling Mysteries Medium | Science & Epochs Medium | Modern Hindutva
Posted on November 23, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 28, 2024
November 27, 2024
November 23, 2024
November 21, 2024