Skip to content

Commit 374dd24

Browse files
committed
Failsafe 3.0 instrumentation introduced
1 parent 1343094 commit 374dd24

File tree

7 files changed

+292
-1
lines changed

7 files changed

+292
-1
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
plugins {
2+
id("otel.library-instrumentation")
3+
}
4+
5+
dependencies {
6+
library("dev.failsafe:failsafe:3.0.1")
7+
8+
testImplementation(project(":instrumentation:failsafe-3.0:testing"))
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.failsafe.v3_0;
7+
8+
import dev.failsafe.CircuitBreakerConfig;
9+
import dev.failsafe.event.CircuitBreakerStateChangedEvent;
10+
import dev.failsafe.event.EventListener;
11+
import dev.failsafe.event.ExecutionCompletedEvent;
12+
import io.opentelemetry.api.common.Attributes;
13+
import io.opentelemetry.api.metrics.LongCounter;
14+
import io.opentelemetry.api.metrics.Meter;
15+
16+
final class CircuitBreakerEventListenerBuilders {
17+
private CircuitBreakerEventListenerBuilders() {
18+
throw new AssertionError();
19+
}
20+
21+
static <R> EventListener<ExecutionCompletedEvent<R>> buildInstrumentedFailureListener(
22+
CircuitBreakerConfig<R> userConfig, Meter meter, Attributes attributes) {
23+
LongCounter failureCounter =
24+
meter
25+
.counterBuilder("failsafe.circuitbreaker.failure.count")
26+
.setDescription("Count of failed circuit breaker executions")
27+
.build();
28+
EventListener<ExecutionCompletedEvent<R>> failureListener = userConfig.getFailureListener();
29+
return e -> {
30+
failureCounter.add(1, attributes);
31+
if (failureListener != null) {
32+
failureListener.accept(e);
33+
}
34+
};
35+
}
36+
37+
static <R> EventListener<ExecutionCompletedEvent<R>> buildInstrumentedSuccessListener(
38+
CircuitBreakerConfig<R> userConfig, Meter meter, Attributes attributes) {
39+
LongCounter successCounter =
40+
meter
41+
.counterBuilder("failsafe.circuitbreaker.success.count")
42+
.setDescription("Count of successful circuit breaker executions")
43+
.build();
44+
EventListener<ExecutionCompletedEvent<R>> successListener = userConfig.getSuccessListener();
45+
return e -> {
46+
successCounter.add(1, attributes);
47+
if (successListener != null) {
48+
successListener.accept(e);
49+
}
50+
};
51+
}
52+
53+
static <R> EventListener<CircuitBreakerStateChangedEvent> buildInstrumentedOpenListener(
54+
CircuitBreakerConfig<R> userConfig, Meter meter, Attributes attributes) {
55+
LongCounter openCircuitBreakerCounter =
56+
meter
57+
.counterBuilder("failsafe.circuitbreaker.open.count")
58+
.setDescription("Count of times that circuit breaker was opened")
59+
.build();
60+
EventListener<CircuitBreakerStateChangedEvent> openListener = userConfig.getOpenListener();
61+
return e -> {
62+
openCircuitBreakerCounter.add(1, attributes);
63+
openListener.accept(e);
64+
};
65+
}
66+
67+
static <R> EventListener<CircuitBreakerStateChangedEvent> buildInstrumentedHalfOpenListener(
68+
CircuitBreakerConfig<R> userConfig, Meter meter, Attributes attributes) {
69+
LongCounter halfOpenCircuitBreakerCounter =
70+
meter
71+
.counterBuilder("failsafe.circuitbreaker.halfopen.count")
72+
.setDescription("Count of times that circuit breaker was half-opened")
73+
.build();
74+
EventListener<CircuitBreakerStateChangedEvent> halfOpenListener =
75+
userConfig.getHalfOpenListener();
76+
return e -> {
77+
halfOpenCircuitBreakerCounter.add(1, attributes);
78+
halfOpenListener.accept(e);
79+
};
80+
}
81+
82+
static <R> EventListener<CircuitBreakerStateChangedEvent> buildInstrumentedCloseListener(
83+
CircuitBreakerConfig<R> userConfig, Meter meter, Attributes attributes) {
84+
LongCounter closedCircuitBreakerCounter =
85+
meter
86+
.counterBuilder("failsafe.circuitbreaker.closed.count")
87+
.setDescription("Count of times that circuit breaker was closed")
88+
.build();
89+
EventListener<CircuitBreakerStateChangedEvent> closeListener = userConfig.getCloseListener();
90+
return e -> {
91+
closedCircuitBreakerCounter.add(1, attributes);
92+
closeListener.accept(e);
93+
};
94+
}
95+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.failsafe.v3_0;
7+
8+
import static io.opentelemetry.instrumentation.failsafe.v3_0.CircuitBreakerEventListenerBuilders.buildInstrumentedCloseListener;
9+
import static io.opentelemetry.instrumentation.failsafe.v3_0.CircuitBreakerEventListenerBuilders.buildInstrumentedFailureListener;
10+
import static io.opentelemetry.instrumentation.failsafe.v3_0.CircuitBreakerEventListenerBuilders.buildInstrumentedHalfOpenListener;
11+
import static io.opentelemetry.instrumentation.failsafe.v3_0.CircuitBreakerEventListenerBuilders.buildInstrumentedOpenListener;
12+
import static io.opentelemetry.instrumentation.failsafe.v3_0.CircuitBreakerEventListenerBuilders.buildInstrumentedSuccessListener;
13+
14+
import dev.failsafe.CircuitBreaker;
15+
import dev.failsafe.CircuitBreakerConfig;
16+
import io.opentelemetry.api.OpenTelemetry;
17+
import io.opentelemetry.api.common.AttributeKey;
18+
import io.opentelemetry.api.common.Attributes;
19+
import io.opentelemetry.api.metrics.Meter;
20+
21+
/** Entrypoint for instrumenting Failsafe components. */
22+
public final class FailsafeTelemetry {
23+
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.failsafe-3.0";
24+
25+
/** Returns a new {@link FailsafeTelemetry} configured with the given {@link OpenTelemetry}. */
26+
public static FailsafeTelemetry create(OpenTelemetry openTelemetry) {
27+
return new FailsafeTelemetry(openTelemetry);
28+
}
29+
30+
private final OpenTelemetry openTelemetry;
31+
32+
private FailsafeTelemetry(OpenTelemetry openTelemetry) {
33+
this.openTelemetry = openTelemetry;
34+
}
35+
36+
/**
37+
* Returns an instrumented {@link CircuitBreaker} by given values.
38+
*
39+
* @param delegate user configured {@link CircuitBreaker} to be instrumented
40+
* @param circuitBreakerName identifier of given {@link CircuitBreaker}
41+
* @param <R> {@link CircuitBreaker}'s result type
42+
* @return instrumented {@link CircuitBreaker}
43+
*/
44+
public <R> CircuitBreaker<R> createCircuitBreaker(
45+
CircuitBreaker<R> delegate, String circuitBreakerName) {
46+
CircuitBreakerConfig<R> userConfig = delegate.getConfig();
47+
Meter meter = openTelemetry.getMeter(INSTRUMENTATION_NAME);
48+
Attributes attributes = Attributes.of(AttributeKey.stringKey("name"), circuitBreakerName);
49+
return CircuitBreaker.builder(userConfig)
50+
.onFailure(buildInstrumentedFailureListener(userConfig, meter, attributes))
51+
.onSuccess(buildInstrumentedSuccessListener(userConfig, meter, attributes))
52+
.onOpen(buildInstrumentedOpenListener(userConfig, meter, attributes))
53+
.onHalfOpen(buildInstrumentedHalfOpenListener(userConfig, meter, attributes))
54+
.onClose(buildInstrumentedCloseListener(userConfig, meter, attributes))
55+
.build();
56+
}
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.failsafe.v3_0;
7+
8+
import dev.failsafe.CircuitBreaker;
9+
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
10+
import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension;
11+
import org.junit.jupiter.api.extension.RegisterExtension;
12+
13+
final class FailsafeTelemetryTest extends AbstractFailsafeTest {
14+
@RegisterExtension
15+
static final InstrumentationExtension testing = LibraryInstrumentationExtension.create();
16+
17+
@Override
18+
protected InstrumentationExtension testing() {
19+
return testing;
20+
}
21+
22+
@Override
23+
protected CircuitBreaker<Object> configure(CircuitBreaker<Object> circuitBreaker) {
24+
return FailsafeTelemetry.create(testing.getOpenTelemetry())
25+
.createCircuitBreaker(circuitBreaker, "testing");
26+
}
27+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
plugins {
2+
id("otel.java-conventions")
3+
}
4+
5+
dependencies {
6+
api(project(":testing-common"))
7+
8+
compileOnly("dev.failsafe:failsafe:3.0.1")
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.failsafe.v3_0;
7+
8+
import static org.hamcrest.CoreMatchers.equalTo;
9+
import static org.hamcrest.MatcherAssert.assertThat;
10+
import static org.junit.jupiter.api.Assertions.assertTrue;
11+
12+
import dev.failsafe.CircuitBreaker;
13+
import dev.failsafe.CircuitBreakerOpenException;
14+
import dev.failsafe.Failsafe;
15+
import io.opentelemetry.api.common.AttributeKey;
16+
import io.opentelemetry.api.common.Attributes;
17+
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
18+
import io.opentelemetry.sdk.metrics.data.LongPointData;
19+
import io.opentelemetry.sdk.metrics.data.MetricData;
20+
import io.opentelemetry.sdk.testing.assertj.MetricAssert;
21+
import java.time.Duration;
22+
import java.util.Objects;
23+
import org.junit.jupiter.api.Test;
24+
25+
public abstract class AbstractFailsafeTest {
26+
27+
protected abstract InstrumentationExtension testing();
28+
29+
protected abstract CircuitBreaker<Object> configure(CircuitBreaker<Object> circuitBreaker);
30+
31+
@Test
32+
void should_Capture_CircuitBreaker_Metrics() {
33+
// given
34+
CircuitBreaker<Object> userCircuitBreaker =
35+
CircuitBreaker.builder()
36+
.handleResultIf(Objects::isNull)
37+
.withFailureThreshold(2)
38+
.withDelay(Duration.ZERO)
39+
.withSuccessThreshold(2)
40+
.build();
41+
CircuitBreaker<Object> instrumentedCircuitBreaker = configure(userCircuitBreaker);
42+
43+
// when
44+
for (int i = 0; i < 5; i++) {
45+
try {
46+
int temp = i;
47+
Failsafe.with(instrumentedCircuitBreaker)
48+
.get(
49+
() -> {
50+
if (temp < 2) {
51+
return null;
52+
} else {
53+
return new Object();
54+
}
55+
});
56+
} catch (CircuitBreakerOpenException e) {
57+
assertThat(i, equalTo(2));
58+
}
59+
}
60+
61+
// then
62+
testing()
63+
.waitAndAssertMetrics(
64+
"io.opentelemetry.failsafe-3.0",
65+
metricAssert ->
66+
assertCircuitBreakerMetric(
67+
metricAssert, "failsafe.circuitbreaker.failure.count", 2),
68+
metricAssert ->
69+
assertCircuitBreakerMetric(
70+
metricAssert, "failsafe.circuitbreaker.success.count", 3),
71+
metricAssert ->
72+
assertCircuitBreakerMetric(metricAssert, "failsafe.circuitbreaker.open.count", 1),
73+
metricAssert ->
74+
assertCircuitBreakerMetric(
75+
metricAssert, "failsafe.circuitbreaker.halfopen.count", 1),
76+
metricAssert ->
77+
assertCircuitBreakerMetric(
78+
metricAssert, "failsafe.circuitbreaker.closed.count", 1));
79+
}
80+
81+
private static void assertCircuitBreakerMetric(
82+
MetricAssert metricAssert, String counterName, long expectedValue) {
83+
MetricData closeCountData = metricAssert.actual();
84+
assertThat(closeCountData.getName(), equalTo(counterName));
85+
assertTrue(closeCountData.getData().getPoints().stream().findFirst().isPresent());
86+
LongPointData closeCountLongPointData =
87+
(LongPointData) closeCountData.getData().getPoints().stream().findFirst().get();
88+
assertThat(
89+
closeCountLongPointData.getAttributes(),
90+
equalTo(Attributes.of(AttributeKey.stringKey("name"), "testing")));
91+
assertThat(closeCountLongPointData.getValue(), equalTo(expectedValue));
92+
}
93+
}

settings.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -626,7 +626,8 @@ include(":instrumentation:xxl-job:xxl-job-2.3.0:javaagent")
626626
include(":instrumentation:xxl-job:xxl-job-common:javaagent")
627627
include(":instrumentation:xxl-job:xxl-job-common:testing")
628628
include(":instrumentation:zio:zio-2.0:javaagent")
629-
629+
include("instrumentation:failsafe-3.0:library")
630+
include("instrumentation:failsafe-3.0:testing")
630631
// benchmark
631632
include(":benchmark-overhead-jmh")
632633
include(":benchmark-jfr-analyzer")

0 commit comments

Comments
 (0)