From 60828636457d90b98484cced7e0b2439db435050 Mon Sep 17 00:00:00 2001 From: Andrea Marziali Date: Tue, 12 Nov 2024 15:46:47 +0100 Subject: [PATCH] Add reactor samples and doc (#7906) * Add reactor samples and doc * Apply suggestions from code review Co-authored-by: Bruce Bujon * review --------- Co-authored-by: Bruce Bujon --- docs/reactor/README.md | 116 +++++++++++++++++ docs/reactor/img.png | Bin 0 -> 7849 bytes docs/reactor/img_1.png | Bin 0 -> 12099 bytes docs/reactor/img_2.png | Bin 0 -> 13138 bytes docs/reactor/pom.xml | 83 ++++++++++++ .../src/test/java/test/ReactorTest.java | 120 ++++++++++++++++++ 6 files changed, 319 insertions(+) create mode 100644 docs/reactor/README.md create mode 100644 docs/reactor/img.png create mode 100644 docs/reactor/img_1.png create mode 100644 docs/reactor/img_2.png create mode 100644 docs/reactor/pom.xml create mode 100644 docs/reactor/src/test/java/test/ReactorTest.java diff --git a/docs/reactor/README.md b/docs/reactor/README.md new file mode 100644 index 00000000000..a55973ae6ca --- /dev/null +++ b/docs/reactor/README.md @@ -0,0 +1,116 @@ +# Reactor with dd-trace-java + +This project shows basic examples of manual tracing with reactor and the datadog java tracer. +The examples are provided in the form of JUnit tests. Manual tracing is achieved using the OpenTelemetry APIs. + +## Get started + +The project is configured to run with a JDK 17 and Apache Maven. +To get started on a command line, just type `mvn test`. It will generate and log traces in a json format on the +console. + +## How the context is propagated + +The Reactor context propagates bottom up from the first subscriber (last publisher) to the last subscriber (first publisher). +The datadog tracer captures the active span when the subscription happens (i.e. methods like `subcribe` or `block` are called) +and activates them when a publisher emits if there is no an already active span. + +This works out of the box for bottom-up propagation. For the opposite, top-down, the reactor context has to be used in order +to let know the publisher which span needs to be activated when emitting downstream. The span has to be +added to the context using the key `dd.span`. + +## Use cases + +The use cases are reflecting the tests in this project + +### Standard bottom-up context propagation + +The sample leverages the `@Trace` annotation to create a span when a method returning a `Mono` or `Flux` is called. +The annotation is available as part of the `dd-trace-api` library. Alternatively, the OpenTelemetry equivalent `@WithSpan` can +also be used. + +```java +@Trace(operationName = "mono", resourceName = "mono") + private Mono monoMethod() { + // ... + } +``` + +Since the tracer runs with the option `-Ddd.trace.annotation.async=true`, it will finish the span when the `Mono` +will complete and not when the method will return. + +In this test the context is captured when `block()` is called and every other span opened by the upstream operators +will have it as parent. + +The diagram below shows the span propagated onSubscribe (up arrows) +and the span propagated downstream onNext/onError/onComplete (down arrows). + +```mermaid +graph TD; + m1[Mono.just
creates mono]-->|parent|m2; + m2[Mono.map
creates child]-->|parent|m3[Mono.block]; + m3-->|parent|m2; + m2-->|parent|m1; +``` + +The resulting trace: + +![img.png](img.png) + + +### Top Down context propagation + +The context propagation can be changed by advising the span to publish via the reactor `Context`. +The span should be put under the key `dd.span`. +As soon as a publisher emits this span, all the downstream operators will also have that span as active. +It is important to use `ContextWrite` in the right places in the reactive chain for this reason. + +Relating to the `testSimpleDownstreamPropagation` test case, the reactive chains will capture `parent` as bottom-up propagation +when `block` is called, but then the propagation is changed when `contextWrite("dd.span", ...)` is called. + +The diagram below shows what's happening: + +```mermaid +graph TD; + m1[Mono.defer+contextWrite
creates mono]-->|mono|m2; + m2[Mono.map
creates child]-->|mono|m3[Mono.block]; + m3-->|parent|m2; + m2-->|parent|m1; +``` + +The resulting trace: + +![img_1.png](img_1.png) + +### A more complex scenario + +`ContextWrite` can be called several times in the chain in order to change the active span that will be propagated. +In fact, generally speaking, when a span is put in the context, it will be propagated by all the upstream publishers +that will have visibility to that reactor context. + +Referring to the `testComplexDownstreamPropagation` test case, the propagation is resumed in the following (simplified) diagram: + +```mermaid +graph TD; + m1[Mono.flatmap
creates first]-->|first|m2; + m2[Mono.contextWrite
set first]-->|first|m3; + m3[Mono.map
creates child]-->|first|m4; + m4[Mono.flatmap
creates second]-->|second|m5; + m5[Mono.contextWrite
set second]-->|second|m6; + m6[Mono.flatmap+contextWrite
creates third]-->|third|m7; + m7[Mono.flatmap+contextWrite
creates third]-->|third|m8; + m8[Mono.map
creates child]-->|third|m9[Mono.block]; + m6-->|parent|m5; + m5-->|parent|m4; + m4-->|third|m3; + m3-->|second|m2; + m2-->|second|m1; + m10[start]-->|parent|m1; +``` + +The graph starts with parent since it's the span captured when `block` is called. +Each flatmap changes the context's active span when `onNext` is signaled + +The resulting trace: +![img_2.png](img_2.png) + diff --git a/docs/reactor/img.png b/docs/reactor/img.png new file mode 100644 index 0000000000000000000000000000000000000000..07e96b8d0c79954efd4c3ce1e653e5910b6dae77 GIT binary patch literal 7849 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 literal 0 HcmV?d00001 diff --git a/docs/reactor/img_1.png b/docs/reactor/img_1.png new file mode 100644 index 0000000000000000000000000000000000000000..029ce177f6aadb9909020c428b308d96584388e1 GIT binary patch literal 12099 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 + + 4.0.0 + + org.example + dd-trace-java-reactor-examples + 1.0-SNAPSHOT + + + 17 + 17 + UTF-8 + + + + io.projectreactor + reactor-core + 3.6.11 + + + org.junit.jupiter + junit-jupiter-engine + 5.11.2 + test + + + + ch.qos.logback + logback-classic + 1.5.11 + + + io.opentelemetry + opentelemetry-api + 1.43.0 + + + com.datadoghq + dd-trace-api + 1.42.0 + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 3.8.0 + + + copy-agent + process-test-classes + + copy + + + + + com.datadoghq + dd-java-agent + 1.42.0 + ${project.build.directory}/agents + dd-java-agent.jar + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.12.4 + + -javaagent:${project.build.directory}/agents/dd-java-agent.jar -Ddd.trace.otel.enabled=true -Ddd.trace.annotation.async=true + + + + + + diff --git a/docs/reactor/src/test/java/test/ReactorTest.java b/docs/reactor/src/test/java/test/ReactorTest.java new file mode 100644 index 00000000000..ef23c811f35 --- /dev/null +++ b/docs/reactor/src/test/java/test/ReactorTest.java @@ -0,0 +1,120 @@ +package test; + +import datadog.trace.api.Trace; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanBuilder; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Scope; +import java.time.Duration; +import java.util.Objects; +import java.util.Random; +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; +import reactor.util.context.Context; + +public class ReactorTest { + + @Trace(operationName = "child", resourceName = "child") + private String doSomeMapping(String s) { + try { + // simulate some work + Thread.sleep(500 + new Random(System.currentTimeMillis()).nextInt(1000)); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + } + return s; + } + + @Trace(operationName = "mono", resourceName = "mono") + private Mono monoMethod() { + // This mono will complete when the delay is expired + return Mono.delay(Duration.ofSeconds(1)).map(ignored -> "Hello World"); + } + + @Trace(operationName = "mono", resourceName = "mono") + private Mono monoMethodDownstreamPropagate() { + // here the active span is the one created by the @Trace annotation before the method executes + return Mono.just("Hello World").contextWrite(Context.of("dd.span", Span.current())); + } + + private Mono tracedMono( + final Tracer tracer, final String spanName, Span parentSpan, final Mono mono) { + SpanBuilder spanBuilder = tracer.spanBuilder(spanName); + if (parentSpan != null) { + spanBuilder.setParent(io.opentelemetry.context.Context.current().with(parentSpan)); + } + final Span span = spanBuilder.startSpan(); + return mono // + .contextWrite(Context.of("dd.span", span)) + .doFinally(ignored -> span.end()); + } + + @Test + public void testSimpleUpstreamPropagation() { + final Tracer tracer = GlobalOpenTelemetry.getTracer(""); + final Span parent = tracer.spanBuilder("parent").startSpan(); + assert io.opentelemetry.context.Context.current() == io.opentelemetry.context.Context.root(); + try (final Scope parentScope = parent.makeCurrent()) { + // monoMethod will start a trace when called but that span will complete only when the + // returned mono completes. + // doSomeMapping will open a span that's child of parent because it's the active one when we + // subscribe + assert Objects.equals(monoMethod().map(this::doSomeMapping).block(), "Hello World"); + } finally { + parent.end(); + } + } + + @Test + public void testSimpleDownstreamPropagation() { + final Tracer tracer = GlobalOpenTelemetry.getTracer(""); + final Span parent = tracer.spanBuilder("parent").startSpan(); + assert io.opentelemetry.context.Context.current() == io.opentelemetry.context.Context.root(); + try (final Scope parentScope = parent.makeCurrent()) { + // monoMethod will start a trace when called but that span will complete only when the + // returned mono completes. + // doSomeMapping will open a span that's child of parent because it's the active one when we + // subscribe + assert Objects.equals( + Mono.defer(this::monoMethodDownstreamPropagate).map(this::doSomeMapping).block(), + "Hello World"); + } finally { + parent.end(); + } + } + + @Test + public void testComplexDownstreamPropagation() { + final Tracer tracer = GlobalOpenTelemetry.getTracer(""); + final Span parent = tracer.spanBuilder("parent").startSpan(); + assert io.opentelemetry.context.Context.current() == io.opentelemetry.context.Context.root(); + + Mono mono = + // here we have no active span. when the mono is emitted we propagate the context captured + // onSubscribe + Mono.just("Hello World") // + // change the downstream propagated span to that new one called 'first' + // first will be child of parent since parent was captured onSubscribe + // (when block is called) and propagated upstream + .flatMap(s -> tracedMono(tracer, "first", null, Mono.just(s + ", GoodBye "))) + // map will use the active one (first) hence the child will be under first + .map(this::doSomeMapping) + // we change again the downstream active span to 'second' that's child of 'first' + .flatMap( + s -> + tracedMono( + tracer, "second", null, Mono.create(sink -> sink.success(s + "World")))) + // now we let the downstream propagate third child of parent + .flatMap(s -> tracedMono(tracer, "third", parent, Mono.just(s + "!"))) + // third is the active span downstream + .map(this::doSomeMapping) // will create a child span having third as parent + .doOnNext(System.out::println); + try (final Scope parentScope = parent.makeCurrent()) { + // block, like subscribe will capture the current scope (parent here) and propagate upstream + assert Objects.equals(mono.block(), "Hello World, GoodBye World!"); + } finally { + parent.end(); + } + } +}