GH-3033: Register ObservationRegistry for Dynamic MessageChannels

Fixes: gh-3033

* ensure `ObservationRegistry` is registered on dynamically created `MessageChannel` instances in `StreamBridge`
This commit is contained in:
Agustino Lim
2024-11-08 13:20:36 +08:00
committed by Oleg Zhurakousky
parent f392488dae
commit a88c34fc9f
6 changed files with 45 additions and 4 deletions

View File

@@ -100,7 +100,7 @@ public class ReactorKafkaBinderObservationTests {
streamBridge.send("rkbot-in-topic", MessageBuilder.withPayload("data")
.build());
await().timeout(Duration.ofSeconds(10)).untilAsserted(() -> assertThat(SPANS.spans()).hasSize(3));
await().timeout(Duration.ofSeconds(10)).untilAsserted(() -> assertThat(SPANS.spans()).hasSize(4));
SpansAssert.assertThat(SPANS.spans().stream().map(BraveFinishedSpan::fromBrave).collect(Collectors.toList()))
.haveSameTraceId();
}

View File

@@ -856,6 +856,21 @@ class StreamBridgeTests {
assertThat(new String(message.getPayload())).isEqualTo("JOHN DOE");
}
@Test
void test_3033() {
try (ConfigurableApplicationContext context = new SpringApplicationBuilder(
TestChannelBinderConfiguration.getCompleteConfiguration(
EmptyConfiguration.class)).web(WebApplicationType.NONE).run(
"--spring.cloud.stream.source=outputA",
"--spring.jmx.enabled=false")) {
StreamBridge streamBridge = context.getBean(StreamBridge.class);
streamBridge.send("outputA", MessageBuilder.withPayload("A").build());
OutputDestination output = context.getBean(OutputDestination.class);
assertThat(output.receive(1000, "outputA").getHeaders().containsKey("traceparent")).isTrue();
}
}
@EnableAutoConfiguration
public static class DynamicProducerDestinationConfig {
@Bean

View File

@@ -24,6 +24,14 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -20,6 +20,8 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.cloud.stream.binder.Binder;
@@ -107,4 +109,8 @@ public class TestChannelBinderConfiguration<T> {
return new TestChannelBinderProvisioner();
}
@Bean
public ObservationRegistry observationRegistry() {
return ObservationRegistry.create();
}
}

View File

@@ -36,6 +36,7 @@ import java.util.function.Supplier;
import java.util.stream.StreamSupport;
import io.micrometer.context.ContextSnapshotFactory;
import io.micrometer.observation.ObservationRegistry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.reactivestreams.Publisher;
@@ -46,6 +47,7 @@ import reactor.util.function.Tuples;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.autoconfigure.AutoConfiguration;
@@ -151,8 +153,10 @@ public class FunctionConfiguration {
@Bean
public StreamBridge streamBridgeUtils(FunctionCatalog functionCatalog,
BindingServiceProperties bindingServiceProperties, ConfigurableApplicationContext applicationContext,
@Nullable NewDestinationBindingCallback callback) {
return new StreamBridge(functionCatalog, bindingServiceProperties, applicationContext, callback);
@Nullable NewDestinationBindingCallback callback,
ObjectProvider<ObservationRegistry> observationRegistries) {
return new StreamBridge(functionCatalog, bindingServiceProperties, applicationContext, callback,
observationRegistries);
}
@Bean

View File

@@ -29,10 +29,12 @@ import java.util.function.Function;
import io.micrometer.context.ContextExecutorService;
import io.micrometer.context.ContextSnapshotFactory;
import io.micrometer.observation.ObservationRegistry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.cloud.function.context.FunctionCatalog;
import org.springframework.cloud.function.context.FunctionRegistration;
@@ -126,6 +128,7 @@ public final class StreamBridge implements StreamOperations, SmartInitializingSi
private static final ReentrantLock lock = new ReentrantLock();
private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
/**
*
* @param functionCatalog instance of {@link FunctionCatalog}
@@ -133,7 +136,7 @@ public final class StreamBridge implements StreamOperations, SmartInitializingSi
* @param applicationContext instance of {@link ConfigurableApplicationContext}
*/
StreamBridge(FunctionCatalog functionCatalog, BindingServiceProperties bindingServiceProperties,
ConfigurableApplicationContext applicationContext, @Nullable NewDestinationBindingCallback destinationBindingCallback) {
ConfigurableApplicationContext applicationContext, @Nullable NewDestinationBindingCallback destinationBindingCallback, ObjectProvider<ObservationRegistry> observationRegistries) {
this.executorService = Executors.newCachedThreadPool();
Assert.notNull(functionCatalog, "'functionCatalog' must not be null");
Assert.notNull(applicationContext, "'applicationContext' must not be null");
@@ -158,6 +161,7 @@ public final class StreamBridge implements StreamOperations, SmartInitializingSi
};
this.functionInvocationHelper = applicationContext.getBean(FunctionInvocationHelper.class);
this.streamBridgeFunctionCache = new HashMap<>();
observationRegistries.ifAvailable(registry -> this.observationRegistry = registry);
}
@Override
@@ -288,8 +292,12 @@ public final class StreamBridge implements StreamOperations, SmartInitializingSi
messageChannel = this.isAsync() ? new ExecutorChannel(this.executorService) : new DirectWithAttributesChannel();
((AbstractSubscribableChannel) messageChannel).setApplicationContext(applicationContext);
((AbstractSubscribableChannel) messageChannel).setComponentName(destinationName);
//<<<<<<< HEAD
BinderWrapper binderWrapper = bindingService.createBinderWrapper(binderName, destinationName, messageChannel.getClass());
//=======
((AbstractSubscribableChannel) messageChannel).registerObservationRegistry(observationRegistry);
//>>>>>>> a1418283c (GH-3033: Register ObservationRegistry for Dynamic MessageChannels)
if (this.destinationBindingCallback != null) {
Object extendedProducerProperties = this.bindingService
.getExtendedProducerProperties(binderWrapper.binder(), destinationName);