Observability — Logs, Metrics & Distributed Tracing
The three pillars of observability, centralized and correlated logging, metrics with Micrometer and Prometheus, RED and USE methods, distributed tracing with Micrometer Tracing and OpenTelemetry, health checks, dashboards and alerting.
#Introduction
A user reports "checkout is slow". The request passed through the gateway, the order, pricing, inventory and payment services and Kafka. Which one is slow? Without observability, you're guessing. Observability means being able to answer questions about a running system from the data it emits.
#The Three Pillars
| Pillar | Answers | Tools |
|---|---|---|
| Logs | What happened in detail, for one event | Logback/Log4j2 → ELK / OpenSearch, Grafana Loki, Splunk |
| Metrics | How much / how fast / how often, aggregated over time | Micrometer → Prometheus → Grafana; Datadog; CloudWatch |
| Traces | Where the time went across services for one request | Micrometer Tracing / OpenTelemetry → Zipkin, Jaeger, Grafana Tempo |
They work best linked together: a dashboard shows a latency spike (metrics) → you open a slow trace (tracing) → you jump to that trace's logs (logs) by its trace ID.
#2. Metrics — Micrometer and Prometheus
Micrometer is the metrics facade in Spring Boot (like SLF4J for metrics). Actuator plus the Prometheus registry exposes metrics at /actuator/prometheus:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>management:
endpoints:
web:
exposure:
include: health,info,prometheus
metrics:
tags:
application: ${spring.application.name}Out of the box you get HTTP request timings, JVM memory and GC, thread pools, HikariCP connections, Kafka clients, Resilience4j and more. Prometheus scrapes each instance; Grafana draws dashboards.
Custom Business Metrics
@Service
public class CheckoutService {
private final Counter ordersPlaced;
private final Timer paymentTimer;
public CheckoutService(MeterRegistry registry) {
this.ordersPlaced = Counter.builder("orders.placed").tag("channel", "web").register(registry);
this.paymentTimer = Timer.builder("payment.duration").publishPercentiles(0.95, 0.99).register(registry);
}
public void checkout(Order order) {
paymentTimer.record(() -> paymentClient.charge(order));
ordersPlaced.increment();
}
}| Meter | Use |
|---|---|
Counter | Things that only go up — orders placed, errors |
Gauge | A current value — queue size, active sessions |
Timer | Durations and rates — request latency, percentiles |
DistributionSummary | Sizes — payload bytes, basket value |
What to Measure
| Method | For | Signals |
|---|---|---|
| RED | Request-driven services | Rate (requests/s), Errors (failed requests), Duration (latency percentiles) |
| USE | Resources (CPU, pools, queues) | Utilisation, Saturation, Errors |
| Four golden signals (Google SRE) | Any user-facing system | Latency, traffic, errors, saturation |
Track latency as percentiles (p50, p95, p99), not averages — averages hide the slow requests users actually notice.
#3. Distributed Tracing
A trace follows one request across services. Each unit of work is a span; spans share a trace ID and form a tree:
- Trace 4bf92f35… — POST /checkout, 1.84 s
- gateway — 1.84 s
- order-service: POST /orders — 1.80 s
- pricing-service: GET /prices — 0.12 s
- inventory-service: POST /reservations — 0.20 s
- payment-service: POST /charges — 1.35 sthe slow part
- external card gateway — 1.30 s
- Kafka publish OrderPlaced — 0.01 s
The waterfall view in Zipkin or Jaeger shows instantly that the card gateway is the bottleneck.
Tracing in Spring Boot 3
Spring Cloud Sleuth has been replaced by Micrometer Tracing, with an OpenTelemetry (or Brave) bridge and an exporter:
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-otel</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-zipkin</artifactId> <!-- or the OTLP exporter -->
</dependency>management:
tracing:
sampling:
probability: 0.1 # trace 10% of requests in production (1.0 in development)
zipkin:
tracing:
endpoint: http://zipkin:9411/api/v2/spansSpring automatically:
- creates spans for incoming HTTP requests,
RestClient/WebClient/ Feign calls,@KafkaListenerandKafkaTemplate, JDBC (with extra instrumentation) and@Observedmethods; - propagates the context in W3C
traceparentheaders — over HTTP, and in Kafka record headers when observation is enabled on the template and listener; - puts
traceId/spanIdinto log lines.
#Health Checks
management:
endpoint:
health:
probes:
enabled: true # enabled automatically on Kubernetes
show-details: when-authorized| Endpoint | Meaning | Used by |
|---|---|---|
/actuator/health/liveness | The app is running and not stuck — if it fails, restart it | Kubernetes liveness probe |
/actuator/health/readiness | The app can serve traffic now — if it fails, stop sending traffic | Kubernetes readiness probe, load balancers |
#Dashboards and Alerting
- One dashboard per service: RED metrics, JVM, connection pools, Kafka consumer lag, circuit-breaker states.
- Alert on symptoms users feel — error rate above X%, p99 latency above the SLO, growing consumer lag — not on every CPU spike.
- SLOs (service-level objectives), e.g. "99.9% of checkout requests succeed within 1 second over 30 days", turn monitoring into clear targets, with error budgets guiding release speed.
- Every alert should link to a runbook describing what to check and how to mitigate.
#Interview Questions
Q1What are the three pillars of observability?
Logs (detailed events), metrics (aggregated numbers over time) and traces (the path and timing of a request across services), ideally linked by trace IDs.
Q2How do you debug a slow request across microservices?
Use distributed tracing to find the slow span, then look at that service's logs for the same trace ID and its metrics (latency, pool saturation, GC) to find the cause.
Q3What replaced Spring Cloud Sleuth?
Micrometer Tracing in Spring Boot 3, with OpenTelemetry or Brave bridges and exporters such as Zipkin or OTLP.
Q4What is a trace ID and how is it propagated?
A unique ID shared by all spans of one request; it travels between services in headers (W3C traceparent) for HTTP calls and in record headers for messaging, and is added to log lines via the MDC.
Q5What is the RED method?
Monitoring each service's request rate, error rate and request duration (latency percentiles).
Q6Liveness vs readiness probes?
Liveness answers "should this container be restarted?"; readiness answers "should this instance receive traffic right now?".
#Key Takeaways
- Logs, metrics and traces answer different questions — link them with trace IDs.
- Centralize structured JSON logs with
traceIdon every line. - Micrometer + Prometheus + Grafana for metrics; watch RED signals and percentiles; avoid high-cardinality tags.
- Micrometer Tracing with OpenTelemetry replaces Sleuth; context propagates through HTTP and Kafka.
- Separate liveness from readiness; alert on user-facing symptoms and SLOs.