diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle b/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle index 4b4ddd521383..7a61cc30af9d 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle @@ -70,6 +70,7 @@ dependencies { optional("io.micrometer:micrometer-registry-wavefront") optional("io.zipkin.reporter2:zipkin-sender-urlconnection") optional("io.opentelemetry:opentelemetry-exporter-zipkin") + optional("io.opentelemetry:opentelemetry-exporter-otlp") optional("io.projectreactor.netty:reactor-netty-http") optional("io.r2dbc:r2dbc-pool") optional("io.r2dbc:r2dbc-spi") diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfiguration.java new file mode 100644 index 000000000000..2425d34c45a1 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfiguration.java @@ -0,0 +1,88 @@ +/* + * Copyright 2012-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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing.otlp; + +import io.micrometer.tracing.otel.bridge.OtelTracer; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; +import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; +import io.opentelemetry.sdk.trace.SdkTracerProvider; + +import org.springframework.boot.actuate.autoconfigure.tracing.ConditionalOnEnabledTracing; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for OTLP. Brave does not support + * OTLP, so we only configure it for OpenTelemetry. OTLP defines three transports that are + * supported: gRPC (/protobuf), HTTP/protobuf, HTTP/JSON. From these transports HTTP/JSON + * is not supported by the OTel Java SDK, and it seems there are no plans supporting it in + * the future, see: opentelemetry-java#3651. + * Because this class configures components from the OTel SDK, it can only support what + * the OTel SDK does: gRPC and HTTP/protobuf. + * + * @author Jonatan Ivanov + * @since 3.1.0 + */ +@AutoConfiguration +@ConditionalOnEnabledTracing +@ConditionalOnClass({ OtelTracer.class, SdkTracerProvider.class, OpenTelemetry.class }) +@EnableConfigurationProperties(OtlpProperties.class) +public class OtlpAutoConfiguration { + + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(OtlpHttpSpanExporter.class) + @ConditionalOnProperty(prefix = "management.otlp.tracing.http", name = "enabled", matchIfMissing = true) + static class HttpConfiguration { + + @Bean + @ConditionalOnMissingBean + OtlpHttpSpanExporter otlpHttpSpanExporter(OtlpProperties properties) { + return OtlpHttpSpanExporter.builder() + .setEndpoint(properties.getHttp().getEndpoint()) + .setTimeout(properties.getHttp().getTimeout()) + .setCompression(properties.getHttp().getCompression().name().toLowerCase()) + .build(); + } + + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(OtlpGrpcSpanExporter.class) + @ConditionalOnProperty(prefix = "management.otlp.tracing.grpc", name = "enabled") + static class GrpcConfiguration { + + @Bean + @ConditionalOnMissingBean + OtlpGrpcSpanExporter otlpGrpcSpanExporter(OtlpProperties properties) { + return OtlpGrpcSpanExporter.builder() + .setEndpoint(properties.getGrpc().getEndpoint()) + .setTimeout(properties.getGrpc().getTimeout()) + .setCompression(properties.getGrpc().getCompression().name().toLowerCase()) + .build(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpProperties.java new file mode 100644 index 000000000000..539a2b102cda --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpProperties.java @@ -0,0 +1,182 @@ +/* + * Copyright 2012-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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing.otlp; + +import java.time.Duration; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Configuration properties for {@link OtlpAutoConfiguration}. + * + * @author Jonatan Ivanov + * @since 3.1.0 + */ +@ConfigurationProperties("management.otlp.tracing") +public class OtlpProperties { + + /** + * HTTP configuration. + */ + private final Http http = new Http(); + + /** + * gRPC configuration. + */ + private final Grpc grpc = new Grpc(); + + public Http getHttp() { + return this.http; + } + + public Grpc getGrpc() { + return this.grpc; + } + + public static class Http { + + /** + * Whether auto-configuration of HTTP exporter is enabled. + */ + private boolean enabled = true; + + /** + * URL to the OTel collector's HTTP API. + */ + private String endpoint = "http://localhost:4318/v1/traces"; + + /** + * Call timeout for the OTel Collector to process an exported batch of data. This + * timeout spans the entire call: resolving DNS, connecting, writing the request + * body, server processing, and reading the response body. If the call requires + * redirects or retries all must complete within one timeout period. + */ + private Duration timeout = Duration.ofSeconds(10); + + /** + * The method used to compress the payload. + */ + private Compression compression = Compression.NONE; + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public String getEndpoint() { + return this.endpoint; + } + + public void setEndpoint(String endpoint) { + this.endpoint = endpoint; + } + + public Duration getTimeout() { + return this.timeout; + } + + public void setTimeout(Duration timeout) { + this.timeout = timeout; + } + + public Compression getCompression() { + return this.compression; + } + + public void setCompression(Compression compression) { + this.compression = compression; + } + + } + + public static class Grpc { + + /** + * Whether auto-configuration of gRPC exporter is enabled. + */ + private boolean enabled = false; + + /** + * URL to the OTel collector's gRPC API. + */ + private String endpoint = "http://localhost:4317"; + + /** + * Call timeout for the OTel Collector to process an exported batch of data. This + * timeout spans the entire call: resolving DNS, connecting, writing the request + * body, server processing, and reading the response body. If the call requires + * redirects or retries all must complete within one timeout period. + */ + private Duration timeout = Duration.ofSeconds(10); + + /** + * The method used to compress the payload. + */ + private Compression compression = Compression.NONE; + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public String getEndpoint() { + return this.endpoint; + } + + public void setEndpoint(String endpoint) { + this.endpoint = endpoint; + } + + public Duration getTimeout() { + return this.timeout; + } + + public void setTimeout(Duration timeout) { + this.timeout = timeout; + } + + public Compression getCompression() { + return this.compression; + } + + public void setCompression(Compression compression) { + this.compression = compression; + } + + } + + enum Compression { + + /** + * Gzip compression. + */ + GZIP, + + /** + * No compression. + */ + NONE + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/package-info.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/package-info.java new file mode 100644 index 000000000000..b021e3da9fca --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Auto-configuration for tracing with OTLP. + */ +package org.springframework.boot.actuate.autoconfigure.tracing.otlp; diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index c4f5e4c08f60..7306d1f31d1f 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -100,6 +100,7 @@ org.springframework.boot.actuate.autoconfigure.system.DiskSpaceHealthContributor org.springframework.boot.actuate.autoconfigure.tracing.BraveAutoConfiguration org.springframework.boot.actuate.autoconfigure.tracing.MicrometerTracingAutoConfiguration org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryAutoConfiguration +org.springframework.boot.actuate.autoconfigure.tracing.otlp.OtlpAutoConfiguration org.springframework.boot.actuate.autoconfigure.tracing.prometheus.PrometheusExemplarsAutoConfiguration org.springframework.boot.actuate.autoconfigure.tracing.wavefront.WavefrontTracingAutoConfiguration org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinAutoConfiguration diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfigurationTests.java new file mode 100644 index 000000000000..22dc7eebc57b --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/otlp/OtlpAutoConfigurationTests.java @@ -0,0 +1,136 @@ +/* + * Copyright 2012-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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing.otlp; + +import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; +import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; +import io.opentelemetry.sdk.trace.export.SpanExporter; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link OtlpAutoConfiguration}. + * + * @author Jonatan Ivanov + */ +class OtlpAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(OtlpAutoConfiguration.class)); + + @Test + void shouldSupplyBeans() { + this.contextRunner.run((context) -> assertThat(context).hasSingleBean(OtlpHttpSpanExporter.class) + .hasSingleBean(SpanExporter.class)); + } + + @Test + void shouldNotSupplyBeansIfTracingIsDisabled() { + this.contextRunner.withPropertyValues("management.tracing.enabled=false") + .run((context) -> assertThat(context).doesNotHaveBean(SpanExporter.class)); + } + + @Test + void shouldNotSupplyBeansIfTracingBridgeIsMissing() { + this.contextRunner.withClassLoader(new FilteredClassLoader("io.micrometer.tracing")) + .run((context) -> assertThat(context).doesNotHaveBean(SpanExporter.class)); + } + + @Test + void shouldNotSupplyBeansIfOtelSdkIsMissing() { + this.contextRunner.withClassLoader(new FilteredClassLoader("io.opentelemetry.sdk")) + .run((context) -> assertThat(context).doesNotHaveBean(SpanExporter.class)); + } + + @Test + void shouldNotSupplyBeansIfOtelApiIsMissing() { + this.contextRunner.withClassLoader(new FilteredClassLoader("io.opentelemetry.api")) + .run((context) -> assertThat(context).doesNotHaveBean(SpanExporter.class)); + } + + @Test + void shouldNotSupplyBeansIfExporterIsMissing() { + this.contextRunner.withClassLoader(new FilteredClassLoader("io.opentelemetry.exporter")) + .run((context) -> assertThat(context).doesNotHaveBean(SpanExporter.class)); + } + + @Test + void shouldNotSupplyBeansIfHttpExporterIsDisabled() { + this.contextRunner.withPropertyValues("management.otlp.tracing.http.enabled=false") + .run((context) -> assertThat(context).doesNotHaveBean(SpanExporter.class)); + } + + @Test + void shouldSupplyGrpcAndHttpExportersIfBothEnabled() { + this.contextRunner.withPropertyValues("management.otlp.tracing.grpc.enabled=true") + .run((context) -> assertThat(context).hasSingleBean(OtlpHttpSpanExporter.class) + .hasSingleBean(OtlpGrpcSpanExporter.class)); + } + + @Test + void shouldSupplyOnlyGrpcExporterIfEnabledButHttpDisabled() { + this.contextRunner + .withPropertyValues("management.otlp.tracing.grpc.enabled=true", + "management.otlp.tracing.http.enabled=false") + .run((context) -> assertThat(context).hasSingleBean(OtlpGrpcSpanExporter.class) + .hasSingleBean(SpanExporter.class)); + } + + @Test + void shouldBackOffOnCustomHttpExporter() { + this.contextRunner.withUserConfiguration(CustomHttpExporterConfiguration.class) + .run((context) -> assertThat(context).hasSingleBean(OtlpHttpSpanExporter.class) + .hasSingleBean(SpanExporter.class)); + } + + @Test + void shouldBackOffOnCustomGrpcExporter() { + this.contextRunner.withUserConfiguration(CustomGrpcExporterConfiguration.class) + .withPropertyValues("management.otlp.tracing.grpc.enabled=true", + "management.otlp.tracing.http.enabled=false") + .run((context) -> assertThat(context).hasSingleBean(OtlpGrpcSpanExporter.class) + .hasSingleBean(SpanExporter.class)); + } + + @Configuration(proxyBeanMethods = false) + private static class CustomHttpExporterConfiguration { + + @Bean + OtlpHttpSpanExporter customOtlpHttpSpanExporter() { + return OtlpHttpSpanExporter.builder().build(); + } + + } + + @Configuration(proxyBeanMethods = false) + private static class CustomGrpcExporterConfiguration { + + @Bean + OtlpGrpcSpanExporter customOtlpGrpcSpanExporter() { + return OtlpGrpcSpanExporter.builder().build(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/tracing.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/tracing.adoc index 8650a9b93d0f..ad5b9e72edcd 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/tracing.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/tracing.adoc @@ -10,7 +10,7 @@ TIP: To learn more about Micrometer Tracing capabilities, see its https://microm === Supported Tracers Spring Boot ships auto-configuration for the following tracers: -* https://opentelemetry.io/[OpenTelemetry] with https://zipkin.io/[Zipkin] or https://docs.wavefront.com/[Wavefront] +* https://opentelemetry.io/[OpenTelemetry] with https://zipkin.io/[Zipkin], https://docs.wavefront.com/[Wavefront], or https://opentelemetry.io/docs/reference/specification/protocol/[OTLP] * https://github.com/openzipkin/brave[OpenZipkin Brave] with https://zipkin.io/[Zipkin] or https://docs.wavefront.com/[Wavefront] @@ -90,6 +90,14 @@ All tracer implementations need the `org.springframework.boot:spring-boot-starte +[[actuator.micrometer-tracing.tracer-implementations.otel-otlp]] +==== OpenTelemetry With OTLP + +* `io.micrometer:micrometer-tracing-bridge-otel` - which is needed to bridge the Micrometer Observation API to OpenTelemetry. +* `io.opentelemetry:opentelemetry-exporter-otlp` - which is needed to report traces to a collector that can accept OTLP. + + + [[actuator.micrometer-tracing.tracer-implementations.brave-zipkin]] ==== OpenZipkin Brave With Zipkin