Actuator & Observability

Actuator

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

Only /actuator/health is exposed over HTTP by default. Open more explicitly:

management.endpoints.web.exposure.include=health,info,metrics,env,loggers
management.endpoint.health.show-details=when-authorized
EndpointShows
/actuator/healthup/down, plus per-component checks
/actuator/infobuild and git info
/actuator/metricscounters, gauges, timers
/actuator/loggerslog levels, changeable at runtime
/actuator/envresolved configuration
/actuator/prometheusmetrics in Prometheus format
curl localhost:8080/actuator/health
curl localhost:8080/actuator/metrics/http.server.requests
Warning

env, heapdump and threaddump leak internals. Expose them only behind authentication, or move the whole actuator to its own port: management.server.port=9001.

Health for Kubernetes

management.endpoint.health.probes.enabled=true
  • /actuator/health/liveness — is the process healthy? restart if not
  • /actuator/health/readiness — can it take traffic? remove from the load balancer if not

Custom check:

@Component
class QueueHealthIndicator implements HealthIndicator {
    @Override public Health health() {
        int depth = queue.depth();
        return depth < 1000
            ? Health.up().withDetail("depth", depth).build()
            : Health.down().withDetail("depth", depth).build();
    }
}

Metrics

Micrometer is the API; the backend is a dependency choice.

@Service
class BookService {

    private final Counter created;

    BookService(MeterRegistry registry) {
        this.created = registry.counter("books.created");
    }

    Book create(BookRequest r) {
        created.increment();
        return ;
    }
}
@Timed("books.search")            // needs @EnableAspectJAutoProxy + aop starter
List<Book> search(String q) {  }

Export to Prometheus:

<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
    <scope>runtime</scope>
</dependency>

Tracing

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-opentelemetry</artifactId>
</dependency>

New in Spring Boot 4: one starter for OpenTelemetry metrics and traces.

management.tracing.export.enabled=true
management.tracing.sampling.probability=0.1
spring.application.name=demo

Trace and span ids land in the log pattern automatically, so a log line can be matched to a trace.

Logging

logging.level.root=INFO
logging.level.com.example=DEBUG
logging.file.name=logs/app.log
logging.console.enabled=true          # new in Spring Boot 4: set false in container setups
logging.structured.format.console=ecs # JSON logs: ecs, gelf, logstash
private static final Logger log = LoggerFactory.getLogger(BookService.class);

log.info("Created book id={} title={}", id, title);   // placeholders, not concatenation

Build info in /actuator/info

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <executions><execution><goals><goal>build-info</goal></goals></execution></executions>
</plugin>

★ Exercises

  1. Expose health, info and metrics and call all three.
  2. Add build-info and check /actuator/info.
  3. Write a HealthIndicator that reports down when a property is set.
  4. Count something with a Micrometer Counter and read it from /actuator/metrics.
  5. Change a log level at runtime through /actuator/loggers.