Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
bccd0fd
Minimal viable instrumentation example with a test
ygree Jun 10, 2025
54affc6
Add latestDepTest
ygree Jun 11, 2025
e585701
Instrument decorateCheckedSupplier
ygree Jun 12, 2025
39d8d5d
Do not create resilience4j span if it already exist
ygree Jun 12, 2025
f5c160c
Instrument decorateCompletionStage
ygree Jun 12, 2025
76e196c
Fix the asynchronous test to ensure proper testing
ygree Jun 13, 2025
4f486f2
Extract DDContext
ygree Jun 16, 2025
b77dd6e
Start RetryInstrumentation
ygree Jun 16, 2025
b8a0fa0
Implemented a very minimal initial Retry CompletionStage instrumentat…
ygree Jun 16, 2025
8e7ec5b
Instrument Retry decorateCheckedSupplier and decorateSupplier.
ygree Jun 17, 2025
d1ca290
Span per decorator layer
ygree Jun 18, 2025
20ddcfd
Change instrumentation approach to only wrap top-level decorators.
ygree Jun 18, 2025
e970d72
Wrap resulting completionStage to finish a decorator span properly
ygree Jun 18, 2025
f71d32f
Wrap the inbound async supplier for scope handling and the outbound f…
ygree Jun 18, 2025
b2db626
Only wrap outbound decorator for both sync and async decorators.
ygree Jun 18, 2025
92be261
Fallback test scenarios
ygree Jun 19, 2025
2a46b25
Rename AbstractResilience4jInstrumentation
ygree Jun 20, 2025
9acbf73
Fallback supplier instrumentation
ygree Jun 20, 2025
ab60ef0
Supplier Fallback instrumentation
ygree Jun 20, 2025
a2ed779
Introduce FallbackCompletionStageInstrumentation
ygree Jun 20, 2025
e6e46e3
Add StackedDecoratorsTest
ygree Jun 20, 2025
e39a5af
Use a single span for the entire R4J decorator.
ygree Jun 23, 2025
512bf97
Use a single span for the entire R4J decorator.
ygree Jun 24, 2025
bc88b5f
Use a single span for the entire R4J decorator.
ygree Jun 24, 2025
4903361
Move out all context handling logic into DecoratorWithContext
ygree Jun 24, 2025
154b985
Move out all wrappers into the DecoratorWithContext
ygree Jun 24, 2025
bb43357
PoC span decorators
ygree Jun 25, 2025
04d4ff1
PoC reactor resilience4j circuit breaker instrumentation
ygree Jun 28, 2025
cf2e2d0
Simplify core r4j scope management. Rely on execution active span ins…
ygree Jun 30, 2025
d149cb0
Extract ActiveResilience4jSpan
ygree Jun 30, 2025
65d0163
Rename ActiveResilience4jSpan methods
ygree Jun 30, 2025
9f3bbe1
PoC Reactor Flux R4j instrumentation and R4j parameterized span decor…
ygree Jul 16, 2025
4b3e0fa
Split r4j core and reactor modules
ygree Jul 16, 2025
fd15ebf
Add RetryOperatorInstrumentation
ygree Jul 16, 2025
9e41b6e
Finish span whenComplete
ygree Jul 16, 2025
97b0d8c
Call span decorator
ygree Jul 16, 2025
d20c14e
Rework r4j reactor instrumentation. Using reactor-core-3.1's dd.span …
ygree Jul 23, 2025
3b5c590
FallbackOperatorInstrumentation WIP
ygree Jul 28, 2025
6b3de3c
WIP
ygree Aug 21, 2025
635649e
Start a separate span for each r4j decorator for easier debugging
ygree Aug 21, 2025
d61eafe
Introduce TraceCapturingTest
ygree Aug 22, 2025
8223f57
Introduce TraceCapturingTest
ygree Aug 22, 2025
c9940b6
Add FallbackTest with Fallback and Retry Mono support
ygree Aug 22, 2025
0091150
Collapse resilience4j spans into a single span.
ygree Sep 5, 2025
bdb4807
Extract scheduleSpanFinish
ygree Sep 5, 2025
f9ef6f7
Do not finish unowned span
ygree Sep 5, 2025
e32263f
test circuit-breaker state reporting
ygree Sep 5, 2025
b84cbea
Span decorators
ygree Sep 8, 2025
fb3fed1
Span decorators
ygree Sep 8, 2025
b6d6b5b
Span decorators
ygree Sep 9, 2025
5aa67be
Retry and Circuit Breaker decorators and tests
ygree Sep 10, 2025
520a67b
Clean up
ygree Sep 11, 2025
4149141
Support Function Decorators
ygree Sep 11, 2025
150157e
Shorten Retry metric tags
ygree Sep 11, 2025
a42fdfd
CheckedRunnable
ygree Sep 11, 2025
6ce6230
Callable
ygree Sep 11, 2025
5f758d6
Consumer CircuitBreaker decorator
ygree Sep 11, 2025
baf1d87
CheckedFunction CircuitBreaker decorator
ygree Sep 11, 2025
9189da8
CheckedFunction Retry decorator
ygree Sep 11, 2025
4d2a087
CircuitBreaker.decorateCheckedConsumer
ygree Sep 11, 2025
0fc35a4
CircuitBreaker.decorateRunnable
ygree Sep 11, 2025
745e0ed
Use instrumented static methods direcly in the tests
ygree Sep 11, 2025
6a9e38a
Reorder CircuitBreaker tests:
ygree Sep 11, 2025
1b49173
Instrument Retry.decorateRunnable
ygree Sep 12, 2025
ee7fdd6
Remove FallbackDecorator
ygree Sep 12, 2025
93573dc
Rename ContextHolder to WrapperWithContext. Get rid of explicit Objec…
ygree Sep 12, 2025
545c432
Implement FallbackCallable
ygree Sep 12, 2025
584510a
Add missing test cases for ofCompletionStage
ygree Sep 12, 2025
705e2b0
FallbackCheckedSupplierInstrumentation
ygree Sep 12, 2025
ffba62a
Instrument withFallback decorators directly
ygree Sep 12, 2025
e242e7c
Rename resilience4j-core-2 to resilience4j-2
ygree Sep 12, 2025
8883777
Fix and enable inverse muzzle check
ygree Sep 12, 2025
999e9af
Clean up
ygree Sep 12, 2025
b87bc50
Decorator clean up
ygree Sep 12, 2025
db8fea9
Support CircuitBreaker.decorateFuture
ygree Sep 12, 2025
6d75de1
Clean up and rearrange r4j-reactor tests
ygree Sep 12, 2025
df434f8
Fix muzzle check for resilience4j-reactor
ygree Sep 12, 2025
179888a
Merge branch 'master' into ygree/resilience4j
ygree Sep 15, 2025
e081b7d
Follow up fix after the rebase
ygree Sep 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dd-java-agent/instrumentation/resilience4j/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
apply from: "$rootDir/gradle/java.gradle"
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
ext {
minJavaVersionForTests = JavaVersion.VERSION_17 // TODO -PtestJvm=17
}

apply from: "$rootDir/gradle/java.gradle"
apply plugin: 'idea'

muzzle {
pass {
group = 'io.github.resilience4j'
module = 'resilience4j-all'
versions = '[2.0.0,)'
assertInverse = true
javaVersion = "17"
}
}

idea {
module {
jdkName = '17'
}
}

// Set all compile tasks to use JDK17 but let instrumentation code target 1.8 compatibility
project.tasks.withType(AbstractCompile).configureEach {
setJavaVersion(it, 17)
}
compileJava.configure {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

addTestSuiteForDir('latestDepTest', 'test')

dependencies {
compileOnly group: 'io.github.resilience4j', name: 'resilience4j-all', version: '2.0.0'

testImplementation group: 'io.github.resilience4j', name: 'resilience4j-all', version: '2.0.0'
latestDepTestImplementation group: 'io.github.resilience4j', name: 'resilience4j-all', version: '2.+'
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package datadog.trace.instrumentation.resilience4j;

import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
import io.github.resilience4j.circuitbreaker.CircuitBreaker;

public final class CircuitBreakerDecorator extends Resilience4jSpanDecorator<CircuitBreaker> {
public static final CircuitBreakerDecorator DECORATE = new CircuitBreakerDecorator();

private CircuitBreakerDecorator() {
super();
}

@Override
public void decorate(AgentSpan span, CircuitBreaker data) {
span.setTag("resilience4j.circuit_breaker.name", data.getName());
span.setTag("resilience4j.circuit_breaker.state", data.getState().toString());

CircuitBreaker.Metrics ms = data.getMetrics();
span.setTag("resilience4j.circuit-breaker.metrics.failure_rate", ms.getFailureRate());
span.setTag("resilience4j.circuit-breaker.metrics.slow_call_rate", ms.getSlowCallRate());
span.setTag("resilience4j.circuit-breaker.metrics.slow_calls", ms.getNumberOfSlowCalls());
span.setTag(
"resilience4j.circuit-breaker.metrics.slow_successful_calls",
ms.getNumberOfSlowSuccessfulCalls());
span.setTag(
"resilience4j.circuit-breaker.metrics.slow_failed_calls", ms.getNumberOfSlowFailedCalls());
span.setTag(
"resilience4j.circuit-breaker.metrics.buffered_calls", ms.getNumberOfBufferedCalls());
span.setTag("resilience4j.circuit-breaker.metrics.failed_calls", ms.getNumberOfFailedCalls());
span.setTag(
"resilience4j.circuit-breaker.metrics.not_permitted_calls",
ms.getNumberOfNotPermittedCalls());
span.setTag(
"resilience4j.circuit-breaker.metrics.successful_calls", ms.getNumberOfSuccessfulCalls());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
package datadog.trace.instrumentation.resilience4j;

import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.isStatic;
import static net.bytebuddy.matcher.ElementMatchers.returns;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;

import com.google.auto.service.AutoService;
import datadog.trace.agent.tooling.InstrumenterModule;
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.core.functions.CheckedConsumer;
import io.github.resilience4j.core.functions.CheckedFunction;
import io.github.resilience4j.core.functions.CheckedRunnable;
import io.github.resilience4j.core.functions.CheckedSupplier;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.Future;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import net.bytebuddy.asm.Advice;

@AutoService(InstrumenterModule.class)
public final class CircuitBreakerInstrumentation extends Resilience4jInstrumentation {

private static final String CIRCUIT_BREAKER_FQCN =
"io.github.resilience4j.circuitbreaker.CircuitBreaker";

private static final String THIS_CLASS = CircuitBreakerInstrumentation.class.getName();

public CircuitBreakerInstrumentation() {
super("resilience4j-circuitbreaker");
}

@Override
public String instrumentedType() {
return CIRCUIT_BREAKER_FQCN;
}

@Override
public void methodAdvice(MethodTransformer transformer) {
transformer.applyAdvice(
isMethod()
.and(isStatic())
.and(named("decorateCheckedSupplier"))
.and(takesArgument(0, named(CIRCUIT_BREAKER_FQCN)))
.and(returns(named(CHECKED_SUPPLIER_FQCN))),
THIS_CLASS + "$CheckedSupplierAdvice");
transformer.applyAdvice(
isMethod()
.and(isStatic())
.and(named("decorateCheckedFunction"))
.and(takesArgument(0, named(CIRCUIT_BREAKER_FQCN)))
.and(returns(named(CHECKED_FUNCTION_FQCN))),
THIS_CLASS + "$CheckedFunctionAdvice");
transformer.applyAdvice(
isMethod()
.and(isStatic())
.and(named("decorateCheckedConsumer"))
.and(takesArgument(0, named(CIRCUIT_BREAKER_FQCN)))
.and(returns(named(CHECKED_CONSUMER_FQCN))),
THIS_CLASS + "$CheckedConsumerAdvice");
transformer.applyAdvice(
isMethod()
.and(isStatic())
.and(named("decorateCompletionStage"))
.and(takesArgument(0, named(CIRCUIT_BREAKER_FQCN)))
.and(returns(named(SUPPLIER_FQCN))),
THIS_CLASS + "$CompletionStageAdvice");
transformer.applyAdvice(
isMethod()
.and(isStatic())
.and(named("decorateFuture"))
.and(takesArgument(0, named(CIRCUIT_BREAKER_FQCN)))
.and(returns(named(SUPPLIER_FQCN))),
THIS_CLASS + "$FutureAdvice");
transformer.applyAdvice(
isMethod()
.and(isStatic())
.and(named("decorateConsumer"))
.and(takesArgument(0, named(CIRCUIT_BREAKER_FQCN)))
.and(returns(named(CONSUMER_FQCN))),
THIS_CLASS + "$ConsumerAdvice");
transformer.applyAdvice(
isMethod()
.and(isStatic())
.and(named("decorateCheckedRunnable"))
.and(takesArgument(0, named(CIRCUIT_BREAKER_FQCN)))
.and(returns(named(CHECKED_RUNNABLE_FQCN))),
THIS_CLASS + "$CheckedRunnableAdvice");
transformer.applyAdvice(
isMethod()
.and(isStatic())
.and(named("decorateCallable"))
.and(takesArgument(0, named(CIRCUIT_BREAKER_FQCN)))
.and(returns(named(CALLABLE_FQCN))),
THIS_CLASS + "$CallableAdvice");
transformer.applyAdvice(
isMethod()
.and(isStatic())
.and(named("decorateRunnable"))
.and(takesArgument(0, named(CIRCUIT_BREAKER_FQCN)))
.and(returns(named(RUNNABLE_FQCN))),
THIS_CLASS + "$RunnableAdvice");
transformer.applyAdvice(
isMethod()
.and(isStatic())
.and(named("decorateSupplier"))
.and(takesArgument(0, named(CIRCUIT_BREAKER_FQCN)))
.and(returns(named(SUPPLIER_FQCN))),
THIS_CLASS + "$SupplierAdvice");
transformer.applyAdvice(
isMethod()
.and(isStatic())
.and(named("decorateFunction"))
.and(takesArgument(0, named(CIRCUIT_BREAKER_FQCN)))
.and(returns(named(FUNCTION_FQCN))),
THIS_CLASS + "$FunctionAdvice");
}

public static class SupplierAdvice {
@Advice.OnMethodExit(suppress = Throwable.class)
public static void afterExecute(
@Advice.Argument(value = 0) CircuitBreaker circuitBreaker,
@Advice.Return(readOnly = false) Supplier<?> outbound) {
outbound =
new WrapperWithContext.SupplierWithContext<>(
outbound, CircuitBreakerDecorator.DECORATE, circuitBreaker);
}
}

public static class CallableAdvice {
@Advice.OnMethodExit(suppress = Throwable.class)
public static void afterExecute(
@Advice.Argument(value = 0) CircuitBreaker circuitBreaker,
@Advice.Return(readOnly = false) Callable<?> outbound) {
outbound =
new WrapperWithContext.CallableWithContext<>(
outbound, CircuitBreakerDecorator.DECORATE, circuitBreaker);
}
}

public static class RunnableAdvice {
@Advice.OnMethodExit(suppress = Throwable.class)
public static void afterExecute(
@Advice.Argument(value = 0) CircuitBreaker circuitBreaker,
@Advice.Return(readOnly = false) Runnable outbound) {
outbound =
new WrapperWithContext.RunnableWithContext<>(
outbound, CircuitBreakerDecorator.DECORATE, circuitBreaker);
}
}

public static class FunctionAdvice {
@Advice.OnMethodExit(suppress = Throwable.class)
public static void afterExecute(
@Advice.Argument(value = 0) CircuitBreaker circuitBreaker,
@Advice.Return(readOnly = false) Function<?, ?> outbound) {
outbound =
new WrapperWithContext.FunctionWithContext<>(
outbound, CircuitBreakerDecorator.DECORATE, circuitBreaker);
}
}

public static class CheckedSupplierAdvice {
@Advice.OnMethodExit(suppress = Throwable.class)
public static void afterExecute(
@Advice.Argument(value = 0) CircuitBreaker circuitBreaker,
@Advice.Return(readOnly = false) CheckedSupplier<?> outbound) {
outbound =
new WrapperWithContext.CheckedSupplierWithContext<>(
outbound, CircuitBreakerDecorator.DECORATE, circuitBreaker);
}
}

public static class CheckedFunctionAdvice {
@Advice.OnMethodExit(suppress = Throwable.class)
public static void afterExecute(
@Advice.Argument(value = 0) CircuitBreaker circuitBreaker,
@Advice.Return(readOnly = false) CheckedFunction<?, ?> outbound) {
outbound =
new WrapperWithContext.CheckedFunctionWithContext<>(
outbound, CircuitBreakerDecorator.DECORATE, circuitBreaker);
}
}

public static class CheckedConsumerAdvice {
@Advice.OnMethodExit(suppress = Throwable.class)
public static void afterExecute(
@Advice.Argument(value = 0) CircuitBreaker circuitBreaker,
@Advice.Return(readOnly = false) CheckedConsumer<?> outbound) {
outbound =
new WrapperWithContext.CheckedConsumerWithContext<>(
outbound, CircuitBreakerDecorator.DECORATE, circuitBreaker);
}
}

public static class CheckedRunnableAdvice {
@Advice.OnMethodExit(suppress = Throwable.class)
public static void afterExecute(
@Advice.Argument(value = 0) CircuitBreaker circuitBreaker,
@Advice.Return(readOnly = false) CheckedRunnable outbound) {
outbound =
new WrapperWithContext.CheckedRunnableWithContext<>(
outbound, CircuitBreakerDecorator.DECORATE, circuitBreaker);
}
}

public static class ConsumerAdvice {
@Advice.OnMethodExit(suppress = Throwable.class)
public static void afterExecute(
@Advice.Argument(value = 0) CircuitBreaker circuitBreaker,
@Advice.Return(readOnly = false) Consumer<?> outbound) {
outbound =
new WrapperWithContext.ConsumerWithContext<>(
outbound, CircuitBreakerDecorator.DECORATE, circuitBreaker);
}
}

public static class CompletionStageAdvice {
@Advice.OnMethodExit(suppress = Throwable.class)
public static void afterExecute(
@Advice.Argument(value = 0) CircuitBreaker circuitBreaker,
@Advice.Return(readOnly = false) Supplier<CompletionStage<?>> outbound) {
outbound =
new WrapperWithContext.SupplierOfCompletionStageWithContext<>(
outbound, CircuitBreakerDecorator.DECORATE, circuitBreaker);
}
}

public static class FutureAdvice {
@Advice.OnMethodExit(suppress = Throwable.class)
public static void afterExecute(
@Advice.Argument(value = 0) CircuitBreaker circuitBreaker,
@Advice.Return(readOnly = false) Supplier<Future<?>> outbound) {
outbound =
new WrapperWithContext.SupplierOfFutureWithContext<>(
outbound, CircuitBreakerDecorator.DECORATE, circuitBreaker);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package datadog.trace.instrumentation.resilience4j;

import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;

import com.google.auto.service.AutoService;
import datadog.trace.agent.tooling.InstrumenterModule;
import io.github.resilience4j.core.functions.CheckedSupplier;
import java.util.concurrent.Callable;
import net.bytebuddy.asm.Advice;

@AutoService(InstrumenterModule.class)
public class FallbackCallableInstrumentation extends Resilience4jInstrumentation {
public FallbackCallableInstrumentation() {
super("resilience4j-fallback");
}

@Override
public String instrumentedType() {
return "io.github.resilience4j.decorators.Decorators$DecorateCallable";
}

@Override
public void methodAdvice(MethodTransformer transformer) {
transformer.applyAdvice(
isMethod().and(named("withFallback")),
FallbackCallableInstrumentation.class.getName() + "$CallableAdvice");
}

public static class CallableAdvice {
@Advice.OnMethodExit(suppress = Throwable.class)
public static void afterExecute(
@Advice.FieldValue(value = "callable", readOnly = false) Callable<?> callable) {
callable =
new WrapperWithContext.CallableWithContext<>(
callable, Resilience4jSpanDecorator.DECORATE, null);
}

// 2.0.0+
public static void muzzleCheck(CheckedSupplier<?> cs) throws Throwable {
cs.get();
}
}
}
Loading