This guide covers the comprehensive observability and monitoring features of the Firefly Common Client Library, including metrics, tracing, health checks, and logging.
- Overview
- Metrics with Micrometer
- Distributed Tracing
- Health Checks
- Request/Response Logging
- Performance Metrics
- Spring Boot Actuator Integration
- Prometheus & Grafana
- Best Practices
The library provides enterprise-grade observability features out of the box:
| Feature | Technology | Description |
|---|---|---|
| Metrics | Micrometer | Request counts, durations, error rates, circuit breaker states |
| Tracing | OpenTelemetry/Zipkin | Distributed tracing across microservices |
| Health Checks | Spring Boot Actuator | Service health monitoring and diagnostics |
| Logging | SLF4J/Logback | Structured logging with correlation IDs |
| Performance | Custom Collectors | Throughput, latency percentiles, cache metrics |
The library automatically collects the following metrics:
service.client.requests.success{service, client.type}
- Total number of successful requests (2xx status codes)
- Tags: service name, client type (REST/gRPC/SOAP)
service.client.requests.failure{service, client.type}
- Total number of failed requests (4xx/5xx status codes)
- Tags: service name, client type
service.client.requests.duration{service, client.type}
- Request duration timer with percentiles (p50, p95, p99)
- Tags: service name, client type
service.client.circuit.breaker.state{service, client.type}
- Current circuit breaker state (0=CLOSED, 1=OPEN, 2=HALF_OPEN)
- Tags: service name, client type
service.client.circuit.breaker.transitions{service, from.state, to.state}
- Circuit breaker state transition counter
- Tags: service name, from state, to state
service.client.errors{service, client.type, error.type}
- Error count by type
- Tags: service name, client type, error type
service.client.performance.requests.total{service}
- Total requests counter
service.client.performance.requests.success{service}
- Successful requests (2xx)
service.client.performance.requests.client.errors{service}
- Client errors (4xx)
service.client.performance.requests.server.errors{service}
- Server errors (5xx)
service.client.performance.retries{service}
- Retry attempts counter
service.client.performance.timeouts{service}
- Timeout counter
service.client.performance.request.duration{service}
- Request duration with percentiles
service.client.performance.response.size{service}
- Response size distribution in bytes
service.client.performance.concurrent.requests{service}
- Current concurrent requests gauge
service.client.performance.cache.hits{service, cache.type}
- Cache hit counter
service.client.performance.cache.misses{service, cache.type}
- Cache miss counter
service.client.performance.connection.pool.active{service}
- Active connections in pool
service.client.performance.connection.pool.idle{service}
- Idle connections in pool
Enable metrics in your application.yml:
management:
metrics:
enable:
service.client: true
export:
prometheus:
enabled: true
distribution:
percentiles-histogram:
service.client.requests.duration: true
service.client.performance.request.duration: true@Autowired
private ServiceClientMetrics metrics;
// Get total requests
long totalRequests = metrics.getTotalRequests("user-service");
// Get success rate
double successRate = metrics.getSuccessRate("user-service");
// Get circuit breaker state
CircuitBreakerState state = metrics.getCircuitBreakerState("user-service");The library supports both Brave (Zipkin) and OpenTelemetry for distributed tracing.
management:
tracing:
sampling:
probability: 1.0 # Sample 100% of requests (adjust for production)
zipkin:
tracing:
endpoint: http://localhost:9411/api/v2/spansTraces are automatically propagated across service boundaries:
// Trace context is automatically propagated
Mono<User> user = restClient.get("/users/{id}", User.class)
.pathVariable("id", "123")
.execute();
// Trace ID and Span ID are included in headersAdd custom spans for detailed tracing:
@Autowired
private Tracer tracer;
public Mono<Result> complexOperation() {
Span span = tracer.nextSpan().name("complex-operation").start();
try (Tracer.SpanInScope ws = tracer.withSpan(span)) {
return performOperation()
.doOnSuccess(result -> span.tag("result.size", String.valueOf(result.size())))
.doOnError(error -> span.error(error))
.doFinally(signal -> span.end());
}
}The library automatically adds the following attributes to traces:
service.name- Target service namehttp.method- HTTP methodhttp.url- Request URLhttp.status_code- Response status codeclient.type- Client type (REST/gRPC/SOAP)circuit.breaker.state- Circuit breaker stateretry.attempt- Retry attempt number (if applicable)
The library provides a custom health indicator that integrates with Spring Boot Actuator.
management:
health:
service-clients:
enabled: true
defaults:
enabled: true
endpoint:
health:
show-details: always
show-components: alwayscurl http://localhost:8080/actuator/health{
"status": "UP",
"components": {
"serviceClients": {
"status": "UP",
"details": {
"overallState": "HEALTHY",
"totalServices": 3,
"healthyServices": 3,
"degradedServices": 0,
"unhealthyServices": 0,
"services": {
"user-service": {
"state": "HEALTHY",
"consecutiveFailures": 0,
"lastCheckTime": "2025-10-25T10:30:00Z",
"timeSinceLastCheck": "5000ms",
"message": "Health check successful"
},
"payment-service": {
"state": "HEALTHY",
"consecutiveFailures": 0,
"lastCheckTime": "2025-10-25T10:30:00Z",
"timeSinceLastCheck": "5000ms",
"message": "Health check successful"
}
}
}
}
}
}@Autowired
private ServiceClientHealthManager healthManager;
// Register a client for health monitoring
healthManager.registerClient(serviceClient);
// Start health monitoring
healthManager.start();
// Get health status
ServiceClientHealthStatus status = healthManager.getHealthStatus("user-service");
if (status.isHealthy()) {
log.info("Service is healthy");
}
// Get overall health state
OverallHealthState overallState = healthManager.getOverallHealthState();Implement custom health check logic:
@Component
public class CustomServiceHealthCheck {
@Autowired
private ServiceClient serviceClient;
@Scheduled(fixedRate = 30000)
public void performHealthCheck() {
serviceClient.healthCheck()
.doOnSuccess(v -> log.info("Health check passed"))
.doOnError(error -> log.error("Health check failed", error))
.subscribe();
}
}The library provides a comprehensive logging interceptor with configurable detail levels.
RequestResponseLoggingInterceptor loggingInterceptor =
RequestResponseLoggingInterceptor.builder()
.logLevel(LogLevel.HEADERS)
.maskSensitiveHeaders(true)
.maxBodyLogSize(1024)
.includeTimings(true)
.includeStatistics(true)
.build();
WebClient client = WebClient.builder()
.filter(loggingInterceptor.filter())
.build();| Level | Description | Logs |
|---|---|---|
NONE |
No logging | Nothing |
BASIC |
Basic info | Method, URL, status code |
HEADERS |
Include headers | Basic + request/response headers |
FULL |
Everything | Headers + request/response bodies |
[Request #1234] GET http://user-service/users/123
Headers: {Authorization=[***MASKED***], Content-Type=[application/json]}
[Response #1234] GET http://user-service/users/123 -> 200 OK [150ms]
Headers: {Content-Type=[application/json], Content-Length=[256]}
By default, the following headers are masked:
AuthorizationX-API-KeyX-Auth-TokenCookieSet-CookieProxy-Authorization
Add custom sensitive headers:
Set<String> customSensitiveHeaders = Set.of("X-Custom-Secret", "X-Internal-Token");
RequestResponseLoggingInterceptor interceptor =
RequestResponseLoggingInterceptor.builder()
.maskSensitiveHeaders(true)
.additionalSensitiveHeaders(customSensitiveHeaders)
.build();Get statistics from the logging interceptor:
Map<String, RequestStatistics> stats = loggingInterceptor.getStatistics();
stats.forEach((endpoint, statistics) -> {
log.info("Endpoint: {} - Total: {}, Success Rate: {:.2f}%, Avg Duration: {:.2f}ms",
endpoint,
statistics.getTotalRequests(),
statistics.getSuccessRate(),
statistics.getAverageDurationMs());
});The PerformanceMetricsCollector provides advanced performance tracking.
@Autowired
private MeterRegistry meterRegistry;
PerformanceMetricsCollector collector = new PerformanceMetricsCollector(meterRegistry);
// Record a request
collector.recordRequest("user-service", "GET", "/users/123",
Duration.ofMillis(150), 200, 1024);
// Record retry
collector.recordRetry("user-service", 2);
// Record timeout
collector.recordTimeout("user-service");
// Record cache hit/miss
collector.recordCacheHit("user-service", "query");
collector.recordCacheMiss("user-service", "query");
// Get summary
PerformanceMetricsSummary summary = collector.getSummary("user-service");
log.info("Service: {}, RPS: {:.2f}, Success Rate: {:.2f}%, Avg Duration: {:.2f}ms",
summary.getServiceName(),
summary.getRequestsPerSecond(),
summary.getSuccessRate(),
summary.getAverageDurationMs());public class PerformanceMetricsSummary {
private String serviceName;
private long totalRequests;
private long successfulRequests;
private long clientErrors;
private long serverErrors;
private long retries;
private long timeouts;
private double averageDurationMs;
private double maxDurationMs;
private double requestsPerSecond;
private double successRate;
private double errorRate;
}The library integrates with Spring Boot Actuator to expose the following endpoints:
| Endpoint | Description |
|---|---|
/actuator/health |
Overall health status including service clients |
/actuator/metrics |
All Micrometer metrics |
/actuator/prometheus |
Prometheus-formatted metrics |
/actuator/info |
Application information |
management:
endpoints:
web:
exposure:
include: health,metrics,prometheus,info
base-path: /actuator
endpoint:
health:
show-details: always
show-components: always
metrics:
enabled: true
prometheus:
enabled: trueCreate a custom endpoint for service client statistics:
@Component
@Endpoint(id = "service-clients")
public class ServiceClientsEndpoint {
@Autowired
private ServiceClientMetrics metrics;
@ReadOperation
public Map<String, Object> serviceClients() {
Map<String, Object> result = new HashMap<>();
// Add custom statistics
return result;
}
}Add the library metrics to your prometheus.yml:
scrape_configs:
- job_name: 'spring-boot-app'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['localhost:8080']Import the provided Grafana dashboard or create custom panels:
rate(service_client_requests_success_total[5m])
rate(service_client_requests_failure_total[5m]) /
rate(service_client_requests_success_total[5m])
histogram_quantile(0.95,
rate(service_client_requests_duration_bucket[5m]))
service_client_circuit_breaker_state
management:
metrics:
enable:
service.client: truemanagement:
tracing:
sampling:
probability: 0.1 # 10% sampling in productionServiceClientHealthManager healthManager = new ServiceClientHealthManager(
Duration.ofSeconds(30), // Check every 30 seconds
Duration.ofSeconds(5), // Timeout after 5 seconds
3 // Mark unhealthy after 3 failures
);log.info("Request completed: service={}, endpoint={}, duration={}ms, status={}",
serviceName, endpoint, duration.toMillis(), statusCode);Set up alerts for circuit breaker state changes:
changes(service_client_circuit_breaker_state[5m]) > 0
Alert on high error rates:
rate(service_client_requests_failure_total[5m]) > 0.05
Alert on high p95 latency:
histogram_quantile(0.95,
rate(service_client_requests_duration_bucket[5m])) > 1000