Auto-configure TracingObservationHandler for HTTP server and clients

- Auto-configures HttpServerTracingObservationHandler and
  HttpClientTracingObservationHandler into Micrometer Tracing. Both
  handlers are ordered before the DefaultTracingObservationHandler,
  which is only used as a fallback.
- The HttpServerHandler and HttpClientHandler implementations are
  auto-configured in the Brave and OpenTelemetry auto-configurations.

Closes gh-30784
pull/30825/head
Moritz Halbritter 3 years ago
parent 061d86e037
commit afdb651b04

@ -22,6 +22,13 @@ import brave.Tracing;
import brave.Tracing.Builder;
import brave.TracingCustomizer;
import brave.handler.SpanHandler;
import brave.http.HttpClientHandler;
import brave.http.HttpClientRequest;
import brave.http.HttpClientResponse;
import brave.http.HttpServerHandler;
import brave.http.HttpServerRequest;
import brave.http.HttpServerResponse;
import brave.http.HttpTracing;
import brave.propagation.B3Propagation;
import brave.propagation.CurrentTraceContext;
import brave.propagation.CurrentTraceContext.ScopeDecorator;
@ -31,6 +38,8 @@ import brave.propagation.ThreadLocalCurrentTraceContext;
import brave.sampler.Sampler;
import io.micrometer.tracing.brave.bridge.BraveBaggageManager;
import io.micrometer.tracing.brave.bridge.BraveCurrentTraceContext;
import io.micrometer.tracing.brave.bridge.BraveHttpClientHandler;
import io.micrometer.tracing.brave.bridge.BraveHttpServerHandler;
import io.micrometer.tracing.brave.bridge.BraveTracer;
import org.springframework.boot.autoconfigure.AutoConfiguration;
@ -107,6 +116,24 @@ public class BraveAutoConfiguration {
return Sampler.create(properties.getSampling().getProbability());
}
@Bean
@ConditionalOnMissingBean
public HttpTracing httpTracing(Tracing tracing) {
return HttpTracing.newBuilder(tracing).build();
}
@Bean
@ConditionalOnMissingBean
public HttpServerHandler<HttpServerRequest, HttpServerResponse> httpServerHandler(HttpTracing httpTracing) {
return HttpServerHandler.create(httpTracing);
}
@Bean
@ConditionalOnMissingBean
public HttpClientHandler<HttpClientRequest, HttpClientResponse> httpClientHandler(HttpTracing httpTracing) {
return HttpClientHandler.create(httpTracing);
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(BraveTracer.class)
static class BraveMicrometer {
@ -124,6 +151,20 @@ public class BraveAutoConfiguration {
return new BraveBaggageManager();
}
@Bean
@ConditionalOnMissingBean
BraveHttpServerHandler braveHttpServerHandler(
HttpServerHandler<HttpServerRequest, HttpServerResponse> httpServerHandler) {
return new BraveHttpServerHandler(httpServerHandler);
}
@Bean
@ConditionalOnMissingBean
BraveHttpClientHandler braveHttpClientHandler(
HttpClientHandler<HttpClientRequest, HttpClientResponse> httpClientHandler) {
return new BraveHttpClientHandler(httpClientHandler);
}
}
}

@ -18,6 +18,10 @@ package org.springframework.boot.actuate.autoconfigure.tracing;
import io.micrometer.tracing.Tracer;
import io.micrometer.tracing.handler.DefaultTracingObservationHandler;
import io.micrometer.tracing.handler.HttpClientTracingObservationHandler;
import io.micrometer.tracing.handler.HttpServerTracingObservationHandler;
import io.micrometer.tracing.http.HttpClientHandler;
import io.micrometer.tracing.http.HttpServerHandler;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@ -25,6 +29,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
/**
* {@link EnableAutoConfiguration Auto-configuration} for the Micrometer Tracing API.
@ -36,11 +42,47 @@ import org.springframework.context.annotation.Bean;
@ConditionalOnClass(Tracer.class)
public class MicrometerTracingAutoConfiguration {
/**
* {@code @Order} value of {@link #defaultTracingObservationHandler(Tracer)}.
*/
public static final int DEFAULT_TRACING_OBSERVATION_HANDLER_ORDER = Ordered.LOWEST_PRECEDENCE - 1000;
/**
* {@code @Order} value of
* {@link #httpServerTracingObservationHandler(Tracer, HttpServerHandler)}.
*/
public static final int HTTP_SERVER_TRACING_OBSERVATION_HANDLER_ORDER = 1000;
/**
* {@code @Order} value of
* {@link #httpClientTracingObservationHandler(Tracer, HttpClientHandler)}.
*/
public static final int HTTP_CLIENT_TRACING_OBSERVATION_HANDLER_ORDER = 2000;
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(Tracer.class)
@Order(DEFAULT_TRACING_OBSERVATION_HANDLER_ORDER)
public DefaultTracingObservationHandler defaultTracingObservationHandler(Tracer tracer) {
return new DefaultTracingObservationHandler(tracer);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean({ Tracer.class, HttpServerHandler.class })
@Order(HTTP_SERVER_TRACING_OBSERVATION_HANDLER_ORDER)
public HttpServerTracingObservationHandler httpServerTracingObservationHandler(Tracer tracer,
HttpServerHandler httpServerHandler) {
return new HttpServerTracingObservationHandler(tracer, httpServerHandler);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean({ Tracer.class, HttpClientHandler.class })
@Order(HTTP_CLIENT_TRACING_OBSERVATION_HANDLER_ORDER)
public HttpClientTracingObservationHandler httpClientTracingObservationHandler(Tracer tracer,
HttpClientHandler httpClientHandler) {
return new HttpClientTracingObservationHandler(tracer, httpClientHandler);
}
}

@ -17,10 +17,16 @@
package org.springframework.boot.actuate.autoconfigure.tracing;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import io.micrometer.tracing.SamplerFunction;
import io.micrometer.tracing.otel.bridge.DefaultHttpClientAttributesGetter;
import io.micrometer.tracing.otel.bridge.DefaultHttpServerAttributesExtractor;
import io.micrometer.tracing.otel.bridge.OtelBaggageManager;
import io.micrometer.tracing.otel.bridge.OtelCurrentTraceContext;
import io.micrometer.tracing.otel.bridge.OtelHttpClientHandler;
import io.micrometer.tracing.otel.bridge.OtelHttpServerHandler;
import io.micrometer.tracing.otel.bridge.OtelTracer;
import io.micrometer.tracing.otel.bridge.OtelTracer.EventPublisher;
import io.opentelemetry.api.OpenTelemetry;
@ -146,6 +152,22 @@ class OpenTelemetryConfigurations {
return new OtelCurrentTraceContext();
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(OpenTelemetry.class)
OtelHttpClientHandler otelHttpClientHandler(OpenTelemetry openTelemetry) {
return new OtelHttpClientHandler(openTelemetry, null, null, SamplerFunction.deferDecision(),
new DefaultHttpClientAttributesGetter());
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(OpenTelemetry.class)
OtelHttpServerHandler otelHttpServerHandler(OpenTelemetry openTelemetry) {
return new OtelHttpServerHandler(openTelemetry, null, null, Pattern.compile(""),
new DefaultHttpServerAttributesExtractor());
}
}
}

@ -18,12 +18,22 @@ package org.springframework.boot.actuate.autoconfigure.tracing;
import brave.Tracer;
import brave.Tracing;
import brave.http.HttpClientHandler;
import brave.http.HttpClientRequest;
import brave.http.HttpClientResponse;
import brave.http.HttpServerHandler;
import brave.http.HttpServerRequest;
import brave.http.HttpServerResponse;
import brave.http.HttpTracing;
import brave.propagation.CurrentTraceContext;
import brave.propagation.Propagation.Factory;
import brave.sampler.Sampler;
import io.micrometer.tracing.brave.bridge.BraveBaggageManager;
import io.micrometer.tracing.brave.bridge.BraveHttpClientHandler;
import io.micrometer.tracing.brave.bridge.BraveHttpServerHandler;
import io.micrometer.tracing.brave.bridge.BraveTracer;
import org.junit.jupiter.api.Test;
import org.mockito.Answers;
import org.mockito.Mockito;
import org.springframework.boot.autoconfigure.AutoConfigurations;
@ -52,6 +62,9 @@ class BraveAutoConfigurationTests {
assertThat(context).hasSingleBean(CurrentTraceContext.class);
assertThat(context).hasSingleBean(Factory.class);
assertThat(context).hasSingleBean(Sampler.class);
assertThat(context).hasSingleBean(HttpTracing.class);
assertThat(context).hasSingleBean(HttpServerHandler.class);
assertThat(context).hasSingleBean(HttpClientHandler.class);
});
}
@ -68,6 +81,12 @@ class BraveAutoConfigurationTests {
assertThat(context).hasSingleBean(Factory.class);
assertThat(context).hasBean("customSampler");
assertThat(context).hasSingleBean(Sampler.class);
assertThat(context).hasBean("customHttpTracing");
assertThat(context).hasSingleBean(HttpTracing.class);
assertThat(context).hasBean("customHttpServerHandler");
assertThat(context).hasSingleBean(HttpServerHandler.class);
assertThat(context).hasBean("customHttpClientHandler");
assertThat(context).hasSingleBean(HttpClientHandler.class);
});
}
@ -76,6 +95,8 @@ class BraveAutoConfigurationTests {
this.contextRunner.run((context) -> {
assertThat(context).hasSingleBean(BraveTracer.class);
assertThat(context).hasSingleBean(BraveBaggageManager.class);
assertThat(context).hasSingleBean(BraveHttpServerHandler.class);
assertThat(context).hasSingleBean(BraveHttpClientHandler.class);
});
}
@ -86,6 +107,10 @@ class BraveAutoConfigurationTests {
assertThat(context).hasSingleBean(BraveTracer.class);
assertThat(context).hasBean("customBraveBaggageManager");
assertThat(context).hasSingleBean(BraveBaggageManager.class);
assertThat(context).hasBean("customBraveHttpServerHandler");
assertThat(context).hasSingleBean(BraveHttpServerHandler.class);
assertThat(context).hasBean("customBraveHttpClientHandler");
assertThat(context).hasSingleBean(BraveHttpClientHandler.class);
});
}
@ -97,6 +122,9 @@ class BraveAutoConfigurationTests {
assertThat(context).doesNotHaveBean(CurrentTraceContext.class);
assertThat(context).doesNotHaveBean(Factory.class);
assertThat(context).doesNotHaveBean(Sampler.class);
assertThat(context).doesNotHaveBean(HttpTracing.class);
assertThat(context).doesNotHaveBean(HttpServerHandler.class);
assertThat(context).doesNotHaveBean(HttpClientHandler.class);
});
}
@ -105,6 +133,8 @@ class BraveAutoConfigurationTests {
this.contextRunner.withClassLoader(new FilteredClassLoader("io.micrometer")).run((context) -> {
assertThat(context).doesNotHaveBean(BraveTracer.class);
assertThat(context).doesNotHaveBean(BraveBaggageManager.class);
assertThat(context).doesNotHaveBean(BraveHttpServerHandler.class);
assertThat(context).doesNotHaveBean(BraveHttpClientHandler.class);
});
}
@ -136,6 +166,23 @@ class BraveAutoConfigurationTests {
return Mockito.mock(Sampler.class);
}
@Bean
HttpTracing customHttpTracing() {
return Mockito.mock(HttpTracing.class);
}
@Bean
HttpServerHandler<HttpServerRequest, HttpServerResponse> customHttpServerHandler() {
HttpTracing httpTracing = Mockito.mock(HttpTracing.class, Answers.RETURNS_MOCKS);
return HttpServerHandler.create(httpTracing);
}
@Bean
HttpClientHandler<HttpClientRequest, HttpClientResponse> customHttpClientHandler() {
HttpTracing httpTracing = Mockito.mock(HttpTracing.class, Answers.RETURNS_MOCKS);
return HttpClientHandler.create(httpTracing);
}
}
@Configuration(proxyBeanMethods = false)
@ -151,6 +198,16 @@ class BraveAutoConfigurationTests {
return Mockito.mock(BraveBaggageManager.class);
}
@Bean
BraveHttpServerHandler customBraveHttpServerHandler() {
return Mockito.mock(BraveHttpServerHandler.class);
}
@Bean
BraveHttpClientHandler customBraveHttpClientHandler() {
return Mockito.mock(BraveHttpClientHandler.class);
}
}
}

@ -16,8 +16,15 @@
package org.springframework.boot.actuate.autoconfigure.tracing;
import java.util.List;
import io.micrometer.tracing.Tracer;
import io.micrometer.tracing.handler.DefaultTracingObservationHandler;
import io.micrometer.tracing.handler.HttpClientTracingObservationHandler;
import io.micrometer.tracing.handler.HttpServerTracingObservationHandler;
import io.micrometer.tracing.handler.TracingObservationHandler;
import io.micrometer.tracing.http.HttpClientHandler;
import io.micrometer.tracing.http.HttpServerHandler;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
@ -41,8 +48,28 @@ class MicrometerTracingAutoConfigurationTests {
@Test
void shouldSupplyBeans() {
this.contextRunner.withUserConfiguration(TracerConfiguration.class)
.run((context) -> assertThat(context).hasSingleBean(DefaultTracingObservationHandler.class));
this.contextRunner.withUserConfiguration(TracerConfiguration.class, HttpClientHandlerConfiguration.class,
HttpServerHandlerConfiguration.class).run((context) -> {
assertThat(context).hasSingleBean(DefaultTracingObservationHandler.class);
assertThat(context).hasSingleBean(HttpServerTracingObservationHandler.class);
assertThat(context).hasSingleBean(HttpClientTracingObservationHandler.class);
});
}
@Test
@SuppressWarnings("rawtypes")
void shouldSupplyBeansInCorrectOrder() {
this.contextRunner.withUserConfiguration(TracerConfiguration.class, HttpClientHandlerConfiguration.class,
HttpServerHandlerConfiguration.class).run((context) -> {
List<TracingObservationHandler> tracingObservationHandlers = context
.getBeanProvider(TracingObservationHandler.class).orderedStream().toList();
assertThat(tracingObservationHandlers).hasSize(3);
assertThat(tracingObservationHandlers.get(0))
.isInstanceOf(HttpServerTracingObservationHandler.class);
assertThat(tracingObservationHandlers.get(1))
.isInstanceOf(HttpClientTracingObservationHandler.class);
assertThat(tracingObservationHandlers.get(2)).isInstanceOf(DefaultTracingObservationHandler.class);
});
}
@Test
@ -50,19 +77,43 @@ class MicrometerTracingAutoConfigurationTests {
this.contextRunner.withUserConfiguration(CustomConfiguration.class).run((context) -> {
assertThat(context).hasBean("customDefaultTracingObservationHandler");
assertThat(context).hasSingleBean(DefaultTracingObservationHandler.class);
assertThat(context).hasBean("customHttpServerTracingObservationHandler");
assertThat(context).hasSingleBean(HttpServerTracingObservationHandler.class);
assertThat(context).hasBean("customHttpClientTracingObservationHandler");
assertThat(context).hasSingleBean(HttpClientTracingObservationHandler.class);
});
}
@Test
void shouldNotSupplyBeansIfMicrometerIsMissing() {
this.contextRunner.withClassLoader(new FilteredClassLoader("io.micrometer"))
.run((context) -> assertThat(context).doesNotHaveBean(DefaultTracingObservationHandler.class));
this.contextRunner.withClassLoader(new FilteredClassLoader("io.micrometer")).run((context) -> {
assertThat(context).doesNotHaveBean(DefaultTracingObservationHandler.class);
assertThat(context).doesNotHaveBean(HttpServerTracingObservationHandler.class);
assertThat(context).doesNotHaveBean(HttpClientTracingObservationHandler.class);
});
}
@Test
void shouldNotSupplyDefaultTracingObservationHandlerIfTracerIsMissing() {
void shouldNotSupplyBeansIfTracerIsMissing() {
this.contextRunner
.run((context) -> assertThat(context).doesNotHaveBean(DefaultTracingObservationHandler.class));
.withUserConfiguration(HttpServerHandlerConfiguration.class, HttpClientHandlerConfiguration.class)
.run((context) -> {
assertThat(context).doesNotHaveBean(DefaultTracingObservationHandler.class);
assertThat(context).doesNotHaveBean(HttpServerTracingObservationHandler.class);
assertThat(context).doesNotHaveBean(HttpClientTracingObservationHandler.class);
});
}
@Test
void shouldNotSupplyBeansIfHttpClientHandlerIsMissing() {
this.contextRunner.withUserConfiguration(TracerConfiguration.class, HttpServerHandlerConfiguration.class)
.run((context) -> assertThat(context).doesNotHaveBean(HttpClientTracingObservationHandler.class));
}
@Test
void shouldNotSupplyBeansIfHttpServerHandlerIsMissing() {
this.contextRunner.withUserConfiguration(TracerConfiguration.class, HttpClientHandlerConfiguration.class)
.run((context) -> assertThat(context).doesNotHaveBean(HttpServerTracingObservationHandler.class));
}
@Configuration(proxyBeanMethods = false)
@ -75,6 +126,26 @@ class MicrometerTracingAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
private static class HttpClientHandlerConfiguration {
@Bean
HttpClientHandler httpClientHandler() {
return Mockito.mock(HttpClientHandler.class);
}
}
@Configuration(proxyBeanMethods = false)
private static class HttpServerHandlerConfiguration {
@Bean
HttpServerHandler httpServerHandler() {
return Mockito.mock(HttpServerHandler.class);
}
}
@Configuration(proxyBeanMethods = false)
private static class CustomConfiguration {
@ -83,6 +154,16 @@ class MicrometerTracingAutoConfigurationTests {
return Mockito.mock(DefaultTracingObservationHandler.class);
}
@Bean
HttpServerTracingObservationHandler customHttpServerTracingObservationHandler() {
return Mockito.mock(HttpServerTracingObservationHandler.class);
}
@Bean
HttpClientTracingObservationHandler customHttpClientTracingObservationHandler() {
return Mockito.mock(HttpClientTracingObservationHandler.class);
}
}
}

@ -17,10 +17,14 @@
package org.springframework.boot.actuate.autoconfigure.tracing;
import io.micrometer.tracing.otel.bridge.OtelCurrentTraceContext;
import io.micrometer.tracing.otel.bridge.OtelHttpClientHandler;
import io.micrometer.tracing.otel.bridge.OtelHttpServerHandler;
import io.micrometer.tracing.otel.bridge.OtelTracer;
import io.micrometer.tracing.otel.bridge.OtelTracer.EventPublisher;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Tracer;
import org.junit.jupiter.api.Test;
import org.mockito.Answers;
import org.mockito.Mockito;
import org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryConfigurations.MicrometerConfiguration;
@ -44,28 +48,41 @@ class OpenTelemetryConfigurationsMicrometerConfigurationTests {
@Test
void shouldSupplyBeans() {
this.contextRunner.withUserConfiguration(TracerConfiguration.class).run((context) -> {
this.contextRunner.withUserConfiguration(TracerConfiguration.class, OpenTelemetryConfiguration.class)
.run((context) -> {
assertThat(context).hasSingleBean(OtelTracer.class);
assertThat(context).hasSingleBean(EventPublisher.class);
assertThat(context).hasSingleBean(OtelCurrentTraceContext.class);
assertThat(context).hasSingleBean(OtelHttpClientHandler.class);
assertThat(context).hasSingleBean(OtelHttpServerHandler.class);
});
}
@Test
void shouldNotSupplyBeansIfMicrometerTracingBridgeOtelIsMissing() {
this.contextRunner.withClassLoader(new FilteredClassLoader("io.micrometer.tracing.otel"))
.withUserConfiguration(TracerConfiguration.class).run((context) -> {
.withUserConfiguration(TracerConfiguration.class, OpenTelemetryConfiguration.class).run((context) -> {
assertThat(context).doesNotHaveBean(OtelTracer.class);
assertThat(context).doesNotHaveBean(EventPublisher.class);
assertThat(context).doesNotHaveBean(OtelCurrentTraceContext.class);
assertThat(context).doesNotHaveBean(OtelHttpClientHandler.class);
assertThat(context).doesNotHaveBean(OtelHttpServerHandler.class);
});
}
@Test
void shouldNotSupplyOtelTracerIfTracerIsMissing() {
void shouldNotSupplyBeansIfTracerIsMissing() {
this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(OtelTracer.class));
}
@Test
void shouldNotSupplyBeansIfOpenTelemetryIsMissing() {
this.contextRunner.withUserConfiguration(TracerConfiguration.class).run((context) -> {
assertThat(context).doesNotHaveBean(OtelHttpClientHandler.class);
assertThat(context).doesNotHaveBean(OtelHttpServerHandler.class);
});
}
@Test
void shouldBackOffOnCustomBeans() {
this.contextRunner.withUserConfiguration(CustomConfiguration.class).run((context) -> {
@ -75,6 +92,10 @@ class OpenTelemetryConfigurationsMicrometerConfigurationTests {
assertThat(context).hasSingleBean(EventPublisher.class);
assertThat(context).hasBean("customOtelCurrentTraceContext");
assertThat(context).hasSingleBean(OtelCurrentTraceContext.class);
assertThat(context).hasBean("customOtelHttpClientHandler");
assertThat(context).hasSingleBean(OtelHttpClientHandler.class);
assertThat(context).hasBean("customOtelHttpServerHandler");
assertThat(context).hasSingleBean(OtelHttpServerHandler.class);
});
}
@ -96,6 +117,16 @@ class OpenTelemetryConfigurationsMicrometerConfigurationTests {
return Mockito.mock(OtelCurrentTraceContext.class);
}
@Bean
OtelHttpClientHandler customOtelHttpClientHandler() {
return Mockito.mock(OtelHttpClientHandler.class);
}
@Bean
OtelHttpServerHandler customOtelHttpServerHandler() {
return Mockito.mock(OtelHttpServerHandler.class);
}
}
@Configuration(proxyBeanMethods = false)
@ -108,4 +139,14 @@ class OpenTelemetryConfigurationsMicrometerConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
private static class OpenTelemetryConfiguration {
@Bean
OpenTelemetry openTelemetry() {
return Mockito.mock(OpenTelemetry.class, Answers.RETURNS_MOCKS);
}
}
}

Loading…
Cancel
Save