Propagate and Observation from Reactive context (#3999)

To propagate an `Observation` from reactive stream (e.g. WebFlux)
we have to capture its context and set it into the current thread scope.

* Add a `io.micrometer:context-propagation` dependency to support reactive context propagation
* Populate a `parentObservation` in the `IntegrationObservation.PRODUCER.observation()`
since it is not available for tracing on `Observation.onStart()`.
Might be tentative until upcoming fix in Micrometer Observation
* Populate from reactive context in the `WebFluxInboundEndpoint` where we use just `send()` operation downstream
* Populate from reactive context in the `MessagingGatewaySupport` where we use `send()` operation downstream or `FluxMessageChannel.subscribeTo()`
* Use `contextCapture()` in the `FluxMessageChannel` to gather a `ThreadLocal` info into a Reactor context
and then set back to `ThreadLocal` in the `transformDeferredContextual()` which really happens on a different thread
* Verify a trace propagation from WebFlux to an integration flow via Brave instrumentation in the `WebFluxObservationPropagationTests`
This commit is contained in:
Artem Bilan
2023-02-01 14:13:40 -05:00
committed by GitHub
parent 841289002c
commit d9d7c49aac
6 changed files with 264 additions and 27 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -335,6 +335,7 @@ public abstract class AbstractMessageChannel extends IntegrationObjectSupport
DefaultMessageSenderObservationConvention.INSTANCE,
() -> new MessageSenderContext(messageToSend, getComponentName()),
this.observationRegistry)
.parentObservation(this.observationRegistry.getCurrentObservation()) // TODO until the fix in micrometer-observation
.observe(() -> sendInternal(messageToSend, timeout));
}

View File

@@ -20,6 +20,7 @@ import java.time.Duration;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
import io.micrometer.context.ContextSnapshot;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import reactor.core.Disposable;
@@ -111,17 +112,20 @@ public class FluxMessageChannel extends AbstractMessageChannel
Flux.from(publisher)
.delaySubscription(this.subscribedSignal.asFlux().filter(Boolean::booleanValue).next())
.publishOn(this.scheduler)
.doOnNext((message) -> {
try {
if (!send(message)) {
throw new MessageDeliveryException(message,
"Failed to send message to channel '" + this);
}
}
catch (Exception ex) {
logger.warn(ex, () -> "Error during processing event: " + message);
}
})
.transformDeferredContextual((flux, contextView) ->
flux.doOnNext((message) -> {
var scope = ContextSnapshot.setAllThreadLocalsFrom(contextView);
try (scope) {
if (!send(message)) {
throw new MessageDeliveryException(message,
"Failed to send message to channel '" + this);
}
}
catch (Exception ex) {
logger.warn(ex, () -> "Error during processing event: " + message);
}
}))
.contextCapture()
.subscribe());
}

View File

@@ -20,6 +20,7 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import io.micrometer.context.ContextSnapshot;
import io.micrometer.observation.ObservationRegistry;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
@@ -720,7 +721,7 @@ public abstract class MessagingGatewaySupport extends AbstractEndpoint
throw new MessageMappingException("Cannot map to message: " + object, e);
}
return Mono.defer(() -> {
return Mono.deferContextual(contextView -> {
Object originalReplyChannelHeader = requestMessage.getHeaders().getReplyChannel();
Object originalErrorChannelHeader = requestMessage.getHeaders().getErrorChannel();
@@ -738,7 +739,10 @@ public abstract class MessagingGatewaySupport extends AbstractEndpoint
.setErrorChannel(replyChan)
.build();
sendMessageForReactiveFlow(requestChannel, messageToSend);
var scope = ContextSnapshot.setAllThreadLocalsFrom(contextView);
try (scope) {
sendMessageForReactiveFlow(requestChannel, messageToSend);
}
return buildReplyMono(requestMessage, replyChan.replyMono.asMono(), error,
originalReplyChannelHeader, originalErrorChannelHeader);