diff --git a/pom.xml b/pom.xml
index c4f4d9b..16f257a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,7 +7,7 @@
org.fireflyframework
fireflyframework-parent
- 26.04.01
+ 26.05.06
fireflyframework-cache
@@ -110,11 +110,11 @@
true
-
+
- io.micrometer
- micrometer-core
- true
+ org.fireflyframework
+ fireflyframework-observability
+ ${project.version}
diff --git a/src/main/java/org/fireflyframework/cache/observability/CacheHealthIndicator.java b/src/main/java/org/fireflyframework/cache/observability/CacheHealthIndicator.java
new file mode 100644
index 0000000..dec2c80
--- /dev/null
+++ b/src/main/java/org/fireflyframework/cache/observability/CacheHealthIndicator.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2024-2026 Firefly Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.fireflyframework.cache.observability;
+
+import org.fireflyframework.cache.core.CacheAdapter;
+import org.fireflyframework.observability.health.FireflyHealthIndicator;
+import org.springframework.boot.actuate.health.Health;
+import reactor.core.publisher.Mono;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.UUID;
+
+/**
+ * Health indicator that pings the cache adapter with a synthetic read/write/delete
+ * cycle and reports the round-trip latency.
+ *
+ * Marks the cache as DOWN when the probe fails or exceeds {@code 1s}.
+ */
+public class CacheHealthIndicator extends FireflyHealthIndicator {
+
+ private static final Duration LATENCY_THRESHOLD = Duration.ofSeconds(1);
+ private static final String HEALTH_KEY_PREFIX = "firefly.cache.health.";
+
+ private final CacheAdapter cacheAdapter;
+ private final String adapterName;
+
+ public CacheHealthIndicator(CacheAdapter cacheAdapter, String adapterName) {
+ super("cache");
+ this.cacheAdapter = cacheAdapter;
+ this.adapterName = adapterName;
+ }
+
+ @Override
+ protected void doHealthCheck(Health.Builder builder) {
+ String probeKey = HEALTH_KEY_PREFIX + UUID.randomUUID();
+ String probeValue = "ok";
+
+ try {
+ Instant start = Instant.now();
+ cacheAdapter.put(probeKey, probeValue, Duration.ofSeconds(5))
+ .then(cacheAdapter.get(probeKey, String.class))
+ .then(cacheAdapter.evict(probeKey))
+ .block(Duration.ofSeconds(2));
+ Duration elapsed = Duration.between(start, Instant.now());
+
+ builder.up()
+ .withDetail("adapter", adapterName)
+ .withDetail("probe.latency.ms", elapsed.toMillis());
+
+ if (elapsed.compareTo(LATENCY_THRESHOLD) > 0) {
+ builder.status("DEGRADED")
+ .withDetail("threshold.ms", LATENCY_THRESHOLD.toMillis());
+ }
+ } catch (Exception e) {
+ builder.down(e)
+ .withDetail("adapter", adapterName);
+ }
+ }
+}
diff --git a/src/main/java/org/fireflyframework/cache/observability/CacheMetrics.java b/src/main/java/org/fireflyframework/cache/observability/CacheMetrics.java
new file mode 100644
index 0000000..efb8112
--- /dev/null
+++ b/src/main/java/org/fireflyframework/cache/observability/CacheMetrics.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2024-2026 Firefly Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.fireflyframework.cache.observability;
+
+import io.micrometer.core.instrument.MeterRegistry;
+import org.fireflyframework.observability.metrics.FireflyMetricsSupport;
+import org.fireflyframework.observability.metrics.MetricTags;
+import reactor.core.publisher.Mono;
+
+import java.util.Optional;
+
+/**
+ * Observability instrumentation for cache operations.
+ *
+ * Records:
+ *
+ * - {@code firefly.cache.gets} — total cache get attempts, tagged by {@code result=hit|miss} and {@code adapter}
+ * - {@code firefly.cache.puts} — total cache put operations, tagged by {@code adapter}
+ * - {@code firefly.cache.evictions} — total cache evictions, tagged by {@code adapter}
+ * - {@code firefly.cache.errors} — failed cache operations, tagged by {@code operation} and {@code error.type}
+ * - {@code firefly.cache.operation.duration} — timer for get/put/evict latency, tagged by {@code operation}
+ *
+ *
+ * When the {@link MeterRegistry} is unavailable, all methods become no-ops with zero overhead.
+ */
+public class CacheMetrics extends FireflyMetricsSupport {
+
+ private static final String TAG_RESULT = "result";
+ private static final String TAG_ADAPTER = "adapter";
+ private static final String TAG_OPERATION = "operation";
+ private static final String RESULT_HIT = "hit";
+ private static final String RESULT_MISS = "miss";
+
+ public CacheMetrics(MeterRegistry meterRegistry) {
+ super(meterRegistry, "cache");
+ }
+
+ public void recordHit(String adapter) {
+ counter("gets", TAG_RESULT, RESULT_HIT, TAG_ADAPTER, adapter).increment();
+ }
+
+ public void recordMiss(String adapter) {
+ counter("gets", TAG_RESULT, RESULT_MISS, TAG_ADAPTER, adapter).increment();
+ }
+
+ public void recordPut(String adapter) {
+ counter("puts", TAG_ADAPTER, adapter).increment();
+ }
+
+ public void recordEviction(String adapter) {
+ counter("evictions", TAG_ADAPTER, adapter).increment();
+ }
+
+ public void recordError(String operation, Throwable error) {
+ recordFailure("errors", error, TAG_OPERATION, operation);
+ }
+
+ /**
+ * Wraps a cache get operation with timing and hit/miss accounting.
+ */
+ public Mono> timedGet(String adapter, Mono> operation) {
+ return timed("operation.duration", operation, TAG_OPERATION, "get", TAG_ADAPTER, adapter)
+ .doOnNext(opt -> {
+ if (opt.isPresent()) {
+ recordHit(adapter);
+ } else {
+ recordMiss(adapter);
+ }
+ })
+ .doOnError(e -> recordError("get", e));
+ }
+
+ /**
+ * Wraps a cache put operation with timing.
+ */
+ public Mono timedPut(String adapter, Mono operation) {
+ return timed("operation.duration", operation, TAG_OPERATION, "put", TAG_ADAPTER, adapter)
+ .doOnSuccess(v -> recordPut(adapter))
+ .doOnError(e -> recordError("put", e));
+ }
+
+ /**
+ * Wraps a cache evict operation with timing.
+ */
+ public Mono timedEvict(String adapter, Mono operation) {
+ return timed("operation.duration", operation, TAG_OPERATION, "evict", TAG_ADAPTER, adapter)
+ .doOnSuccess(v -> recordEviction(adapter))
+ .doOnError(e -> recordError("evict", e));
+ }
+}
diff --git a/src/main/java/org/fireflyframework/cache/observability/CacheObservabilityAutoConfiguration.java b/src/main/java/org/fireflyframework/cache/observability/CacheObservabilityAutoConfiguration.java
new file mode 100644
index 0000000..7c95b47
--- /dev/null
+++ b/src/main/java/org/fireflyframework/cache/observability/CacheObservabilityAutoConfiguration.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2024-2026 Firefly Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.fireflyframework.cache.observability;
+
+import io.micrometer.core.instrument.MeterRegistry;
+import org.fireflyframework.cache.core.CacheAdapter;
+import org.fireflyframework.cache.manager.FireflyCacheManager;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.boot.actuate.health.HealthIndicator;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * Wires observability beans (metrics + health) for fireflyframework-cache.
+ *
+ * Activates when {@code firefly.observability.metrics.enabled} or
+ * {@code firefly.observability.health.enabled} (defaults: true) are not disabled.
+ */
+@AutoConfiguration
+@ConditionalOnClass({MeterRegistry.class, HealthIndicator.class})
+@ConditionalOnProperty(prefix = "firefly.cache", name = "enabled",
+ havingValue = "true", matchIfMissing = true)
+public class CacheObservabilityAutoConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean
+ @ConditionalOnBean(MeterRegistry.class)
+ @ConditionalOnProperty(prefix = "firefly.observability.metrics", name = "enabled",
+ havingValue = "true", matchIfMissing = true)
+ CacheMetrics cacheMetrics(MeterRegistry meterRegistry) {
+ return new CacheMetrics(meterRegistry);
+ }
+
+ @Bean
+ @ConditionalOnMissingBean(name = "cacheHealthIndicator")
+ @ConditionalOnBean({HealthIndicator.class, CacheAdapter.class})
+ @ConditionalOnProperty(prefix = "firefly.observability.health", name = "enabled",
+ havingValue = "true", matchIfMissing = true)
+ HealthIndicator cacheHealthIndicator(ObjectProvider managerProvider,
+ ObjectProvider adapterProvider) {
+ CacheAdapter adapter = adapterProvider.getIfAvailable();
+ FireflyCacheManager manager = managerProvider.getIfAvailable();
+ String adapterName = manager != null ? "firefly" : "cache";
+ return new CacheHealthIndicator(adapter, adapterName);
+ }
+}
diff --git a/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
index 347de63..a2cb8dc 100644
--- a/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
+++ b/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -1,3 +1,3 @@
org.fireflyframework.cache.config.CacheAutoConfiguration
org.fireflyframework.cache.config.RedisCacheAutoConfiguration
-
+org.fireflyframework.cache.observability.CacheObservabilityAutoConfiguration