-
Notifications
You must be signed in to change notification settings - Fork 0
State Listeners and Metrics
Frody edited this page Sep 3, 2026
·
1 revision
Fuse exposes a clean callback listener API to notify monitoring, logging, and metrics systems whenever a circuit breaker changes its operational state.
Register one or more state transition listeners using onStateChange:
circuitBreaker.onStateChange((name, fromState, toState) -> {
// Invoked whenever the circuit transitions (e.g., CLOSED -> OPEN)
});-
Thread Confinement Safety: Listeners are stored internally in a lock-free
java.util.concurrent.CopyOnWriteArrayList. Adding or executing listeners is completely thread-safe. -
Fault Isolation: If a registered listener throws an unhandled
RuntimeException, Fuse catches and swallows the exception internally. A faulty monitoring callback will never interrupt or break the client's business transaction.
Emit structured log records whenever the circuit degrades or recovers:
circuitBreaker.onStateChange((name, fromState, toState) -> {
if (toState == CircuitBreakerState.OPEN) {
log.error("ALERT: Circuit breaker '{}' TRIPPED from {} to {}. Downstream calls will be isolated.",
name, fromState, toState);
} else if (toState == CircuitBreakerState.CLOSED) {
log.info("RECOVERED: Circuit breaker '{}' RESTORED from {} to {}. Normal traffic resumed.",
name, fromState, toState);
} else {
log.warn("PROBING: Circuit breaker '{}' entered {}. Testing downstream recovery.",
name, toState);
}
});In Spring Boot or Micrometer-enabled applications, track the circuit breaker's state as a Prometheus gauge or state tag:
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tags;
import java.util.concurrent.atomic.AtomicInteger;
@Component
public class CircuitBreakerMetricsBinder {
public void bind(CircuitBreaker cb, MeterRegistry registry) {
// Map states to numeric values for Prometheus: 0 = CLOSED, 1 = HALF_OPEN, 2 = OPEN
AtomicInteger stateGauge = new AtomicInteger(stateToNumber(cb.getState()));
registry.gauge("circuitbreaker.state",
Tags.of("name", cb.getName()),
stateGauge,
AtomicInteger::get);
cb.onStateChange((name, from, to) -> {
stateGauge.set(stateToNumber(to));
registry.counter("circuitbreaker.transitions.total",
Tags.of("name", name, "from", from.name(), "to", to.name()))
.increment();
});
}
private int stateToNumber(CircuitBreakerState state) {
return switch (state) {
case CLOSED -> 0;
case HALF_OPEN -> 1;
case OPEN -> 2;
};
}
}Expose circuit breaker status in /actuator/health:
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
public class CircuitBreakerHealthIndicator implements HealthIndicator {
private final CircuitBreaker circuitBreaker;
public CircuitBreakerHealthIndicator(CircuitBreaker circuitBreaker) {
this.circuitBreaker = circuitBreaker;
}
@Override
public Health health() {
CircuitBreakerState state = circuitBreaker.getState();
return switch (state) {
case CLOSED -> Health.up()
.withDetail("circuit", circuitBreaker.getName())
.withDetail("state", state)
.build();
case HALF_OPEN -> Health.status("DEGRADED")
.withDetail("circuit", circuitBreaker.getName())
.withDetail("state", state)
.build();
case OPEN -> Health.down()
.withDetail("circuit", circuitBreaker.getName())
.withDetail("state", state)
.build();
};
}
}Fuse • Zero-Dependency Circuit Breaker for Java 17+ • Licensed under Apache-2.0