Skip to content

Commit e9f8dc8

Browse files
authored
Resilience4j 2+ (#9525)
Resilience4j 2+ tracing instrumentation for core and reactor modules (circuit breaker, retry, and fallback)
1 parent 1546940 commit e9f8dc8

33 files changed

+3614
-0
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
ext {
2+
minJavaVersionForTests = JavaVersion.VERSION_17
3+
}
4+
5+
apply from: "$rootDir/gradle/java.gradle"
6+
apply plugin: 'idea'
7+
8+
muzzle {
9+
pass {
10+
group = 'io.github.resilience4j'
11+
module = 'resilience4j-all'
12+
versions = '[2.0.0,)'
13+
assertInverse = true
14+
javaVersion = "17"
15+
}
16+
}
17+
18+
idea {
19+
module {
20+
jdkName = '17'
21+
}
22+
}
23+
24+
// Set all compile tasks to use JDK17 but let instrumentation code target 1.8 compatibility
25+
project.tasks.withType(AbstractCompile).configureEach {
26+
setJavaVersion(it, 17)
27+
}
28+
compileJava.configure {
29+
sourceCompatibility = JavaVersion.VERSION_1_8
30+
targetCompatibility = JavaVersion.VERSION_1_8
31+
}
32+
33+
addTestSuiteForDir('latestDepTest', 'test')
34+
35+
dependencies {
36+
compileOnly group: 'io.github.resilience4j', name: 'resilience4j-all', version: '2.0.0'
37+
38+
testImplementation group: 'io.github.resilience4j', name: 'resilience4j-all', version: '2.0.0'
39+
latestDepTestImplementation group: 'io.github.resilience4j', name: 'resilience4j-all', version: '2.+'
40+
}
41+

dd-java-agent/instrumentation/resilience4j/resilience4j-2.0/gradle.lockfile

Lines changed: 189 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package datadog.trace.instrumentation.resilience4j;
2+
3+
import datadog.trace.api.Config;
4+
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
5+
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
6+
7+
public final class CircuitBreakerDecorator extends Resilience4jSpanDecorator<CircuitBreaker> {
8+
public static final CircuitBreakerDecorator DECORATE = new CircuitBreakerDecorator();
9+
public static final String TAG_PREFIX = "resilience4j.circuit_breaker.";
10+
public static final String TAG_METRICS_PREFIX = TAG_PREFIX + "metrics.";
11+
12+
private CircuitBreakerDecorator() {
13+
super();
14+
}
15+
16+
@Override
17+
public void decorate(AgentSpan span, CircuitBreaker data) {
18+
span.setTag(TAG_PREFIX + "name", data.getName());
19+
span.setTag(TAG_PREFIX + "state", data.getState().toString());
20+
if (Config.get().isResilience4jTagMetricsEnabled()) {
21+
CircuitBreaker.Metrics ms = data.getMetrics();
22+
span.setTag(TAG_METRICS_PREFIX + "failure_rate", ms.getFailureRate());
23+
span.setTag(TAG_METRICS_PREFIX + "slow_call_rate", ms.getSlowCallRate());
24+
span.setTag(TAG_METRICS_PREFIX + "slow_calls", ms.getNumberOfSlowCalls());
25+
span.setTag(
26+
TAG_METRICS_PREFIX + "slow_successful_calls", ms.getNumberOfSlowSuccessfulCalls());
27+
span.setTag(TAG_METRICS_PREFIX + "slow_failed_calls", ms.getNumberOfSlowFailedCalls());
28+
span.setTag(TAG_METRICS_PREFIX + "buffered_calls", ms.getNumberOfBufferedCalls());
29+
span.setTag(TAG_METRICS_PREFIX + "failed_calls", ms.getNumberOfFailedCalls());
30+
span.setTag(TAG_METRICS_PREFIX + "not_permitted_calls", ms.getNumberOfNotPermittedCalls());
31+
span.setTag(TAG_METRICS_PREFIX + "successful_calls", ms.getNumberOfSuccessfulCalls());
32+
}
33+
}
34+
}
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
package datadog.trace.instrumentation.resilience4j;
2+
3+
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
4+
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
5+
import static net.bytebuddy.matcher.ElementMatchers.isStatic;
6+
import static net.bytebuddy.matcher.ElementMatchers.returns;
7+
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
8+
9+
import datadog.trace.agent.tooling.Instrumenter;
10+
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
11+
import io.github.resilience4j.core.functions.CheckedConsumer;
12+
import io.github.resilience4j.core.functions.CheckedFunction;
13+
import io.github.resilience4j.core.functions.CheckedRunnable;
14+
import io.github.resilience4j.core.functions.CheckedSupplier;
15+
import java.util.concurrent.Callable;
16+
import java.util.concurrent.CompletionStage;
17+
import java.util.concurrent.Future;
18+
import java.util.function.Consumer;
19+
import java.util.function.Function;
20+
import java.util.function.Supplier;
21+
import net.bytebuddy.asm.Advice;
22+
23+
public final class CircuitBreakerInstrumentation
24+
implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice {
25+
private static final String CIRCUIT_BREAKER_FQCN =
26+
"io.github.resilience4j.circuitbreaker.CircuitBreaker";
27+
private static final String THIS_CLASS = CircuitBreakerInstrumentation.class.getName();
28+
29+
@Override
30+
public String instrumentedType() {
31+
return CIRCUIT_BREAKER_FQCN;
32+
}
33+
34+
@Override
35+
public void methodAdvice(MethodTransformer transformer) {
36+
transformer.applyAdvice(
37+
isMethod()
38+
.and(isStatic())
39+
.and(named("decorateCheckedSupplier"))
40+
.and(takesArgument(0, named(CIRCUIT_BREAKER_FQCN)))
41+
.and(returns(named("io.github.resilience4j.core.functions.CheckedSupplier"))),
42+
THIS_CLASS + "$CheckedSupplierAdvice");
43+
transformer.applyAdvice(
44+
isMethod()
45+
.and(isStatic())
46+
.and(named("decorateCheckedFunction"))
47+
.and(takesArgument(0, named(CIRCUIT_BREAKER_FQCN)))
48+
.and(returns(named("io.github.resilience4j.core.functions.CheckedFunction"))),
49+
THIS_CLASS + "$CheckedFunctionAdvice");
50+
transformer.applyAdvice(
51+
isMethod()
52+
.and(isStatic())
53+
.and(named("decorateCheckedConsumer"))
54+
.and(takesArgument(0, named(CIRCUIT_BREAKER_FQCN)))
55+
.and(returns(named("io.github.resilience4j.core.functions.CheckedConsumer"))),
56+
THIS_CLASS + "$CheckedConsumerAdvice");
57+
transformer.applyAdvice(
58+
isMethod()
59+
.and(isStatic())
60+
.and(named("decorateCompletionStage"))
61+
.and(takesArgument(0, named(CIRCUIT_BREAKER_FQCN)))
62+
.and(returns(named(Supplier.class.getName()))),
63+
THIS_CLASS + "$CompletionStageAdvice");
64+
transformer.applyAdvice(
65+
isMethod()
66+
.and(isStatic())
67+
.and(named("decorateFuture"))
68+
.and(takesArgument(0, named(CIRCUIT_BREAKER_FQCN)))
69+
.and(returns(named(Supplier.class.getName()))),
70+
THIS_CLASS + "$FutureAdvice");
71+
transformer.applyAdvice(
72+
isMethod()
73+
.and(isStatic())
74+
.and(named("decorateConsumer"))
75+
.and(takesArgument(0, named(CIRCUIT_BREAKER_FQCN)))
76+
.and(returns(named(Consumer.class.getName()))),
77+
THIS_CLASS + "$ConsumerAdvice");
78+
transformer.applyAdvice(
79+
isMethod()
80+
.and(isStatic())
81+
.and(named("decorateCheckedRunnable"))
82+
.and(takesArgument(0, named(CIRCUIT_BREAKER_FQCN)))
83+
.and(returns(named("io.github.resilience4j.core.functions.CheckedRunnable"))),
84+
THIS_CLASS + "$CheckedRunnableAdvice");
85+
transformer.applyAdvice(
86+
isMethod()
87+
.and(isStatic())
88+
.and(named("decorateCallable"))
89+
.and(takesArgument(0, named(CIRCUIT_BREAKER_FQCN)))
90+
.and(returns(named(Callable.class.getName()))),
91+
THIS_CLASS + "$CallableAdvice");
92+
transformer.applyAdvice(
93+
isMethod()
94+
.and(isStatic())
95+
.and(named("decorateRunnable"))
96+
.and(takesArgument(0, named(CIRCUIT_BREAKER_FQCN)))
97+
.and(returns(named(Runnable.class.getName()))),
98+
THIS_CLASS + "$RunnableAdvice");
99+
transformer.applyAdvice(
100+
isMethod()
101+
.and(isStatic())
102+
.and(named("decorateSupplier"))
103+
.and(takesArgument(0, named(CIRCUIT_BREAKER_FQCN)))
104+
.and(returns(named(Supplier.class.getName()))),
105+
THIS_CLASS + "$SupplierAdvice");
106+
transformer.applyAdvice(
107+
isMethod()
108+
.and(isStatic())
109+
.and(named("decorateFunction"))
110+
.and(takesArgument(0, named(CIRCUIT_BREAKER_FQCN)))
111+
.and(returns(named(Function.class.getName()))),
112+
THIS_CLASS + "$FunctionAdvice");
113+
}
114+
115+
public static class SupplierAdvice {
116+
@Advice.OnMethodExit(suppress = Throwable.class)
117+
public static void afterExecute(
118+
@Advice.Argument(value = 0) CircuitBreaker circuitBreaker,
119+
@Advice.Return(readOnly = false) Supplier<?> result) {
120+
result =
121+
new WrapperWithContext.SupplierWithContext<>(
122+
result, CircuitBreakerDecorator.DECORATE, circuitBreaker);
123+
}
124+
}
125+
126+
public static class CallableAdvice {
127+
@Advice.OnMethodExit(suppress = Throwable.class)
128+
public static void afterExecute(
129+
@Advice.Argument(value = 0) CircuitBreaker circuitBreaker,
130+
@Advice.Return(readOnly = false) Callable<?> result) {
131+
result =
132+
new WrapperWithContext.CallableWithContext<>(
133+
result, CircuitBreakerDecorator.DECORATE, circuitBreaker);
134+
}
135+
}
136+
137+
public static class RunnableAdvice {
138+
@Advice.OnMethodExit(suppress = Throwable.class)
139+
public static void afterExecute(
140+
@Advice.Argument(value = 0) CircuitBreaker circuitBreaker,
141+
@Advice.Return(readOnly = false) Runnable result) {
142+
result =
143+
new WrapperWithContext.RunnableWithContext<>(
144+
result, CircuitBreakerDecorator.DECORATE, circuitBreaker);
145+
}
146+
}
147+
148+
public static class FunctionAdvice {
149+
@Advice.OnMethodExit(suppress = Throwable.class)
150+
public static void afterExecute(
151+
@Advice.Argument(value = 0) CircuitBreaker circuitBreaker,
152+
@Advice.Return(readOnly = false) Function<?, ?> result) {
153+
result =
154+
new WrapperWithContext.FunctionWithContext<>(
155+
result, CircuitBreakerDecorator.DECORATE, circuitBreaker);
156+
}
157+
}
158+
159+
public static class CheckedSupplierAdvice {
160+
@Advice.OnMethodExit(suppress = Throwable.class)
161+
public static void afterExecute(
162+
@Advice.Argument(value = 0) CircuitBreaker circuitBreaker,
163+
@Advice.Return(readOnly = false) CheckedSupplier<?> result) {
164+
result =
165+
new WrapperWithContext.CheckedSupplierWithContext<>(
166+
result, CircuitBreakerDecorator.DECORATE, circuitBreaker);
167+
}
168+
}
169+
170+
public static class CheckedFunctionAdvice {
171+
@Advice.OnMethodExit(suppress = Throwable.class)
172+
public static void afterExecute(
173+
@Advice.Argument(value = 0) CircuitBreaker circuitBreaker,
174+
@Advice.Return(readOnly = false) CheckedFunction<?, ?> result) {
175+
result =
176+
new WrapperWithContext.CheckedFunctionWithContext<>(
177+
result, CircuitBreakerDecorator.DECORATE, circuitBreaker);
178+
}
179+
}
180+
181+
public static class CheckedConsumerAdvice {
182+
@Advice.OnMethodExit(suppress = Throwable.class)
183+
public static void afterExecute(
184+
@Advice.Argument(value = 0) CircuitBreaker circuitBreaker,
185+
@Advice.Return(readOnly = false) CheckedConsumer<?> result) {
186+
result =
187+
new WrapperWithContext.CheckedConsumerWithContext<>(
188+
result, CircuitBreakerDecorator.DECORATE, circuitBreaker);
189+
}
190+
}
191+
192+
public static class CheckedRunnableAdvice {
193+
@Advice.OnMethodExit(suppress = Throwable.class)
194+
public static void afterExecute(
195+
@Advice.Argument(value = 0) CircuitBreaker circuitBreaker,
196+
@Advice.Return(readOnly = false) CheckedRunnable result) {
197+
result =
198+
new WrapperWithContext.CheckedRunnableWithContext<>(
199+
result, CircuitBreakerDecorator.DECORATE, circuitBreaker);
200+
}
201+
}
202+
203+
public static class ConsumerAdvice {
204+
@Advice.OnMethodExit(suppress = Throwable.class)
205+
public static void afterExecute(
206+
@Advice.Argument(value = 0) CircuitBreaker circuitBreaker,
207+
@Advice.Return(readOnly = false) Consumer<?> result) {
208+
result =
209+
new WrapperWithContext.ConsumerWithContext<>(
210+
result, CircuitBreakerDecorator.DECORATE, circuitBreaker);
211+
}
212+
}
213+
214+
public static class CompletionStageAdvice {
215+
@Advice.OnMethodExit(suppress = Throwable.class)
216+
public static void afterExecute(
217+
@Advice.Argument(value = 0) CircuitBreaker circuitBreaker,
218+
@Advice.Return(readOnly = false) Supplier<CompletionStage<?>> result) {
219+
result =
220+
new WrapperWithContext.SupplierOfCompletionStageWithContext<>(
221+
result, CircuitBreakerDecorator.DECORATE, circuitBreaker);
222+
}
223+
}
224+
225+
public static class FutureAdvice {
226+
@Advice.OnMethodExit(suppress = Throwable.class)
227+
public static void afterExecute(
228+
@Advice.Argument(value = 0) CircuitBreaker circuitBreaker,
229+
@Advice.Return(readOnly = false) Supplier<Future<?>> result) {
230+
result =
231+
new WrapperWithContext.SupplierOfFutureWithContext<>(
232+
result, CircuitBreakerDecorator.DECORATE, circuitBreaker);
233+
}
234+
}
235+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package datadog.trace.instrumentation.resilience4j;
2+
3+
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
4+
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
5+
6+
import datadog.trace.agent.tooling.Instrumenter;
7+
import io.github.resilience4j.core.functions.CheckedSupplier;
8+
import java.util.concurrent.Callable;
9+
import net.bytebuddy.asm.Advice;
10+
11+
public class FallbackCallableInstrumentation
12+
implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice {
13+
14+
@Override
15+
public String instrumentedType() {
16+
return "io.github.resilience4j.decorators.Decorators$DecorateCallable";
17+
}
18+
19+
@Override
20+
public void methodAdvice(MethodTransformer transformer) {
21+
transformer.applyAdvice(
22+
isMethod().and(named("withFallback")),
23+
FallbackCallableInstrumentation.class.getName() + "$CallableAdvice");
24+
}
25+
26+
public static class CallableAdvice {
27+
@Advice.OnMethodExit(suppress = Throwable.class)
28+
public static void afterExecute(
29+
@Advice.FieldValue(value = "callable", readOnly = false) Callable<?> callable) {
30+
callable =
31+
new WrapperWithContext.CallableWithContext<>(
32+
callable, Resilience4jSpanDecorator.DECORATE, null);
33+
}
34+
35+
// 2.0.0+
36+
public static void muzzleCheck(CheckedSupplier<?> cs) throws Throwable {
37+
cs.get();
38+
}
39+
}
40+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package datadog.trace.instrumentation.resilience4j;
2+
3+
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
4+
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
5+
6+
import datadog.trace.agent.tooling.Instrumenter;
7+
import io.github.resilience4j.core.functions.CheckedSupplier;
8+
import net.bytebuddy.asm.Advice;
9+
10+
public class FallbackCheckedSupplierInstrumentation
11+
implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice {
12+
13+
@Override
14+
public String instrumentedType() {
15+
return "io.github.resilience4j.decorators.Decorators$DecorateCheckedSupplier";
16+
}
17+
18+
@Override
19+
public void methodAdvice(MethodTransformer transformer) {
20+
transformer.applyAdvice(
21+
isMethod().and(named("withFallback")),
22+
FallbackCheckedSupplierInstrumentation.class.getName() + "$CheckedSupplierAdvice");
23+
}
24+
25+
public static class CheckedSupplierAdvice {
26+
@Advice.OnMethodExit(suppress = Throwable.class)
27+
public static void afterExecute(
28+
@Advice.FieldValue(value = "supplier", readOnly = false) CheckedSupplier<?> supplier) {
29+
supplier =
30+
new WrapperWithContext.CheckedSupplierWithContext<>(
31+
supplier, Resilience4jSpanDecorator.DECORATE, null);
32+
}
33+
}
34+
}

0 commit comments

Comments
 (0)