diff --git a/.fossa.yml b/.fossa.yml
index 0a29d3ce460a..8ca3477c2f3c 100644
--- a/.fossa.yml
+++ b/.fossa.yml
@@ -100,6 +100,9 @@ targets:
- type: gradle
path: ./
target: ':instrumentation:external-annotations:javaagent'
+ - type: gradle
+ path: ./
+ target: ':instrumentation:failsafe-3.0:library'
- type: gradle
path: ./
target: ':instrumentation:finagle-http-23.11:javaagent'
diff --git a/docs/supported-libraries.md b/docs/supported-libraries.md
index 85118cd92502..1ef866ea0338 100644
--- a/docs/supported-libraries.md
+++ b/docs/supported-libraries.md
@@ -65,6 +65,7 @@ These are the supported libraries and frameworks:
| [Elasticsearch API Client](https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/index.html) | 7.16 - 7.17.19,
8.0 - 8.9.+ [4] | N/A | [Elasticsearch Client Spans] |
| [Elasticsearch REST Client](https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/index.html) | 5.0+ | N/A | [Database Client Spans], [Database Client Metrics] [6] |
| [Elasticsearch Transport Client](https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/index.html) | 5.0+ | N/A | [Database Client Spans], [Database Client Metrics] [6] |
+| [Failsafe](https://failsafe.dev/) | 3.0.1+ | [opentelemetry-failsafe-3.0](../instrumentation/failsafe-3.0/library) | none |
| [Finagle](https://github.com/twitter/finagle) | 23.11+ | N/A | none |
| [Finatra](https://github.com/twitter/finatra) | 2.9+ | N/A | Provides `http.route` [2], Controller Spans [3] |
| [Geode Client](https://geode.apache.org/) | 1.4+ | N/A | [Database Client Spans], [Database Client Metrics] [6] |
diff --git a/instrumentation/failsafe-3.0/README.md b/instrumentation/failsafe-3.0/README.md
new file mode 100644
index 000000000000..7f2c6df5aad9
--- /dev/null
+++ b/instrumentation/failsafe-3.0/README.md
@@ -0,0 +1,39 @@
+# Library Instrumentation for Failsafe version 3.0.1 and higher
+
+Provides OpenTelemetry instrumentation for [Failsafe](https://failsafe.dev/).
+
+## Quickstart
+
+### Add these dependencies to your project
+
+Replace `OPENTELEMETRY_VERSION` with the [latest release](https://search.maven.org/search?q=g:io.opentelemetry.instrumentation%20AND%20a:opentelemetry-failsafe-3.0).
+
+For Maven, add to your `pom.xml` dependencies:
+
+```xml
+
+
+ io.opentelemetry.instrumentation
+ opentelemetry-failsafe-3.0
+ OPENTELEMETRY_VERSION
+
+
+```
+
+For Gradle, add to your dependencies:
+
+```groovy
+implementation("io.opentelemetry.instrumentation:opentelemetry-failsafe-3.0:OPENTELEMETRY_VERSION")
+```
+
+### Usage
+
+The instrumentation library allows creating instrumented `CircuitBreaker` instances for collecting
+OpenTelemetry-based metrics.
+
+```java
+ CircuitBreaker configure(OpenTelemetry openTelemetry, CircuitBreaker circuitBreaker) {
+ FailsafeTelemetry failsafeTelemetry = FailsafeTelemetry.create(openTelemetry);
+ return failsafeTelemetry.createCircuitBreaker(circuitBreaker, "my-circuit-breaker");
+}
+```
diff --git a/instrumentation/failsafe-3.0/library/build.gradle.kts b/instrumentation/failsafe-3.0/library/build.gradle.kts
new file mode 100644
index 000000000000..e1667a063e6e
--- /dev/null
+++ b/instrumentation/failsafe-3.0/library/build.gradle.kts
@@ -0,0 +1,9 @@
+plugins {
+ id("otel.library-instrumentation")
+}
+
+dependencies {
+ library("dev.failsafe:failsafe:3.0.1")
+
+ testImplementation(project(":testing-common"))
+}
diff --git a/instrumentation/failsafe-3.0/library/src/main/java/io/opentelemetry/instrumentation/failsafe/v3_0/CircuitBreakerEventListenerBuilders.java b/instrumentation/failsafe-3.0/library/src/main/java/io/opentelemetry/instrumentation/failsafe/v3_0/CircuitBreakerEventListenerBuilders.java
new file mode 100644
index 000000000000..73ff69beb14e
--- /dev/null
+++ b/instrumentation/failsafe-3.0/library/src/main/java/io/opentelemetry/instrumentation/failsafe/v3_0/CircuitBreakerEventListenerBuilders.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.failsafe.v3_0;
+
+import dev.failsafe.CircuitBreakerConfig;
+import dev.failsafe.event.CircuitBreakerStateChangedEvent;
+import dev.failsafe.event.EventListener;
+import dev.failsafe.event.ExecutionCompletedEvent;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.metrics.LongCounter;
+import io.opentelemetry.api.metrics.Meter;
+
+final class CircuitBreakerEventListenerBuilders {
+ private CircuitBreakerEventListenerBuilders() {}
+
+ static EventListener> buildInstrumentedFailureListener(
+ CircuitBreakerConfig userConfig, Meter meter, Attributes attributes) {
+ LongCounter failureCounter =
+ meter
+ .counterBuilder("failsafe.circuitbreaker.failure.count")
+ .setDescription("Count of failed circuit breaker executions.")
+ .build();
+ EventListener> failureListener = userConfig.getFailureListener();
+ return e -> {
+ failureCounter.add(1, attributes);
+ if (failureListener != null) {
+ failureListener.accept(e);
+ }
+ };
+ }
+
+ static EventListener> buildInstrumentedSuccessListener(
+ CircuitBreakerConfig userConfig, Meter meter, Attributes attributes) {
+ LongCounter successCounter =
+ meter
+ .counterBuilder("failsafe.circuitbreaker.success.count")
+ .setDescription("Count of successful circuit breaker executions.")
+ .build();
+ EventListener> successListener = userConfig.getSuccessListener();
+ return e -> {
+ successCounter.add(1, attributes);
+ if (successListener != null) {
+ successListener.accept(e);
+ }
+ };
+ }
+
+ static EventListener buildInstrumentedOpenListener(
+ CircuitBreakerConfig userConfig, Meter meter, Attributes attributes) {
+ LongCounter openCircuitBreakerCounter =
+ meter
+ .counterBuilder("failsafe.circuitbreaker.open.count")
+ .setDescription("Count of times that circuit breaker was opened.")
+ .build();
+ EventListener openListener = userConfig.getOpenListener();
+ return e -> {
+ openCircuitBreakerCounter.add(1, attributes);
+ openListener.accept(e);
+ };
+ }
+
+ static EventListener buildInstrumentedHalfOpenListener(
+ CircuitBreakerConfig userConfig, Meter meter, Attributes attributes) {
+ LongCounter halfOpenCircuitBreakerCounter =
+ meter
+ .counterBuilder("failsafe.circuitbreaker.halfopen.count")
+ .setDescription("Count of times that circuit breaker was half-opened.")
+ .build();
+ EventListener halfOpenListener =
+ userConfig.getHalfOpenListener();
+ return e -> {
+ halfOpenCircuitBreakerCounter.add(1, attributes);
+ halfOpenListener.accept(e);
+ };
+ }
+
+ static EventListener buildInstrumentedCloseListener(
+ CircuitBreakerConfig userConfig, Meter meter, Attributes attributes) {
+ LongCounter closedCircuitBreakerCounter =
+ meter
+ .counterBuilder("failsafe.circuitbreaker.closed.count")
+ .setDescription("Count of times that circuit breaker was closed.")
+ .build();
+ EventListener closeListener = userConfig.getCloseListener();
+ return e -> {
+ closedCircuitBreakerCounter.add(1, attributes);
+ closeListener.accept(e);
+ };
+ }
+}
diff --git a/instrumentation/failsafe-3.0/library/src/main/java/io/opentelemetry/instrumentation/failsafe/v3_0/FailsafeTelemetry.java b/instrumentation/failsafe-3.0/library/src/main/java/io/opentelemetry/instrumentation/failsafe/v3_0/FailsafeTelemetry.java
new file mode 100644
index 000000000000..1c9f4ae2c34e
--- /dev/null
+++ b/instrumentation/failsafe-3.0/library/src/main/java/io/opentelemetry/instrumentation/failsafe/v3_0/FailsafeTelemetry.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.failsafe.v3_0;
+
+import static io.opentelemetry.instrumentation.failsafe.v3_0.CircuitBreakerEventListenerBuilders.buildInstrumentedCloseListener;
+import static io.opentelemetry.instrumentation.failsafe.v3_0.CircuitBreakerEventListenerBuilders.buildInstrumentedFailureListener;
+import static io.opentelemetry.instrumentation.failsafe.v3_0.CircuitBreakerEventListenerBuilders.buildInstrumentedHalfOpenListener;
+import static io.opentelemetry.instrumentation.failsafe.v3_0.CircuitBreakerEventListenerBuilders.buildInstrumentedOpenListener;
+import static io.opentelemetry.instrumentation.failsafe.v3_0.CircuitBreakerEventListenerBuilders.buildInstrumentedSuccessListener;
+
+import dev.failsafe.CircuitBreaker;
+import dev.failsafe.CircuitBreakerConfig;
+import io.opentelemetry.api.OpenTelemetry;
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.metrics.Meter;
+
+/** Entrypoint for instrumenting Failsafe components. */
+public final class FailsafeTelemetry {
+ private static final String INSTRUMENTATION_NAME = "io.opentelemetry.failsafe-3.0";
+
+ /** Returns a new {@link FailsafeTelemetry} configured with the given {@link OpenTelemetry}. */
+ public static FailsafeTelemetry create(OpenTelemetry openTelemetry) {
+ return new FailsafeTelemetry(openTelemetry);
+ }
+
+ private final OpenTelemetry openTelemetry;
+
+ private FailsafeTelemetry(OpenTelemetry openTelemetry) {
+ this.openTelemetry = openTelemetry;
+ }
+
+ /**
+ * Returns an instrumented {@link CircuitBreaker} by given values.
+ *
+ * @param delegate user configured {@link CircuitBreaker} to be instrumented
+ * @param circuitBreakerName identifier of given {@link CircuitBreaker}
+ * @param {@link CircuitBreaker}'s result type
+ * @return instrumented {@link CircuitBreaker}
+ */
+ public CircuitBreaker createCircuitBreaker(
+ CircuitBreaker delegate, String circuitBreakerName) {
+ CircuitBreakerConfig userConfig = delegate.getConfig();
+ Meter meter = openTelemetry.getMeter(INSTRUMENTATION_NAME);
+ Attributes attributes = Attributes.of(AttributeKey.stringKey("name"), circuitBreakerName);
+ return CircuitBreaker.builder(userConfig)
+ .onFailure(buildInstrumentedFailureListener(userConfig, meter, attributes))
+ .onSuccess(buildInstrumentedSuccessListener(userConfig, meter, attributes))
+ .onOpen(buildInstrumentedOpenListener(userConfig, meter, attributes))
+ .onHalfOpen(buildInstrumentedHalfOpenListener(userConfig, meter, attributes))
+ .onClose(buildInstrumentedCloseListener(userConfig, meter, attributes))
+ .build();
+ }
+}
diff --git a/instrumentation/failsafe-3.0/library/src/test/java/io/opentelemetry/instrumentation/failsafe/v3_0/FailsafeTelemetryTest.java b/instrumentation/failsafe-3.0/library/src/test/java/io/opentelemetry/instrumentation/failsafe/v3_0/FailsafeTelemetryTest.java
new file mode 100644
index 000000000000..26e3618367ae
--- /dev/null
+++ b/instrumentation/failsafe-3.0/library/src/test/java/io/opentelemetry/instrumentation/failsafe/v3_0/FailsafeTelemetryTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.failsafe.v3_0;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import dev.failsafe.CircuitBreakerOpenException;
+import dev.failsafe.Failsafe;
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
+import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension;
+import io.opentelemetry.sdk.metrics.data.LongPointData;
+import io.opentelemetry.sdk.metrics.data.MetricData;
+import io.opentelemetry.sdk.testing.assertj.MetricAssert;
+import java.time.Duration;
+import java.util.Objects;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+final class FailsafeTelemetryTest {
+ @RegisterExtension
+ static final InstrumentationExtension testing = LibraryInstrumentationExtension.create();
+
+ @Nested
+ final class CircuitBreaker {
+ @Test
+ void should_Capture_CircuitBreaker_Metrics() {
+ // given
+ dev.failsafe.CircuitBreaker