A reusable Spring Boot starter that bundles structured logging, distributed tracing (OpenTelemetry), and metrics (Micrometer) into a single dependency. Drop it into any Spring Boot 3.x app and get production-grade observability out of the box — no boilerplate required.
Every microservice needs observability, but wiring up structured logging, distributed tracing, and metrics from scratch is tedious and error-prone. Teams end up with inconsistent setups across services — different log formats, missing trace context, no correlation between logs and traces.
This starter solves that by providing a single, opinionated dependency that gives you:
- Structured JSON logging with
traceId,spanId,requestId, andcorrelationIdinjected into every log line automatically - Distributed tracing via OpenTelemetry with automatic span creation for HTTP requests and Kafka messages
- Metrics via Micrometer with JVM metrics out of the box and custom
@Timed/@Countedannotation support - Correlation ID propagation across service boundaries via
X-Correlation-Idheader - Graceful degradation — the app starts cleanly even if Jaeger or the OTLP endpoint is unreachable
- Kafka trace context propagation using W3C
traceparentheaders
graph TD
subgraph "Your Spring Boot App"
HTTP["HTTP Request"]
KAFKA["Kafka Message"]
subgraph "spring-boot-starter-observability"
RCF["RequestContextFilter\n(MDC: traceId, spanId,\ncorrelationId, requestId)"]
TI["TracingInterceptor\n(OTel SERVER span)"]
KP["Kafka Producer\nInterceptor"]
KC["Kafka Consumer\nInterceptor"]
TA["@Traced\n(method spans)"]
MA["@Timed / @Counted\n(Micrometer)"]
HI["Health Indicator\n(/actuator/health)"]
end
LOG["JSON Logs\n(Logback + MDC)"]
MET["Micrometer\nMeterRegistry"]
end
HTTP --> RCF --> TI --> TA
KAFKA --> KP
KAFKA --> KC
TA --> MA
TI -- "OTLP gRPC" --> JAEGER["Jaeger"]
MET -- "scrape" --> PROM["Prometheus"]
PROM --> GRAFANA["Grafana"]
LOG --> STDOUT["stdout / log aggregator"]
| Module | Description |
|---|---|
starter-core |
Shared constants and annotations (@Traced, @Timed, @Counted) |
starter-autoconfigure |
All Spring Boot auto-configuration (logging, tracing, metrics, health) |
starter-sample |
Sample CRUD API with full docker-compose stack (Jaeger + Prometheus + Grafana) |
Note: This library is under active development and not yet published to Maven Central. Build locally for now.
git clone https://github.com/ayushwing/spring-boot-starter-observability.git
cd spring-boot-starter-observability
mvn clean install -DskipTests<dependency>
<groupId>com.ayushwing</groupId>
<artifactId>observability-starter-autoconfigure</artifactId>
<version>0.1.0-SNAPSHOT</version>
</dependency>
<!-- Required transitive runtime deps -->
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>7.4</version>
</dependency>observability:
logging:
format: json # "json" (default) or "text"
include-headers: false # add request headers to MDC
header-filter: "X-Trace-Id,Accept" # whitelist of headers to include
include-request-info: true # add httpMethod + requestUri to MDC
service-name: ${spring.application.name}
custom-fields:
environment: production
region: us-east-1
tracing:
enabled: true
endpoint: http://localhost:4317 # OTLP gRPC endpoint
service-name: ${spring.application.name}
sampling-ratio: 1.0 # 0.0–1.0
exporter-timeout-ms: 30000
metrics:
enabled: trueEvery log line emitted during an HTTP request automatically includes:
{
"timestamp": "2024-03-15T10:22:45.123Z",
"level": "INFO",
"logger": "com.example.OrderService",
"message": "Order placed successfully",
"traceId": "a3f1c2d4...",
"spanId": "b7e8f9a0",
"requestId": "550e8400-e29b-41d4-a716-446655440000",
"correlationId": "7f3a1b2c-...",
"httpMethod": "POST",
"requestUri": "/api/orders",
"service": "order-service"
}HTTP spans are created automatically for every request. Use @Traced for custom method-level spans:
@Traced("payment.charge")
public Receipt chargeCustomer(String customerId, BigDecimal amount) {
// This method runs inside its own OpenTelemetry span
// named "payment.charge" in Jaeger
}
// Exception details are automatically recorded on the span
@Traced(recordException = true)
public Inventory checkStock(String productId) { ... }Kafka trace context propagates automatically via W3C traceparent headers — no code changes needed.
JVM metrics (memory, GC, threads, CPU) are registered automatically. Add per-method instrumentation with annotations:
@Timed("orders.process.duration")
public Order processOrder(String orderId) {
// Records execution time with tags: class, method, exception
}
@Counted("payments.initiated")
public void initiatePayment(Payment payment) {
// Increments counter with tags: class, method, result (success/failure)
}The X-Correlation-Id header is read on inbound requests (or generated if absent) and:
- Stored in MDC as
correlationId— appears in every log line - Returned in the response header
- Should be forwarded in downstream HTTP calls for full cross-service traceability
When spring-boot-actuator is present, a dedicated health indicator is available:
GET /actuator/health/observability
{
"status": "UP",
"details": {
"metrics": "UP",
"tracing": "UP",
"logging": "UP"
}
}
The starter-sample module contains a full product CRUD API wired up to the complete observability stack.
cd starter-sample
mvn package -DskipTests -pl .. -am
docker compose up -d| Service | URL |
|---|---|
| Sample API | http://localhost:8080/api/products |
| Actuator Health | http://localhost:8080/actuator/health |
| Jaeger UI | http://localhost:16686 |
| Prometheus | http://localhost:9090 |
| Grafana | http://localhost:3000 (admin / admin) |
Try a few requests and watch traces appear in Jaeger:
# List products
curl http://localhost:8080/api/products
# Create a product
curl -X POST http://localhost:8080/api/products \
-H "Content-Type: application/json" \
-d '{"name":"Mechanical Keyboard","category":"electronics","price":149.99}'
# Check the correlation ID in the response header
curl -v http://localhost:8080/api/products 2>&1 | grep X-Correlation-Id| Property | Default | Description |
|---|---|---|
format |
json |
Log format: json or text |
include-headers |
false |
Include request headers in MDC |
header-filter |
"" |
Comma-separated header whitelist (empty = all) |
include-mdc |
true |
Include trace context in log output |
include-request-info |
true |
Add httpMethod and requestUri to MDC |
service-name |
(spring.application.name) | Service name in log output |
custom-fields |
{} |
Static MDC key-value pairs added to every log line |
| Property | Default | Description |
|---|---|---|
enabled |
true |
Enable distributed tracing |
endpoint |
http://localhost:4317 |
OTLP gRPC exporter endpoint |
service-name |
(spring.application.name) | Service name in spans |
sampling-ratio |
1.0 |
Sampling ratio (0.0–1.0) |
exporter-timeout-ms |
30000 |
Flush timeout on shutdown (ms) |
| Property | Default | Description |
|---|---|---|
enabled |
true |
Enable Micrometer metrics auto-configuration |
- Java 17+
- Spring Boot 3.2
- OpenTelemetry SDK 1.36
- Micrometer 1.12
- SLF4J + Logback + logstash-logback-encoder
- JUnit 5 + Testcontainers + Mockito
See CONTRIBUTING.md. Issues and PRs welcome.