Add AsyncSpanEndStrategy for Reactor 3.x instrumentation (#2714)
This commit is contained in:
parent
fa359a4a5d
commit
c7e431404e
|
@ -13,10 +13,13 @@ dependencies {
|
||||||
implementation project(':instrumentation:reactor-3.1:library')
|
implementation project(':instrumentation:reactor-3.1:library')
|
||||||
|
|
||||||
testLibrary group: 'io.projectreactor', name: 'reactor-core', version: '3.1.0.RELEASE'
|
testLibrary group: 'io.projectreactor', name: 'reactor-core', version: '3.1.0.RELEASE'
|
||||||
|
testLibrary group: 'io.projectreactor', name: 'reactor-test', version: '3.1.0.RELEASE'
|
||||||
|
|
||||||
testImplementation project(':instrumentation:reactor-3.1:testing')
|
testImplementation project(':instrumentation:reactor-3.1:testing')
|
||||||
|
testImplementation deps.opentelemetryExtAnnotations
|
||||||
|
|
||||||
latestDepTestLibrary group: 'io.projectreactor', name: 'reactor-core', version: '3.+'
|
latestDepTestLibrary group: 'io.projectreactor', name: 'reactor-core', version: '3.+'
|
||||||
|
latestDepTestLibrary group: 'io.projectreactor', name: 'reactor-test', version: '3.+'
|
||||||
// Looks like later versions on reactor need this dependency for some reason even though it is marked as optional.
|
// Looks like later versions on reactor need this dependency for some reason even though it is marked as optional.
|
||||||
latestDepTestLibrary group: 'io.micrometer', name: 'micrometer-core', version: '1.+'
|
latestDepTestLibrary group: 'io.micrometer', name: 'micrometer-core', version: '1.+'
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,248 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import io.opentelemetry.api.trace.SpanKind
|
||||||
|
import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification
|
||||||
|
import io.opentelemetry.instrumentation.reactor.TracedWithSpan
|
||||||
|
import reactor.core.publisher.Flux
|
||||||
|
import reactor.core.publisher.Mono
|
||||||
|
import reactor.core.publisher.UnicastProcessor
|
||||||
|
import reactor.test.StepVerifier
|
||||||
|
|
||||||
|
class ReactorWithSpanInstrumentationTest extends AgentInstrumentationSpecification {
|
||||||
|
|
||||||
|
def "should capture span for already completed Mono"() {
|
||||||
|
setup:
|
||||||
|
def source = Mono.just("Value")
|
||||||
|
def result = new TracedWithSpan()
|
||||||
|
.mono(source)
|
||||||
|
|
||||||
|
expect:
|
||||||
|
StepVerifier.create(result)
|
||||||
|
.expectNext("Value")
|
||||||
|
.verifyComplete()
|
||||||
|
|
||||||
|
assertTraces(1) {
|
||||||
|
trace(0, 1) {
|
||||||
|
span(0) {
|
||||||
|
name "TracedWithSpan.mono"
|
||||||
|
kind SpanKind.INTERNAL
|
||||||
|
hasNoParent()
|
||||||
|
errored false
|
||||||
|
attributes {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def "should capture span for eventually completed Mono"() {
|
||||||
|
setup:
|
||||||
|
def source = UnicastProcessor.<String>create()
|
||||||
|
def mono = source.singleOrEmpty()
|
||||||
|
def result = new TracedWithSpan()
|
||||||
|
.mono(mono)
|
||||||
|
def verifier = StepVerifier.create(result)
|
||||||
|
.expectSubscription()
|
||||||
|
|
||||||
|
expect:
|
||||||
|
Thread.sleep(500) // sleep a bit just to make sure no span is captured
|
||||||
|
assertTraces(0) {}
|
||||||
|
|
||||||
|
source.onNext("Value")
|
||||||
|
source.onComplete()
|
||||||
|
|
||||||
|
verifier.expectNext("Value")
|
||||||
|
.verifyComplete()
|
||||||
|
|
||||||
|
assertTraces(1) {
|
||||||
|
trace(0, 1) {
|
||||||
|
span(0) {
|
||||||
|
name "TracedWithSpan.mono"
|
||||||
|
kind SpanKind.INTERNAL
|
||||||
|
hasNoParent()
|
||||||
|
errored false
|
||||||
|
attributes {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def "should capture span for already errored Mono"() {
|
||||||
|
setup:
|
||||||
|
def error = new IllegalArgumentException("Boom")
|
||||||
|
def source = Mono.error(error)
|
||||||
|
def result = new TracedWithSpan()
|
||||||
|
.mono(source)
|
||||||
|
|
||||||
|
expect:
|
||||||
|
StepVerifier.create(result)
|
||||||
|
.verifyErrorMatches({ it == error })
|
||||||
|
|
||||||
|
assertTraces(1) {
|
||||||
|
trace(0, 1) {
|
||||||
|
span(0) {
|
||||||
|
name "TracedWithSpan.mono"
|
||||||
|
kind SpanKind.INTERNAL
|
||||||
|
hasNoParent()
|
||||||
|
errored true
|
||||||
|
errorEvent(IllegalArgumentException, "Boom")
|
||||||
|
attributes {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def "should capture span for eventually errored Mono"() {
|
||||||
|
setup:
|
||||||
|
def error = new IllegalArgumentException("Boom")
|
||||||
|
def source = UnicastProcessor.<String>create()
|
||||||
|
def mono = source.singleOrEmpty()
|
||||||
|
def result = new TracedWithSpan()
|
||||||
|
.mono(mono)
|
||||||
|
def verifier = StepVerifier.create(result)
|
||||||
|
.expectSubscription()
|
||||||
|
|
||||||
|
expect:
|
||||||
|
Thread.sleep(500) // sleep a bit just to make sure no span is captured
|
||||||
|
assertTraces(0) {}
|
||||||
|
|
||||||
|
source.onError(error)
|
||||||
|
|
||||||
|
verifier
|
||||||
|
.verifyErrorMatches({ it == error })
|
||||||
|
|
||||||
|
assertTraces(1) {
|
||||||
|
trace(0, 1) {
|
||||||
|
span(0) {
|
||||||
|
name "TracedWithSpan.mono"
|
||||||
|
kind SpanKind.INTERNAL
|
||||||
|
hasNoParent()
|
||||||
|
errored true
|
||||||
|
errorEvent(IllegalArgumentException, "Boom")
|
||||||
|
attributes {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def "should capture span for already completed Flux"() {
|
||||||
|
setup:
|
||||||
|
def source = Flux.just("Value")
|
||||||
|
def result = new TracedWithSpan()
|
||||||
|
.flux(source)
|
||||||
|
|
||||||
|
expect:
|
||||||
|
StepVerifier.create(result)
|
||||||
|
.expectNext("Value")
|
||||||
|
.verifyComplete()
|
||||||
|
|
||||||
|
assertTraces(1) {
|
||||||
|
trace(0, 1) {
|
||||||
|
span(0) {
|
||||||
|
name "TracedWithSpan.flux"
|
||||||
|
kind SpanKind.INTERNAL
|
||||||
|
hasNoParent()
|
||||||
|
errored false
|
||||||
|
attributes {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def "should capture span for eventually completed Flux"() {
|
||||||
|
setup:
|
||||||
|
def source = UnicastProcessor.<String>create()
|
||||||
|
def result = new TracedWithSpan()
|
||||||
|
.flux(source)
|
||||||
|
def verifier = StepVerifier.create(result)
|
||||||
|
.expectSubscription()
|
||||||
|
|
||||||
|
expect:
|
||||||
|
Thread.sleep(500) // sleep a bit just to make sure no span is captured
|
||||||
|
assertTraces(0) {}
|
||||||
|
|
||||||
|
source.onNext("Value")
|
||||||
|
source.onComplete()
|
||||||
|
|
||||||
|
verifier.expectNext("Value")
|
||||||
|
.verifyComplete()
|
||||||
|
|
||||||
|
assertTraces(1) {
|
||||||
|
trace(0, 1) {
|
||||||
|
span(0) {
|
||||||
|
name "TracedWithSpan.flux"
|
||||||
|
kind SpanKind.INTERNAL
|
||||||
|
hasNoParent()
|
||||||
|
errored false
|
||||||
|
attributes {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def "should capture span for already errored Flux"() {
|
||||||
|
setup:
|
||||||
|
def error = new IllegalArgumentException("Boom")
|
||||||
|
def source = Flux.error(error)
|
||||||
|
def result = new TracedWithSpan()
|
||||||
|
.flux(source)
|
||||||
|
|
||||||
|
expect:
|
||||||
|
StepVerifier.create(result)
|
||||||
|
.verifyErrorMatches({ it == error })
|
||||||
|
|
||||||
|
assertTraces(1) {
|
||||||
|
trace(0, 1) {
|
||||||
|
span(0) {
|
||||||
|
name "TracedWithSpan.flux"
|
||||||
|
kind SpanKind.INTERNAL
|
||||||
|
hasNoParent()
|
||||||
|
errored true
|
||||||
|
errorEvent(IllegalArgumentException, "Boom")
|
||||||
|
attributes {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def "should capture span for eventually errored Flux"() {
|
||||||
|
setup:
|
||||||
|
def error = new IllegalArgumentException("Boom")
|
||||||
|
def source = UnicastProcessor.<String>create()
|
||||||
|
def result = new TracedWithSpan()
|
||||||
|
.flux(source)
|
||||||
|
def verifier = StepVerifier.create(result)
|
||||||
|
.expectSubscription()
|
||||||
|
|
||||||
|
expect:
|
||||||
|
Thread.sleep(500) // sleep a bit just to make sure no span is captured
|
||||||
|
assertTraces(0) {}
|
||||||
|
|
||||||
|
source.onError(error)
|
||||||
|
|
||||||
|
verifier.verifyErrorMatches({ it == error })
|
||||||
|
|
||||||
|
assertTraces(1) {
|
||||||
|
trace(0, 1) {
|
||||||
|
span(0) {
|
||||||
|
name "TracedWithSpan.flux"
|
||||||
|
kind SpanKind.INTERNAL
|
||||||
|
hasNoParent()
|
||||||
|
errored true
|
||||||
|
errorEvent(IllegalArgumentException, "Boom")
|
||||||
|
attributes {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.reactor;
|
||||||
|
|
||||||
|
import io.opentelemetry.extension.annotations.WithSpan;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
public class TracedWithSpan {
|
||||||
|
@WithSpan
|
||||||
|
public Mono<String> mono(Mono<String> mono) {
|
||||||
|
return mono;
|
||||||
|
}
|
||||||
|
|
||||||
|
@WithSpan
|
||||||
|
public Flux<String> flux(Flux<String> flux) {
|
||||||
|
return flux;
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,10 +2,11 @@ apply from: "$rootDir/gradle/instrumentation-library.gradle"
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
library group: 'io.projectreactor', name: 'reactor-core', version: '3.1.0.RELEASE'
|
library group: 'io.projectreactor', name: 'reactor-core', version: '3.1.0.RELEASE'
|
||||||
|
testLibrary group: 'io.projectreactor', name: 'reactor-test', version: '3.1.0.RELEASE'
|
||||||
|
|
||||||
testImplementation project(':instrumentation:reactor-3.1:testing')
|
testImplementation project(':instrumentation:reactor-3.1:testing')
|
||||||
|
|
||||||
latestDepTestLibrary group: 'io.projectreactor', name: 'reactor-core', version: '3.+'
|
latestDepTestLibrary group: 'io.projectreactor', name: 'reactor-core', version: '3.+'
|
||||||
|
latestDepTestLibrary group: 'io.projectreactor', name: 'reactor-test', version: '3.+'
|
||||||
// Looks like later versions on reactor need this dependency for some reason even though it is marked as optional.
|
// Looks like later versions on reactor need this dependency for some reason even though it is marked as optional.
|
||||||
latestDepTestLibrary group: 'io.micrometer', name: 'micrometer-core', version: '1.+'
|
latestDepTestLibrary group: 'io.micrometer', name: 'micrometer-core', version: '1.+'
|
||||||
}
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.reactor;
|
||||||
|
|
||||||
|
import io.opentelemetry.context.Context;
|
||||||
|
import io.opentelemetry.instrumentation.api.tracer.BaseTracer;
|
||||||
|
import io.opentelemetry.instrumentation.api.tracer.async.AsyncSpanEndStrategy;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import org.reactivestreams.Publisher;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
public enum ReactorAsyncSpanEndStrategy implements AsyncSpanEndStrategy {
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(Class<?> returnType) {
|
||||||
|
return returnType == Publisher.class || returnType == Mono.class || returnType == Flux.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object end(BaseTracer tracer, Context context, Object returnValue) {
|
||||||
|
|
||||||
|
EndOnFirstNotificationConsumer notificationConsumer =
|
||||||
|
new EndOnFirstNotificationConsumer(tracer, context);
|
||||||
|
if (returnValue instanceof Mono) {
|
||||||
|
Mono<?> mono = (Mono<?>) returnValue;
|
||||||
|
return mono.doOnError(notificationConsumer).doOnSuccess(notificationConsumer::onSuccess);
|
||||||
|
} else {
|
||||||
|
Flux<?> flux = Flux.from((Publisher<?>) returnValue);
|
||||||
|
return flux.doOnError(notificationConsumer).doOnComplete(notificationConsumer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class to ensure that the span is ended exactly once regardless of how many OnComplete or
|
||||||
|
* OnError notifications are received. Multiple notifications can happen anytime multiple
|
||||||
|
* subscribers subscribe to the same publisher.
|
||||||
|
*/
|
||||||
|
private static final class EndOnFirstNotificationConsumer extends AtomicBoolean
|
||||||
|
implements Runnable, Consumer<Throwable> {
|
||||||
|
|
||||||
|
private final BaseTracer tracer;
|
||||||
|
private final Context context;
|
||||||
|
|
||||||
|
public EndOnFirstNotificationConsumer(BaseTracer tracer, Context context) {
|
||||||
|
super(false);
|
||||||
|
this.tracer = tracer;
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> void onSuccess(T ignored) {
|
||||||
|
accept(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
accept(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void accept(Throwable exception) {
|
||||||
|
if (compareAndSet(false, true)) {
|
||||||
|
if (exception != null) {
|
||||||
|
tracer.endExceptionally(context, exception);
|
||||||
|
} else {
|
||||||
|
tracer.end(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,6 +23,7 @@
|
||||||
package io.opentelemetry.instrumentation.reactor;
|
package io.opentelemetry.instrumentation.reactor;
|
||||||
|
|
||||||
import io.opentelemetry.context.Context;
|
import io.opentelemetry.context.Context;
|
||||||
|
import io.opentelemetry.instrumentation.api.tracer.async.AsyncSpanEndStrategies;
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import org.reactivestreams.Publisher;
|
import org.reactivestreams.Publisher;
|
||||||
|
@ -43,11 +44,13 @@ public class TracingOperator {
|
||||||
*/
|
*/
|
||||||
public static void registerOnEachOperator() {
|
public static void registerOnEachOperator() {
|
||||||
Hooks.onEachOperator(TracingSubscriber.class.getName(), tracingLift());
|
Hooks.onEachOperator(TracingSubscriber.class.getName(), tracingLift());
|
||||||
|
AsyncSpanEndStrategies.getInstance().registerStrategy(ReactorAsyncSpanEndStrategy.INSTANCE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Unregisters the hook registered by {@link #registerOnEachOperator()}. */
|
/** Unregisters the hook registered by {@link #registerOnEachOperator()}. */
|
||||||
public static void resetOnEachOperator() {
|
public static void resetOnEachOperator() {
|
||||||
Hooks.resetOnEachOperator(TracingSubscriber.class.getName());
|
Hooks.resetOnEachOperator(TracingSubscriber.class.getName());
|
||||||
|
AsyncSpanEndStrategies.getInstance().unregisterStrategy(ReactorAsyncSpanEndStrategy.INSTANCE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T> Function<? super Publisher<T>, ? extends Publisher<T>> tracingLift() {
|
private static <T> Function<? super Publisher<T>, ? extends Publisher<T>> tracingLift() {
|
||||||
|
|
|
@ -0,0 +1,273 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.reactor
|
||||||
|
|
||||||
|
import io.opentelemetry.context.Context
|
||||||
|
import io.opentelemetry.instrumentation.api.tracer.BaseTracer
|
||||||
|
import reactor.core.publisher.Flux
|
||||||
|
import reactor.core.publisher.Mono
|
||||||
|
import reactor.core.publisher.UnicastProcessor
|
||||||
|
import reactor.test.StepVerifier
|
||||||
|
import spock.lang.Specification
|
||||||
|
|
||||||
|
class ReactorAsyncSpanEndStrategyTest extends Specification {
|
||||||
|
BaseTracer tracer
|
||||||
|
|
||||||
|
Context context
|
||||||
|
|
||||||
|
def underTest = ReactorAsyncSpanEndStrategy.INSTANCE
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
tracer = Mock()
|
||||||
|
context = Mock()
|
||||||
|
}
|
||||||
|
|
||||||
|
static class MonoTest extends ReactorAsyncSpanEndStrategyTest {
|
||||||
|
def "is supported"() {
|
||||||
|
expect:
|
||||||
|
underTest.supports(Mono)
|
||||||
|
}
|
||||||
|
|
||||||
|
def "ends span on already completed"() {
|
||||||
|
when:
|
||||||
|
def result = (Mono<?>) underTest.end(tracer, context, Mono.just("Value"))
|
||||||
|
StepVerifier.create(result)
|
||||||
|
.expectNext("Value")
|
||||||
|
.verifyComplete()
|
||||||
|
|
||||||
|
then:
|
||||||
|
1 * tracer.end(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
def "ends span on already empty"() {
|
||||||
|
when:
|
||||||
|
def result = (Mono<?>) underTest.end(tracer, context, Mono.empty())
|
||||||
|
StepVerifier.create(result)
|
||||||
|
.verifyComplete()
|
||||||
|
|
||||||
|
then:
|
||||||
|
1 * tracer.end(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
def "ends span on already errored"() {
|
||||||
|
given:
|
||||||
|
def exception = new IllegalStateException()
|
||||||
|
|
||||||
|
when:
|
||||||
|
def result = (Mono<?>) underTest.end(tracer, context, Mono.error(exception))
|
||||||
|
StepVerifier.create(result)
|
||||||
|
.verifyErrorMatches({ it == exception })
|
||||||
|
|
||||||
|
then:
|
||||||
|
1 * tracer.endExceptionally(context, exception)
|
||||||
|
}
|
||||||
|
|
||||||
|
def "ends span when completed"() {
|
||||||
|
given:
|
||||||
|
def source = UnicastProcessor.<String>create()
|
||||||
|
def mono = source.singleOrEmpty()
|
||||||
|
|
||||||
|
when:
|
||||||
|
def result = (Mono<?>) underTest.end(tracer, context, mono)
|
||||||
|
def verifier = StepVerifier.create(result)
|
||||||
|
.expectSubscription()
|
||||||
|
|
||||||
|
then:
|
||||||
|
0 * tracer._
|
||||||
|
|
||||||
|
when:
|
||||||
|
source.onNext("Value")
|
||||||
|
source.onComplete()
|
||||||
|
verifier.expectNext("Value")
|
||||||
|
.verifyComplete()
|
||||||
|
|
||||||
|
then:
|
||||||
|
1 * tracer.end(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
def "ends span when empty"() {
|
||||||
|
given:
|
||||||
|
def source = UnicastProcessor.<String>create()
|
||||||
|
def mono = source.singleOrEmpty()
|
||||||
|
|
||||||
|
when:
|
||||||
|
def result = (Mono<?>) underTest.end(tracer, context, mono)
|
||||||
|
def verifier = StepVerifier.create(result)
|
||||||
|
.expectSubscription()
|
||||||
|
|
||||||
|
then:
|
||||||
|
0 * tracer._
|
||||||
|
|
||||||
|
when:
|
||||||
|
source.onComplete()
|
||||||
|
verifier.verifyComplete()
|
||||||
|
|
||||||
|
then:
|
||||||
|
1 * tracer.end(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
def "ends span when errored"() {
|
||||||
|
given:
|
||||||
|
def exception = new IllegalStateException()
|
||||||
|
def source = UnicastProcessor.<String>create()
|
||||||
|
def mono = source.singleOrEmpty()
|
||||||
|
|
||||||
|
when:
|
||||||
|
def result = (Mono<?>) underTest.end(tracer, context, mono)
|
||||||
|
def verifier = StepVerifier.create(result)
|
||||||
|
.expectSubscription()
|
||||||
|
|
||||||
|
then:
|
||||||
|
0 * tracer._
|
||||||
|
|
||||||
|
when:
|
||||||
|
source.onError(exception)
|
||||||
|
verifier.verifyErrorMatches({ it == exception })
|
||||||
|
|
||||||
|
then:
|
||||||
|
1 * tracer.endExceptionally(context, exception)
|
||||||
|
}
|
||||||
|
|
||||||
|
def "ends span once for multiple subscribers"() {
|
||||||
|
|
||||||
|
when:
|
||||||
|
def result = (Mono<?>) underTest.end(tracer, context, Mono.just("Value"))
|
||||||
|
StepVerifier.create(result)
|
||||||
|
.expectNext("Value")
|
||||||
|
.verifyComplete()
|
||||||
|
StepVerifier.create(result)
|
||||||
|
.expectNext("Value")
|
||||||
|
.verifyComplete()
|
||||||
|
StepVerifier.create(result)
|
||||||
|
.expectNext("Value")
|
||||||
|
.verifyComplete()
|
||||||
|
|
||||||
|
then:
|
||||||
|
1 * tracer.end(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class FluxTest extends ReactorAsyncSpanEndStrategyTest {
|
||||||
|
def "is supported"() {
|
||||||
|
expect:
|
||||||
|
underTest.supports(Flux)
|
||||||
|
}
|
||||||
|
|
||||||
|
def "ends span on already completed"() {
|
||||||
|
when:
|
||||||
|
def result = (Flux<?>) underTest.end(tracer, context, Flux.just("Value"))
|
||||||
|
StepVerifier.create(result)
|
||||||
|
.expectNext("Value")
|
||||||
|
.verifyComplete()
|
||||||
|
|
||||||
|
then:
|
||||||
|
1 * tracer.end(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
def "ends span on already empty"() {
|
||||||
|
when:
|
||||||
|
def result = (Flux<?>) underTest.end(tracer, context, Flux.empty())
|
||||||
|
StepVerifier.create(result)
|
||||||
|
.verifyComplete()
|
||||||
|
|
||||||
|
then:
|
||||||
|
1 * tracer.end(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
def "ends span on already errored"() {
|
||||||
|
given:
|
||||||
|
def exception = new IllegalStateException()
|
||||||
|
|
||||||
|
when:
|
||||||
|
def result = (Flux<?>) underTest.end(tracer, context, Flux.error(exception))
|
||||||
|
StepVerifier.create(result)
|
||||||
|
.verifyErrorMatches({ it == exception })
|
||||||
|
|
||||||
|
then:
|
||||||
|
1 * tracer.endExceptionally(context, exception)
|
||||||
|
}
|
||||||
|
|
||||||
|
def "ends span when completed"() {
|
||||||
|
given:
|
||||||
|
def source = UnicastProcessor.<String>create()
|
||||||
|
|
||||||
|
when:
|
||||||
|
def result = (Flux<?>) underTest.end(tracer, context, source)
|
||||||
|
def verifier = StepVerifier.create(result)
|
||||||
|
.expectSubscription()
|
||||||
|
|
||||||
|
then:
|
||||||
|
0 * tracer._
|
||||||
|
|
||||||
|
when:
|
||||||
|
source.onNext("Value")
|
||||||
|
source.onComplete()
|
||||||
|
verifier.expectNext("Value")
|
||||||
|
.verifyComplete()
|
||||||
|
|
||||||
|
then:
|
||||||
|
1 * tracer.end(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
def "ends span when empty"() {
|
||||||
|
given:
|
||||||
|
def source = UnicastProcessor.<String>create()
|
||||||
|
|
||||||
|
when:
|
||||||
|
def result = (Flux<?>) underTest.end(tracer, context, source)
|
||||||
|
def verifier = StepVerifier.create(result)
|
||||||
|
.expectSubscription()
|
||||||
|
|
||||||
|
then:
|
||||||
|
0 * tracer._
|
||||||
|
|
||||||
|
when:
|
||||||
|
source.onComplete()
|
||||||
|
verifier.verifyComplete()
|
||||||
|
|
||||||
|
then:
|
||||||
|
1 * tracer.end(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
def "ends span when errored"() {
|
||||||
|
given:
|
||||||
|
def exception = new IllegalStateException()
|
||||||
|
def source = UnicastProcessor.<String>create()
|
||||||
|
|
||||||
|
when:
|
||||||
|
def result = (Flux<?>) underTest.end(tracer, context, source)
|
||||||
|
def verifier = StepVerifier.create(result)
|
||||||
|
.expectSubscription()
|
||||||
|
|
||||||
|
then:
|
||||||
|
0 * tracer._
|
||||||
|
|
||||||
|
when:
|
||||||
|
source.onError(exception)
|
||||||
|
verifier.verifyErrorMatches({ it == exception })
|
||||||
|
|
||||||
|
then:
|
||||||
|
1 * tracer.endExceptionally(context, exception)
|
||||||
|
}
|
||||||
|
|
||||||
|
def "ends span once for multiple subscribers"() {
|
||||||
|
when:
|
||||||
|
def result = (Flux<?>) underTest.end(tracer, context, Flux.just("Value"))
|
||||||
|
StepVerifier.create(result)
|
||||||
|
.expectNext("Value")
|
||||||
|
.verifyComplete()
|
||||||
|
StepVerifier.create(result)
|
||||||
|
.expectNext("Value")
|
||||||
|
.verifyComplete()
|
||||||
|
StepVerifier.create(result)
|
||||||
|
.expectNext("Value")
|
||||||
|
.verifyComplete()
|
||||||
|
|
||||||
|
then:
|
||||||
|
1 * tracer.end(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue