From 463deb6aac0d60a1b1b7d1047c5392b15432c178 Mon Sep 17 00:00:00 2001 From: Onur Kayabasi Date: Wed, 18 Jun 2025 08:35:17 +0200 Subject: [PATCH 1/6] Failsafe 3.0 instrumentation introduced --- .../failsafe-3.0/library/build.gradle.kts | 9 ++ .../CircuitBreakerEventListenerBuilders.java | 95 +++++++++++++++++++ .../failsafe/v3_0/FailsafeTelemetry.java | 57 +++++++++++ .../failsafe/v3_0/FailsafeTelemetryTest.java | 27 ++++++ .../failsafe-3.0/testing/build.gradle.kts | 9 ++ .../failsafe/v3_0/AbstractFailsafeTest.java | 93 ++++++++++++++++++ settings.gradle.kts | 3 +- 7 files changed, 292 insertions(+), 1 deletion(-) create mode 100644 instrumentation/failsafe-3.0/library/build.gradle.kts create mode 100644 instrumentation/failsafe-3.0/library/src/main/java/io/opentelemetry/instrumentation/failsafe/v3_0/CircuitBreakerEventListenerBuilders.java create mode 100644 instrumentation/failsafe-3.0/library/src/main/java/io/opentelemetry/instrumentation/failsafe/v3_0/FailsafeTelemetry.java create mode 100644 instrumentation/failsafe-3.0/library/src/test/java/io/opentelemetry/instrumentation/failsafe/v3_0/FailsafeTelemetryTest.java create mode 100644 instrumentation/failsafe-3.0/testing/build.gradle.kts create mode 100644 instrumentation/failsafe-3.0/testing/src/main/java/io/opentelemetry/instrumentation/failsafe/v3_0/AbstractFailsafeTest.java 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..b2ce953e03d0 --- /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(":instrumentation:failsafe-3.0:testing")) +} 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..0dd9c611beef --- /dev/null +++ b/instrumentation/failsafe-3.0/library/src/main/java/io/opentelemetry/instrumentation/failsafe/v3_0/CircuitBreakerEventListenerBuilders.java @@ -0,0 +1,95 @@ +/* + * 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() { + throw new AssertionError(); + } + + 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..d889afd80d51 --- /dev/null +++ b/instrumentation/failsafe-3.0/library/src/test/java/io/opentelemetry/instrumentation/failsafe/v3_0/FailsafeTelemetryTest.java @@ -0,0 +1,27 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.failsafe.v3_0; + +import dev.failsafe.CircuitBreaker; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import org.junit.jupiter.api.extension.RegisterExtension; + +final class FailsafeTelemetryTest extends AbstractFailsafeTest { + @RegisterExtension + static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @Override + protected InstrumentationExtension testing() { + return testing; + } + + @Override + protected CircuitBreaker configure(CircuitBreaker circuitBreaker) { + return FailsafeTelemetry.create(testing.getOpenTelemetry()) + .createCircuitBreaker(circuitBreaker, "testing"); + } +} diff --git a/instrumentation/failsafe-3.0/testing/build.gradle.kts b/instrumentation/failsafe-3.0/testing/build.gradle.kts new file mode 100644 index 000000000000..7f5c345ac2e0 --- /dev/null +++ b/instrumentation/failsafe-3.0/testing/build.gradle.kts @@ -0,0 +1,9 @@ +plugins { + id("otel.java-conventions") +} + +dependencies { + api(project(":testing-common")) + + compileOnly("dev.failsafe:failsafe:3.0.1") +} diff --git a/instrumentation/failsafe-3.0/testing/src/main/java/io/opentelemetry/instrumentation/failsafe/v3_0/AbstractFailsafeTest.java b/instrumentation/failsafe-3.0/testing/src/main/java/io/opentelemetry/instrumentation/failsafe/v3_0/AbstractFailsafeTest.java new file mode 100644 index 000000000000..c5de55ea2fdb --- /dev/null +++ b/instrumentation/failsafe-3.0/testing/src/main/java/io/opentelemetry/instrumentation/failsafe/v3_0/AbstractFailsafeTest.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.CircuitBreaker; +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.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.Test; + +public abstract class AbstractFailsafeTest { + + protected abstract InstrumentationExtension testing(); + + protected abstract CircuitBreaker configure(CircuitBreaker circuitBreaker); + + @Test + void should_Capture_CircuitBreaker_Metrics() { + // given + CircuitBreaker userCircuitBreaker = + CircuitBreaker.builder() + .handleResultIf(Objects::isNull) + .withFailureThreshold(2) + .withDelay(Duration.ZERO) + .withSuccessThreshold(2) + .build(); + CircuitBreaker instrumentedCircuitBreaker = configure(userCircuitBreaker); + + // when + for (int i = 0; i < 5; i++) { + try { + int temp = i; + Failsafe.with(instrumentedCircuitBreaker) + .get( + () -> { + if (temp < 2) { + return null; + } else { + return new Object(); + } + }); + } catch (CircuitBreakerOpenException e) { + assertThat(i, equalTo(2)); + } + } + + // then + testing() + .waitAndAssertMetrics( + "io.opentelemetry.failsafe-3.0", + metricAssert -> + assertCircuitBreakerMetric( + metricAssert, "failsafe.circuitbreaker.failure.count", 2), + metricAssert -> + assertCircuitBreakerMetric( + metricAssert, "failsafe.circuitbreaker.success.count", 3), + metricAssert -> + assertCircuitBreakerMetric(metricAssert, "failsafe.circuitbreaker.open.count", 1), + metricAssert -> + assertCircuitBreakerMetric( + metricAssert, "failsafe.circuitbreaker.halfopen.count", 1), + metricAssert -> + assertCircuitBreakerMetric( + metricAssert, "failsafe.circuitbreaker.closed.count", 1)); + } + + private static void assertCircuitBreakerMetric( + MetricAssert metricAssert, String counterName, long expectedValue) { + MetricData closeCountData = metricAssert.actual(); + assertThat(closeCountData.getName(), equalTo(counterName)); + assertTrue(closeCountData.getData().getPoints().stream().findFirst().isPresent()); + LongPointData closeCountLongPointData = + (LongPointData) closeCountData.getData().getPoints().stream().findFirst().get(); + assertThat( + closeCountLongPointData.getAttributes(), + equalTo(Attributes.of(AttributeKey.stringKey("name"), "testing"))); + assertThat(closeCountLongPointData.getValue(), equalTo(expectedValue)); + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index b8c419a9a5e0..a46e1abfe769 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -626,7 +626,8 @@ include(":instrumentation:xxl-job:xxl-job-2.3.0:javaagent") include(":instrumentation:xxl-job:xxl-job-common:javaagent") include(":instrumentation:xxl-job:xxl-job-common:testing") include(":instrumentation:zio:zio-2.0:javaagent") - +include("instrumentation:failsafe-3.0:library") +include("instrumentation:failsafe-3.0:testing") // benchmark include(":benchmark-overhead-jmh") include(":benchmark-jfr-analyzer") From d450563350cc11915e31deb3830ba9965bfe5aac Mon Sep 17 00:00:00 2001 From: Onur Kayabasi Date: Wed, 18 Jun 2025 08:51:55 +0200 Subject: [PATCH 2/6] generateFossaConfiguration executed --- .fossa.yml | 3 +++ 1 file changed, 3 insertions(+) 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' From 55f2c79546d1e143c882905cd841e0b2d6b2b6d8 Mon Sep 17 00:00:00 2001 From: Onur Kayabasi Date: Thu, 19 Jun 2025 08:26:35 +0200 Subject: [PATCH 3/6] Dependency issue fixed --- instrumentation/failsafe-3.0/testing/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instrumentation/failsafe-3.0/testing/build.gradle.kts b/instrumentation/failsafe-3.0/testing/build.gradle.kts index 7f5c345ac2e0..14793f9fe278 100644 --- a/instrumentation/failsafe-3.0/testing/build.gradle.kts +++ b/instrumentation/failsafe-3.0/testing/build.gradle.kts @@ -5,5 +5,5 @@ plugins { dependencies { api(project(":testing-common")) - compileOnly("dev.failsafe:failsafe:3.0.1") + implementation("dev.failsafe:failsafe:3.0.1") } From 69a4ecb6ad4796bdca309b92ab637325720b8dcb Mon Sep 17 00:00:00 2001 From: Onur Kayabasi Date: Thu, 19 Jun 2025 08:48:05 +0200 Subject: [PATCH 4/6] Dependency issue fixed --- instrumentation/failsafe-3.0/testing/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instrumentation/failsafe-3.0/testing/build.gradle.kts b/instrumentation/failsafe-3.0/testing/build.gradle.kts index 14793f9fe278..afcf747f6e32 100644 --- a/instrumentation/failsafe-3.0/testing/build.gradle.kts +++ b/instrumentation/failsafe-3.0/testing/build.gradle.kts @@ -5,5 +5,5 @@ plugins { dependencies { api(project(":testing-common")) - implementation("dev.failsafe:failsafe:3.0.1") + api("dev.failsafe:failsafe:3.0.1") } From bf6c98282362b7f6484d3b7c068a5bc81cc09281 Mon Sep 17 00:00:00 2001 From: Onur Kayabasi Date: Tue, 24 Jun 2025 08:19:47 +0200 Subject: [PATCH 5/6] PR comments addressed --- docs/supported-libraries.md | 1 + instrumentation/failsafe-3.0/README.md | 39 ++++++++ .../failsafe-3.0/library/build.gradle.kts | 2 +- .../CircuitBreakerEventListenerBuilders.java | 14 ++- .../failsafe/v3_0/FailsafeTelemetryTest.java | 84 +++++++++++++++-- .../failsafe-3.0/testing/build.gradle.kts | 9 -- .../failsafe/v3_0/AbstractFailsafeTest.java | 93 ------------------- settings.gradle.kts | 3 +- 8 files changed, 123 insertions(+), 122 deletions(-) create mode 100644 instrumentation/failsafe-3.0/README.md delete mode 100644 instrumentation/failsafe-3.0/testing/build.gradle.kts delete mode 100644 instrumentation/failsafe-3.0/testing/src/main/java/io/opentelemetry/instrumentation/failsafe/v3_0/AbstractFailsafeTest.java diff --git a/docs/supported-libraries.md b/docs/supported-libraries.md index 85118cd92502..f55be757b43f 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+ | N/A | 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 index b2ce953e03d0..e1667a063e6e 100644 --- a/instrumentation/failsafe-3.0/library/build.gradle.kts +++ b/instrumentation/failsafe-3.0/library/build.gradle.kts @@ -5,5 +5,5 @@ plugins { dependencies { library("dev.failsafe:failsafe:3.0.1") - testImplementation(project(":instrumentation:failsafe-3.0:testing")) + 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 index 0dd9c611beef..73ff69beb14e 100644 --- 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 @@ -14,16 +14,14 @@ import io.opentelemetry.api.metrics.Meter; final class CircuitBreakerEventListenerBuilders { - private CircuitBreakerEventListenerBuilders() { - throw new AssertionError(); - } + 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") + .setDescription("Count of failed circuit breaker executions.") .build(); EventListener> failureListener = userConfig.getFailureListener(); return e -> { @@ -39,7 +37,7 @@ static EventListener> buildInstrumentedSuccessLis LongCounter successCounter = meter .counterBuilder("failsafe.circuitbreaker.success.count") - .setDescription("Count of successful circuit breaker executions") + .setDescription("Count of successful circuit breaker executions.") .build(); EventListener> successListener = userConfig.getSuccessListener(); return e -> { @@ -55,7 +53,7 @@ static EventListener buildInstrumentedOpenL LongCounter openCircuitBreakerCounter = meter .counterBuilder("failsafe.circuitbreaker.open.count") - .setDescription("Count of times that circuit breaker was opened") + .setDescription("Count of times that circuit breaker was opened.") .build(); EventListener openListener = userConfig.getOpenListener(); return e -> { @@ -69,7 +67,7 @@ static EventListener buildInstrumentedHalfO LongCounter halfOpenCircuitBreakerCounter = meter .counterBuilder("failsafe.circuitbreaker.halfopen.count") - .setDescription("Count of times that circuit breaker was half-opened") + .setDescription("Count of times that circuit breaker was half-opened.") .build(); EventListener halfOpenListener = userConfig.getHalfOpenListener(); @@ -84,7 +82,7 @@ static EventListener buildInstrumentedClose LongCounter closedCircuitBreakerCounter = meter .counterBuilder("failsafe.circuitbreaker.closed.count") - .setDescription("Count of times that circuit breaker was closed") + .setDescription("Count of times that circuit breaker was closed.") .build(); EventListener closeListener = userConfig.getCloseListener(); return e -> { 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 index d889afd80d51..26e3618367ae 100644 --- 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 @@ -5,23 +5,89 @@ package io.opentelemetry.instrumentation.failsafe.v3_0; -import dev.failsafe.CircuitBreaker; +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 extends AbstractFailsafeTest { +final class FailsafeTelemetryTest { @RegisterExtension static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); - @Override - protected InstrumentationExtension testing() { - return testing; + @Nested + final class CircuitBreaker { + @Test + void should_Capture_CircuitBreaker_Metrics() { + // given + dev.failsafe.CircuitBreaker userCircuitBreaker = + dev.failsafe.CircuitBreaker.builder() + .handleResultIf(Objects::isNull) + .withFailureThreshold(2) + .withDelay(Duration.ZERO) + .withSuccessThreshold(2) + .build(); + FailsafeTelemetry failsafeTelemetry = FailsafeTelemetry.create(testing.getOpenTelemetry()); + dev.failsafe.CircuitBreaker instrumentedCircuitBreaker = + failsafeTelemetry.createCircuitBreaker(userCircuitBreaker, "testing"); + + // when + for (int i = 0; i < 5; i++) { + try { + int temp = i; + Failsafe.with(instrumentedCircuitBreaker) + .get( + () -> { + if (temp < 2) { + return null; + } else { + return new Object(); + } + }); + } catch (CircuitBreakerOpenException e) { + assertThat(i, equalTo(2)); + } + } + + // then + testing.waitAndAssertMetrics( + "io.opentelemetry.failsafe-3.0", + metricAssert -> + assertCircuitBreakerMetric(metricAssert, "failsafe.circuitbreaker.failure.count", 2), + metricAssert -> + assertCircuitBreakerMetric(metricAssert, "failsafe.circuitbreaker.success.count", 3), + metricAssert -> + assertCircuitBreakerMetric(metricAssert, "failsafe.circuitbreaker.open.count", 1), + metricAssert -> + assertCircuitBreakerMetric(metricAssert, "failsafe.circuitbreaker.halfopen.count", 1), + metricAssert -> + assertCircuitBreakerMetric(metricAssert, "failsafe.circuitbreaker.closed.count", 1)); + } } - @Override - protected CircuitBreaker configure(CircuitBreaker circuitBreaker) { - return FailsafeTelemetry.create(testing.getOpenTelemetry()) - .createCircuitBreaker(circuitBreaker, "testing"); + private static void assertCircuitBreakerMetric( + MetricAssert metricAssert, String counterName, long expectedValue) { + MetricData closeCountData = metricAssert.actual(); + assertThat(closeCountData.getName(), equalTo(counterName)); + assertTrue(closeCountData.getData().getPoints().stream().findFirst().isPresent()); + LongPointData closeCountLongPointData = + (LongPointData) closeCountData.getData().getPoints().stream().findFirst().get(); + assertThat( + closeCountLongPointData.getAttributes(), + equalTo(Attributes.of(AttributeKey.stringKey("name"), "testing"))); + assertThat(closeCountLongPointData.getValue(), equalTo(expectedValue)); } } diff --git a/instrumentation/failsafe-3.0/testing/build.gradle.kts b/instrumentation/failsafe-3.0/testing/build.gradle.kts deleted file mode 100644 index afcf747f6e32..000000000000 --- a/instrumentation/failsafe-3.0/testing/build.gradle.kts +++ /dev/null @@ -1,9 +0,0 @@ -plugins { - id("otel.java-conventions") -} - -dependencies { - api(project(":testing-common")) - - api("dev.failsafe:failsafe:3.0.1") -} diff --git a/instrumentation/failsafe-3.0/testing/src/main/java/io/opentelemetry/instrumentation/failsafe/v3_0/AbstractFailsafeTest.java b/instrumentation/failsafe-3.0/testing/src/main/java/io/opentelemetry/instrumentation/failsafe/v3_0/AbstractFailsafeTest.java deleted file mode 100644 index c5de55ea2fdb..000000000000 --- a/instrumentation/failsafe-3.0/testing/src/main/java/io/opentelemetry/instrumentation/failsafe/v3_0/AbstractFailsafeTest.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * 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.CircuitBreaker; -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.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.Test; - -public abstract class AbstractFailsafeTest { - - protected abstract InstrumentationExtension testing(); - - protected abstract CircuitBreaker configure(CircuitBreaker circuitBreaker); - - @Test - void should_Capture_CircuitBreaker_Metrics() { - // given - CircuitBreaker userCircuitBreaker = - CircuitBreaker.builder() - .handleResultIf(Objects::isNull) - .withFailureThreshold(2) - .withDelay(Duration.ZERO) - .withSuccessThreshold(2) - .build(); - CircuitBreaker instrumentedCircuitBreaker = configure(userCircuitBreaker); - - // when - for (int i = 0; i < 5; i++) { - try { - int temp = i; - Failsafe.with(instrumentedCircuitBreaker) - .get( - () -> { - if (temp < 2) { - return null; - } else { - return new Object(); - } - }); - } catch (CircuitBreakerOpenException e) { - assertThat(i, equalTo(2)); - } - } - - // then - testing() - .waitAndAssertMetrics( - "io.opentelemetry.failsafe-3.0", - metricAssert -> - assertCircuitBreakerMetric( - metricAssert, "failsafe.circuitbreaker.failure.count", 2), - metricAssert -> - assertCircuitBreakerMetric( - metricAssert, "failsafe.circuitbreaker.success.count", 3), - metricAssert -> - assertCircuitBreakerMetric(metricAssert, "failsafe.circuitbreaker.open.count", 1), - metricAssert -> - assertCircuitBreakerMetric( - metricAssert, "failsafe.circuitbreaker.halfopen.count", 1), - metricAssert -> - assertCircuitBreakerMetric( - metricAssert, "failsafe.circuitbreaker.closed.count", 1)); - } - - private static void assertCircuitBreakerMetric( - MetricAssert metricAssert, String counterName, long expectedValue) { - MetricData closeCountData = metricAssert.actual(); - assertThat(closeCountData.getName(), equalTo(counterName)); - assertTrue(closeCountData.getData().getPoints().stream().findFirst().isPresent()); - LongPointData closeCountLongPointData = - (LongPointData) closeCountData.getData().getPoints().stream().findFirst().get(); - assertThat( - closeCountLongPointData.getAttributes(), - equalTo(Attributes.of(AttributeKey.stringKey("name"), "testing"))); - assertThat(closeCountLongPointData.getValue(), equalTo(expectedValue)); - } -} diff --git a/settings.gradle.kts b/settings.gradle.kts index a46e1abfe769..acb68f30cffd 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -224,6 +224,7 @@ include(":instrumentation:executors:jdk21-testing") include(":instrumentation:executors:testing") include(":instrumentation:external-annotations:javaagent") include(":instrumentation:external-annotations:javaagent-unit-tests") +include(":instrumentation:failsafe-3.0:library") include(":instrumentation:finagle-http-23.11:javaagent") include(":instrumentation:finatra-2.9:javaagent") include(":instrumentation:geode-1.4:javaagent") @@ -626,8 +627,6 @@ include(":instrumentation:xxl-job:xxl-job-2.3.0:javaagent") include(":instrumentation:xxl-job:xxl-job-common:javaagent") include(":instrumentation:xxl-job:xxl-job-common:testing") include(":instrumentation:zio:zio-2.0:javaagent") -include("instrumentation:failsafe-3.0:library") -include("instrumentation:failsafe-3.0:testing") // benchmark include(":benchmark-overhead-jmh") include(":benchmark-jfr-analyzer") From 8fd8249da830f4510902ddb435361f67f1a82c90 Mon Sep 17 00:00:00 2001 From: Onur Kayabasi Date: Tue, 24 Jun 2025 08:24:15 +0200 Subject: [PATCH 6/6] Missing column added in supported-libraries.md --- docs/supported-libraries.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/supported-libraries.md b/docs/supported-libraries.md index f55be757b43f..1ef866ea0338 100644 --- a/docs/supported-libraries.md +++ b/docs/supported-libraries.md @@ -65,7 +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+ | N/A | none | +| [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] |