From acd7c8c2778e109928e9643af0e3ca59bb339e87 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Tue, 12 Aug 2025 13:38:50 +0200 Subject: [PATCH 1/2] Duplicate Spring Boot 2/3 starter and autoconfigure modules for Spring Boot 4 --- .fossa.yml | 6 + ...elemetry-spring-boot-autoconfigure-4.0.txt | 8 + .../spring-boot-autoconfigure-4.0/README.md | 9 + .../build.gradle.kts | 186 ++++ .../gradle.properties | 1 + .../metadata.yaml | 4 + .../OpenTelemetryAutoConfiguration.java | 197 ++++ .../ConditionalOnEnabledInstrumentation.java | 28 + .../InstrumentationPropertyEnabled.java | 37 + .../internal/OtelMapConverter.java | 34 + .../autoconfigure/internal/SdkEnabled.java | 22 + ...mentationAnnotationsAutoConfiguration.java | 36 + .../InstrumentationWithSpanAspect.java | 32 + .../annotations/JoinPointRequest.java | 84 ++ .../JointPointCodeAttributesExtractor.java | 22 + .../OpenTelemetryAnnotationsRuntimeHints.java | 28 + .../annotations/WithSpanAspect.java | 88 ++ ...spectParameterAttributeNamesExtractor.java | 76 ++ .../jdbc/DataSourcePostProcessor.java | 77 ++ .../JdbcInstrumentationAutoConfiguration.java | 39 + ...ListenerContainerFactoryPostProcessor.java | 59 ++ ...KafkaInstrumentationAutoConfiguration.java | 63 ++ .../LogbackAppenderApplicationListener.java | 76 ++ .../logging/LogbackAppenderInstaller.java | 260 ++++++ ...penTelemetryAppenderAutoConfiguration.java | 53 ++ .../MicrometerBridgeAutoConfiguration.java | 38 + ...lientInstrumentationAutoConfiguration.java | 42 + ...R2dbcInstrumentationAutoConfiguration.java | 37 + .../R2dbcInstrumentingPostProcessor.java | 57 ++ .../Java17RuntimeMetricsProvider.java | 34 + .../Java8RuntimeMetricsProvider.java | 34 + .../RuntimeMetricsAutoConfiguration.java | 65 ++ ...eMetricsBeanRegistrationExcludeFilter.java | 26 + .../RuntimeMetricsProvider.java | 23 + ...SpringSchedulingInstrumentationAspect.java | 92 ++ ...ulingInstrumentationAutoConfiguration.java | 34 + .../web/RestClientBeanPostProcessor.java | 66 ++ ...lientInstrumentationAutoConfiguration.java | 50 + .../web/RestTemplateBeanPostProcessor.java | 38 + .../web/RestTemplateInstrumentation.java | 42 + ...ngWebInstrumentationAutoConfiguration.java | 49 + ...bfluxInstrumentationAutoConfiguration.java | 46 + .../webflux/WebClientBeanPostProcessor.java | 69 ++ ...bMvc7InstrumentationAutoConfiguration.java | 39 + .../properties/ConfigPropertiesBridge.java | 119 +++ .../properties/InstrumentationConfigUtil.java | 48 + .../properties/OtelResourceProperties.java | 27 + .../properties/OtelSpringProperties.java | 433 +++++++++ .../properties/OtlpExporterProperties.java | 52 ++ .../properties/SpringConfigProperties.java | 275 ++++++ .../DistroVersionResourceProvider.java | 38 + .../resources/SpringResourceProvider.java | 47 + ...itional-spring-configuration-metadata.json | 857 ++++++++++++++++++ .../resource-config.json | 9 + .../resources/META-INF/spring/aot.factories | 5 + ...ot.autoconfigure.AutoConfiguration.imports | 16 + .../OpenTelemetryAutoConfigurationTest.java | 172 ++++ ...ationAnnotationsAutoConfigurationTest.java | 41 + .../InstrumentationWithSpanAspectTest.java | 479 ++++++++++ ...MicrometerBridgeAutoConfigurationTest.java | 58 ++ ...cInstrumentationAutoConfigurationTest.java | 65 ++ .../SchedulingInstrumentationAspectTest.java | 192 ++++ ...gInstrumentationAutoConfigurationTest.java | 46 + ...bInstrumentationAutoConfigurationTest.java | 101 +++ ...xInstrumentationAutoConfigurationTest.java | 59 ++ .../WebClientBeanPostProcessorTest.java | 118 +++ ...Instrumentation5AutoConfigurationTest.java | 56 ++ .../SpringConfigPropertiesTest.java | 180 ++++ .../DistroVersionResourceProviderTest.java | 51 ++ .../resources/SpringResourceProviderTest.java | 80 ++ .../src/test/resources/application.yaml | 25 + .../logging/CustomListAppender.java | 24 + .../logging/LogbackAppenderTest.java | 266 ++++++ .../resources/logback-no-otel-appenders.xml | 18 + .../resources/logback-test.xml | 27 + .../logging/LogbackMissingTest.java | 26 + ...tInstrumentationAutoConfigurationTest.java | 87 ++ ...Instrumentation6AutoConfigurationTest.java | 56 ++ .../spring-boot-starter-4.0/README.md | 3 + .../spring-boot-starter-4.0/build.gradle.kts | 29 + .../spring-boot-starter-4.0/gradle.properties | 1 + settings.gradle.kts | 2 + 82 files changed, 6594 insertions(+) create mode 100644 docs/apidiffs/current_vs_latest/opentelemetry-spring-boot-autoconfigure-4.0.txt create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/README.md create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/build.gradle.kts create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/gradle.properties create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/metadata.yaml create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/ConditionalOnEnabledInstrumentation.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/InstrumentationPropertyEnabled.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/OtelMapConverter.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/SdkEnabled.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/InstrumentationAnnotationsAutoConfiguration.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/InstrumentationWithSpanAspect.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/JoinPointRequest.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/JointPointCodeAttributesExtractor.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/OpenTelemetryAnnotationsRuntimeHints.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/WithSpanAspect.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/WithSpanAspectParameterAttributeNamesExtractor.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/DataSourcePostProcessor.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfiguration.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/ConcurrentKafkaListenerContainerFactoryPostProcessor.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfiguration.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/LogbackAppenderApplicationListener.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/LogbackAppenderInstaller.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/OpenTelemetryAppenderAutoConfiguration.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfiguration.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/mongo/MongoClientInstrumentationAutoConfiguration.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2dbcInstrumentationAutoConfiguration.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2dbcInstrumentingPostProcessor.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/Java17RuntimeMetricsProvider.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/Java8RuntimeMetricsProvider.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/RuntimeMetricsAutoConfiguration.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/RuntimeMetricsBeanRegistrationExcludeFilter.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/RuntimeMetricsProvider.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SpringSchedulingInstrumentationAspect.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SpringSchedulingInstrumentationAutoConfiguration.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestClientBeanPostProcessor.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestClientInstrumentationAutoConfiguration.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestTemplateBeanPostProcessor.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestTemplateInstrumentation.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfiguration.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webflux/SpringWebfluxInstrumentationAutoConfiguration.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webflux/WebClientBeanPostProcessor.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webmvc/SpringWebMvc7InstrumentationAutoConfiguration.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/ConfigPropertiesBridge.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/InstrumentationConfigUtil.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/OtelResourceProperties.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/OtelSpringProperties.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/OtlpExporterProperties.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigProperties.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/resources/DistroVersionResourceProvider.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/resources/SpringResourceProvider.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/resources/META-INF/additional-spring-configuration-metadata.json create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/resources/META-INF/native-image/io.opentelemetry.instrumentation/opentelemetry-spring-boot/resource-config.json create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/resources/META-INF/spring/aot.factories create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfigurationTest.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/InstrumentationAnnotationsAutoConfigurationTest.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/InstrumentationWithSpanAspectTest.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SchedulingInstrumentationAspectTest.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SchedulingInstrumentationAutoConfigurationTest.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfigurationTest.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webflux/SpringWebfluxInstrumentationAutoConfigurationTest.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webflux/WebClientBeanPostProcessorTest.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webmvc/SpringWebMvcInstrumentation5AutoConfigurationTest.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigPropertiesTest.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/resources/DistroVersionResourceProviderTest.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/resources/SpringResourceProviderTest.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/resources/application.yaml create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/testLogbackAppender/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/CustomListAppender.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/testLogbackAppender/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/LogbackAppenderTest.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/testLogbackAppender/resources/logback-no-otel-appenders.xml create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/testLogbackAppender/resources/logback-test.xml create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/testLogbackMissing/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/LogbackMissingTest.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/testSpring3/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestClientInstrumentationAutoConfigurationTest.java create mode 100644 instrumentation/spring/spring-boot-autoconfigure-4.0/src/testSpring3/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webmvc/SpringWebMvcInstrumentation6AutoConfigurationTest.java create mode 100644 instrumentation/spring/starters/spring-boot-starter-4.0/README.md create mode 100644 instrumentation/spring/starters/spring-boot-starter-4.0/build.gradle.kts create mode 100644 instrumentation/spring/starters/spring-boot-starter-4.0/gradle.properties diff --git a/.fossa.yml b/.fossa.yml index 69f579717200..f0d453aba6d2 100644 --- a/.fossa.yml +++ b/.fossa.yml @@ -268,6 +268,9 @@ targets: - type: gradle path: ./ target: ':instrumentation:spring:spring-boot-autoconfigure' + - type: gradle + path: ./ + target: ':instrumentation:spring:spring-boot-autoconfigure-4.0' - type: gradle path: ./ target: ':instrumentation:spymemcached-2.12:javaagent' @@ -937,6 +940,9 @@ targets: - type: gradle path: ./ target: ':instrumentation:spring:starters:spring-boot-starter' + - type: gradle + path: ./ + target: ':instrumentation:spring:starters:spring-boot-starter-4.0' - type: gradle path: ./ target: ':instrumentation:spring:starters:zipkin-spring-boot-starter' diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-spring-boot-autoconfigure-4.0.txt b/docs/apidiffs/current_vs_latest/opentelemetry-spring-boot-autoconfigure-4.0.txt new file mode 100644 index 000000000000..c02c9f3e4f31 --- /dev/null +++ b/docs/apidiffs/current_vs_latest/opentelemetry-spring-boot-autoconfigure-4.0.txt @@ -0,0 +1,8 @@ +Comparing source compatibility of opentelemetry-spring-boot-autoconfigure-4.0-2.19.0-SNAPSHOT.jar against ++++ NEW CLASS: PUBLIC(+) io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration (not serializable) + +++ CLASS FILE FORMAT VERSION: 61.0 <- n.a. + +++ NEW SUPERCLASS: java.lang.Object + +++ NEW CONSTRUCTOR: PUBLIC(+) OpenTelemetryAutoConfiguration() + +++ NEW ANNOTATION: org.springframework.context.annotation.Configuration + +++ NEW ANNOTATION: org.springframework.boot.context.properties.EnableConfigurationProperties + +++ NEW ELEMENT: value=io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.OtlpExporterProperties,io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.OtelResourceProperties,io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.OtelSpringProperties (+) diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/README.md b/instrumentation/spring/spring-boot-autoconfigure-4.0/README.md new file mode 100644 index 000000000000..b96b77c069d8 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/README.md @@ -0,0 +1,9 @@ +# OpenTelemetry Spring Auto-Configuration + +Auto-configures OpenTelemetry instrumentation for [spring-web](../spring-web/spring-web-3.1/library) +, [spring-webmvc](../spring-webmvc/spring-webmvc-5.3/library), +and [spring-webflux](../spring-webflux/spring-webflux-5.3/library). Leverages Spring Aspect Oriented +Programming, dependency injection, and bean post-processing to trace spring applications. To include +all features listed below use the [opentelemetry-spring-boot-starter](https://opentelemetry.io/docs/zero-code/java/spring-boot-starter/). + +Documentation for OpenTelemetry Spring Auto-Configuration can be found [here](https://opentelemetry.io/docs/zero-code/java/spring-boot-starter/out-of-the-box-instrumentation/). diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/build.gradle.kts b/instrumentation/spring/spring-boot-autoconfigure-4.0/build.gradle.kts new file mode 100644 index 000000000000..ec00d87d8c6f --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/build.gradle.kts @@ -0,0 +1,186 @@ +plugins { + id("otel.library-instrumentation") + id("otel.japicmp-conventions") +} + +base.archivesName.set("opentelemetry-spring-boot-autoconfigure-4.0") +group = "io.opentelemetry.instrumentation" + +val springBootVersion = "4.0.0-M1" + +// r2dbc-proxy is shadowed to prevent org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration +// from being loaded by Spring Boot (by the presence of META-INF/services/io.r2dbc.spi.ConnectionFactoryProvider) - even if the user doesn't want to use R2DBC. +sourceSets { + main { + val shadedDep = project(":instrumentation:r2dbc-1.0:library-instrumentation-shaded") + output.dir( + shadedDep.file("build/extracted/shadow-spring"), + "builtBy" to ":instrumentation:r2dbc-1.0:library-instrumentation-shaded:extractShadowJarSpring", + ) + } +} + +dependencies { + compileOnly("org.springframework.boot:spring-boot-autoconfigure:$springBootVersion") + annotationProcessor("org.springframework.boot:spring-boot-autoconfigure-processor:$springBootVersion") + annotationProcessor("org.springframework.boot:spring-boot-configuration-processor:$springBootVersion") + implementation("javax.validation:validation-api") + + implementation(project(":instrumentation-annotations-support")) + implementation(project(":instrumentation:kafka:kafka-clients:kafka-clients-2.6:library")) + implementation(project(":instrumentation:mongo:mongo-3.1:library")) + compileOnly(project(path = ":instrumentation:r2dbc-1.0:library-instrumentation-shaded", configuration = "shadow")) + implementation(project(":instrumentation:spring:spring-kafka-2.7:library")) + implementation(project(":instrumentation:spring:spring-web:spring-web-3.1:library")) + implementation(project(":instrumentation:spring:spring-webmvc:spring-webmvc-5.3:library")) + compileOnly("jakarta.servlet:jakarta.servlet-api:5.0.0") + implementation(project(":instrumentation:spring:spring-webflux:spring-webflux-5.3:library")) + implementation(project(":instrumentation:micrometer:micrometer-1.5:library")) + implementation(project(":instrumentation:log4j:log4j-appender-2.17:library")) + compileOnly("org.apache.logging.log4j:log4j-core:2.17.0") + implementation(project(":instrumentation:logback:logback-appender-1.0:library")) + implementation(project(":instrumentation:logback:logback-mdc-1.0:library")) + compileOnly("ch.qos.logback:logback-classic:1.0.0") + implementation(project(":instrumentation:jdbc:library")) + implementation(project(":instrumentation:runtime-telemetry:runtime-telemetry-java8:library")) + implementation(project(":instrumentation:runtime-telemetry:runtime-telemetry-java17:library")) + compileOnly("org.springframework.boot:spring-boot-starter-kafka:$springBootVersion") + compileOnly("org.springframework.boot:spring-boot-starter-mongodb:$springBootVersion") + compileOnly("org.springframework.boot:spring-boot-starter-jdbc:$springBootVersion") + + library("org.springframework.kafka:spring-kafka:2.9.0") + library("org.springframework.boot:spring-boot-starter-actuator:$springBootVersion") + library("org.springframework.boot:spring-boot-starter-aop:$springBootVersion") + library("org.springframework.boot:spring-boot-starter-web:$springBootVersion") + library("org.springframework.boot:spring-boot-starter-webflux:$springBootVersion") + library("org.springframework.boot:spring-boot-starter-data-mongodb:$springBootVersion") + library("org.springframework.boot:spring-boot-starter-data-r2dbc:$springBootVersion") + + implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") + implementation(project(":sdk-autoconfigure-support")) + compileOnly("io.opentelemetry:opentelemetry-extension-trace-propagators") + compileOnly("io.opentelemetry.contrib:opentelemetry-aws-xray-propagator") + compileOnly("io.opentelemetry:opentelemetry-exporter-logging") + compileOnly("io.opentelemetry:opentelemetry-exporter-otlp") + compileOnly("io.opentelemetry:opentelemetry-exporter-zipkin") + compileOnly(project(":instrumentation-annotations")) + + compileOnly(project(":instrumentation:resources:library")) + annotationProcessor("com.google.auto.service:auto-service") + compileOnly("com.google.auto.service:auto-service-annotations") + + testLibrary("org.springframework.boot:spring-boot-starter-test:$springBootVersion") { + exclude("org.junit.vintage", "junit-vintage-engine") + } +// testImplementation("javax.servlet:javax.servlet-api:3.1.0") + testImplementation("jakarta.servlet:jakarta.servlet-api:5.0.0") + testRuntimeOnly("com.h2database:h2:1.4.197") + testRuntimeOnly("io.r2dbc:r2dbc-h2:1.0.0.RELEASE") + + testImplementation(project(":testing-common")) + testImplementation("io.opentelemetry:opentelemetry-sdk") + testImplementation("io.opentelemetry:opentelemetry-sdk-testing") + testImplementation(project(":instrumentation:resources:library")) + testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi") + testImplementation("io.opentelemetry:opentelemetry-extension-trace-propagators") + testImplementation("io.opentelemetry.contrib:opentelemetry-aws-xray-propagator") + testImplementation("io.opentelemetry:opentelemetry-exporter-logging") + testImplementation("io.opentelemetry:opentelemetry-exporter-otlp") + testImplementation("io.opentelemetry:opentelemetry-exporter-zipkin") + testImplementation(project(":instrumentation-annotations")) + + implementation(project(":instrumentation:spring:spring-webmvc:spring-webmvc-6.0:library")) + compileOnly("org.springframework.boot:spring-boot-starter-restclient:$springBootVersion") +} + +val latestDepTest = findProperty("testLatestDeps") as Boolean + +// spring 6 (spring boot 3) requires java 17 +if (latestDepTest) { + otelJava { + minJavaVersionSupported.set(JavaVersion.VERSION_17) + } +} + +val testJavaVersion = gradle.startParameter.projectProperties["testJavaVersion"]?.let(JavaVersion::toVersion) + +testing { + suites { + val testLogbackAppender by registering(JvmTestSuite::class) { + dependencies { + implementation(project()) + implementation(project(":testing-common")) + implementation("io.opentelemetry:opentelemetry-sdk") + implementation("io.opentelemetry:opentelemetry-sdk-testing") + implementation("org.mockito:mockito-inline") + implementation("org.springframework.boot:spring-boot-autoconfigure:$springBootVersion") + + implementation(project(":instrumentation:logback:logback-appender-1.0:library")) + implementation(project(":instrumentation:logback:logback-mdc-1.0:library")) + // using the same versions as in the spring-boot-autoconfigure + implementation("ch.qos.logback:logback-classic") { + version { + strictly("1.2.11") + } + } + implementation("org.slf4j:slf4j-api") { + version { + strictly("1.7.32") + } + } + } + } + + val testLogbackMissing by registering(JvmTestSuite::class) { + dependencies { + implementation(project()) + implementation("org.springframework.boot:spring-boot-autoconfigure:$springBootVersion") + + implementation("org.slf4j:slf4j-api") { + version { + strictly("1.7.32") + } + } + } + } + } +} + +configurations.configureEach { + if (name.contains("testLogbackMissing")) { + exclude("ch.qos.logback", "logback-classic") + } +} + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +// options.release.set(17) +} + +otelJava { + minJavaVersionSupported.set(JavaVersion.VERSION_17) +} + +tasks { + compileTestJava { + options.compilerArgs.add("-parameters") + } + + withType().configureEach { + systemProperty("testLatestDeps", latestDepTest) + + // required on jdk17 + jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED") + jvmArgs("-XX:+IgnoreUnrecognizedVMOptions") + } + + val testStableSemconv by registering(Test::class) { + jvmArgs("-Dotel.semconv-stability.opt-in=database") + } + + check { + dependsOn(testing.suites) + dependsOn(testStableSemconv) + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/gradle.properties b/instrumentation/spring/spring-boot-autoconfigure-4.0/gradle.properties new file mode 100644 index 000000000000..3cd93a8bc48f --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/gradle.properties @@ -0,0 +1 @@ +otel.stable=false diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/metadata.yaml b/instrumentation/spring/spring-boot-autoconfigure-4.0/metadata.yaml new file mode 100644 index 000000000000..5a9cc7af0fc6 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/metadata.yaml @@ -0,0 +1,4 @@ +description: > + This instrumentation auto-configures OpenTelemetry instrumentation for spring-web, spring-webmvc, + and spring-webflux to instrument Spring Boot applications. It does not produce telemetry on its + own. This instrumentation is mostly used as part of the Spring Boot starter. diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java new file mode 100644 index 000000000000..b9a9bd7c9a2a --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java @@ -0,0 +1,197 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.TracerProvider; +import io.opentelemetry.common.ComponentLoader; +import io.opentelemetry.instrumentation.api.internal.EmbeddedInstrumentationProperties; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.OtelMapConverter; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.SdkEnabled; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.OtelResourceProperties; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.OtelSpringProperties; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.OtlpExporterProperties; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.SpringConfigProperties; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.resources.DistroVersionResourceProvider; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.resources.SpringResourceProvider; +import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; +import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil; +import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.ConfigurationPropertiesBinding; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.info.BuildProperties; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.DependsOn; +import org.springframework.core.env.Environment; + +/** + * Create {@link io.opentelemetry.api.OpenTelemetry} bean if bean is missing. + * + *

Adds span exporter beans to the active tracer provider. + * + *

Updates the sampler probability for the configured {@link TracerProvider}. + */ +@Configuration +@EnableConfigurationProperties({ + OtlpExporterProperties.class, + OtelResourceProperties.class, + OtelSpringProperties.class +}) +public class OpenTelemetryAutoConfiguration { + private static final Logger logger = + LoggerFactory.getLogger(OpenTelemetryAutoConfiguration.class); + + public OpenTelemetryAutoConfiguration() {} + + @Bean + @ConfigurationPropertiesBinding + OtelMapConverter otelMapConverter() { + // This is needed for otlp exporter headers and OtelResourceProperties. + + // We need this converter, even if the SDK is disabled, + // because the properties are parsed before the SDK is disabled. + + // We also need this converter if the OpenTelemetry bean is user supplied, + // because the environment variables may still contain a value that needs to be converted, + // even if the SDK is disabled (and the value thus ignored). + return new OtelMapConverter(); + } + + @Configuration + @Conditional(SdkEnabled.class) + @DependsOn("otelMapConverter") + @ConditionalOnMissingBean(OpenTelemetry.class) + static class OpenTelemetrySdkConfig { + + @Bean + public OpenTelemetrySdkComponentLoader openTelemetrySdkComponentLoader( + ApplicationContext applicationContext) { + return new OpenTelemetrySdkComponentLoader(applicationContext); + } + + @Bean + public ResourceProvider otelSpringResourceProvider(Optional buildProperties) { + return new SpringResourceProvider(buildProperties); + } + + @Bean + public ResourceProvider otelDistroVersionResourceProvider() { + return new DistroVersionResourceProvider(); + } + + @Bean + public AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk( + Environment env, + OtlpExporterProperties otlpExporterProperties, + OtelResourceProperties resourceProperties, + OtelSpringProperties otelSpringProperties, + OpenTelemetrySdkComponentLoader componentLoader) { + + return AutoConfigureUtil.setConfigPropertiesCustomizer( + AutoConfiguredOpenTelemetrySdk.builder().setComponentLoader(componentLoader), + c -> + SpringConfigProperties.create( + env, otlpExporterProperties, resourceProperties, otelSpringProperties, c)) + .build(); + } + + @Bean + public OpenTelemetry openTelemetry( + AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk) { + logger.info( + "OpenTelemetry Spring Boot starter ({}) has been started", + EmbeddedInstrumentationProperties.findVersion( + "io.opentelemetry.spring-boot-autoconfigure")); + + return autoConfiguredOpenTelemetrySdk.getOpenTelemetrySdk(); + } + + /** + * Expose the {@link ConfigProperties} bean for use in other auto-configurations. + * + *

Not using spring boot properties directly in order to support {@link + * io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer#addPropertiesCustomizer(Function)} + * and {@link + * io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer#addPropertiesSupplier(Supplier)}. + */ + @Bean + public ConfigProperties otelProperties( + AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk) { + return AutoConfigureUtil.getConfig(autoConfiguredOpenTelemetrySdk); + } + } + + @Configuration + @DependsOn("otelMapConverter") + @ConditionalOnMissingBean(OpenTelemetry.class) + @ConditionalOnProperty(name = "otel.sdk.disabled", havingValue = "true") + static class DisabledOpenTelemetrySdkConfig { + @Bean + public OpenTelemetry openTelemetry() { + logger.info("OpenTelemetry Spring Boot starter has been disabled"); + + return OpenTelemetry.noop(); + } + + @Bean + public ConfigProperties otelProperties() { + return DefaultConfigProperties.createFromMap(Collections.emptyMap()); + } + } + + @Configuration + @ConditionalOnBean(OpenTelemetry.class) + @ConditionalOnMissingBean({ConfigProperties.class}) + static class FallbackConfigProperties { + @Bean + public ConfigProperties otelProperties(ApplicationContext applicationContext) { + return DefaultConfigProperties.create( + Collections.emptyMap(), new OpenTelemetrySdkComponentLoader(applicationContext)); + } + } + + /** + * The {@link ComponentLoader} is used by the SDK autoconfiguration to load all components, e.g. + * here + */ + static class OpenTelemetrySdkComponentLoader implements ComponentLoader { + private final ApplicationContext applicationContext; + + private final SpiHelper spiHelper = + SpiHelper.create(OpenTelemetrySdkComponentLoader.class.getClassLoader()); + + public OpenTelemetrySdkComponentLoader(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + @Override + public Iterable load(Class spiClass) { + List spi = spiHelper.load(spiClass); + List beans = + applicationContext.getBeanProvider(spiClass).orderedStream().collect(Collectors.toList()); + spi.addAll(beans); + return spi; + } + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/ConditionalOnEnabledInstrumentation.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/ConditionalOnEnabledInstrumentation.java new file mode 100644 index 000000000000..7c55f430d1e4 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/ConditionalOnEnabledInstrumentation.java @@ -0,0 +1,28 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal; + +import io.opentelemetry.api.OpenTelemetry; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.context.annotation.Conditional; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@ConditionalOnBean(OpenTelemetry.class) +@Conditional({SdkEnabled.class, InstrumentationPropertyEnabled.class}) +public @interface ConditionalOnEnabledInstrumentation { + String module(); + + boolean enabledByDefault() default true; +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/InstrumentationPropertyEnabled.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/InstrumentationPropertyEnabled.java new file mode 100644 index 000000000000..87687f668b6c --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/InstrumentationPropertyEnabled.java @@ -0,0 +1,37 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal; + +import java.util.Map; +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public class InstrumentationPropertyEnabled implements Condition { + + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + Map attributes = + metadata.getAnnotationAttributes(ConditionalOnEnabledInstrumentation.class.getName()); + + String name = String.format("otel.instrumentation.%s.enabled", attributes.get("module")); + Boolean explicit = context.getEnvironment().getProperty(name, Boolean.class); + if (explicit != null) { + return explicit; + } + boolean defaultValue = (boolean) attributes.get("enabledByDefault"); + if (!defaultValue) { + return false; + } + return context + .getEnvironment() + .getProperty("otel.instrumentation.common.default-enabled", Boolean.class, true); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/OtelMapConverter.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/OtelMapConverter.java new file mode 100644 index 000000000000..e32cfb75f5b1 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/OtelMapConverter.java @@ -0,0 +1,34 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal; + +import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; +import java.util.Collections; +import java.util.Map; +import org.springframework.core.convert.converter.Converter; + +/** + * The MapConverter class is used to convert a String to a Map. The String is expected to be in the + * format of a comma separated list of key=value pairs, e.g. key1=value1,key2=value2. + * + *

This is the expected format for the OTEL_RESOURCE_ATTRIBUTES and + * OTEL_EXPORTER_OTLP_HEADERS environment variables. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public class OtelMapConverter implements Converter> { + + public static final String KEY = "key"; + + @Override + public Map convert(String source) { + DefaultConfigProperties properties = + DefaultConfigProperties.createFromMap(Collections.singletonMap(KEY, source)); + + return properties.getMap(KEY); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/SdkEnabled.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/SdkEnabled.java new file mode 100644 index 000000000000..082bdebc00b7 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/SdkEnabled.java @@ -0,0 +1,22 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal; + +import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public class SdkEnabled extends AnyNestedCondition { + public SdkEnabled() { + super(ConfigurationPhase.PARSE_CONFIGURATION); + } + + @ConditionalOnProperty(name = "otel.sdk.disabled", havingValue = "false", matchIfMissing = true) + static class NotDisabled {} +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/InstrumentationAnnotationsAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/InstrumentationAnnotationsAutoConfiguration.java new file mode 100644 index 000000000000..0e8081622388 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/InstrumentationAnnotationsAutoConfiguration.java @@ -0,0 +1,36 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.annotations; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.annotations.WithSpan; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.DefaultParameterNameDiscoverer; +import org.springframework.core.ParameterNameDiscoverer; + +/** + * Configures {@link WithSpanAspect} to trace bean methods annotated with {@link WithSpan}. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +@ConditionalOnEnabledInstrumentation(module = "annotations") +@ConditionalOnClass(Aspect.class) +@Configuration +public class InstrumentationAnnotationsAutoConfiguration { + private final ParameterNameDiscoverer parameterNameDiscoverer = + new DefaultParameterNameDiscoverer(); + + @Bean + @ConditionalOnClass(WithSpan.class) + InstrumentationWithSpanAspect otelInstrumentationWithSpanAspect(OpenTelemetry openTelemetry) { + return new InstrumentationWithSpanAspect(openTelemetry, parameterNameDiscoverer); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/InstrumentationWithSpanAspect.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/InstrumentationWithSpanAspect.java new file mode 100644 index 000000000000..90c8c2e229d5 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/InstrumentationWithSpanAspect.java @@ -0,0 +1,32 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.annotations; + +import io.opentelemetry.api.OpenTelemetry; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.core.ParameterNameDiscoverer; + +@Aspect +class InstrumentationWithSpanAspect extends WithSpanAspect { + + InstrumentationWithSpanAspect( + OpenTelemetry openTelemetry, ParameterNameDiscoverer parameterNameDiscoverer) { + super( + openTelemetry, + parameterNameDiscoverer, + new JoinPointRequest.InstrumentationAnnotationFactory(), + new WithSpanAspectParameterAttributeNamesExtractor + .InstrumentationAnnotationAttributeNameSupplier()); + } + + @Override + @Around("@annotation(io.opentelemetry.instrumentation.annotations.WithSpan)") + public Object traceMethod(ProceedingJoinPoint pjp) throws Throwable { + return super.traceMethod(pjp); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/JoinPointRequest.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/JoinPointRequest.java new file mode 100644 index 000000000000..6dbfa686e4d3 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/JoinPointRequest.java @@ -0,0 +1,84 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.annotations; + +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.annotations.WithSpan; +import io.opentelemetry.instrumentation.api.semconv.util.SpanNames; +import java.lang.reflect.Method; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.reflect.MethodSignature; + +final class JoinPointRequest { + + private final JoinPoint joinPoint; + private final Method method; + private final String spanName; + private final SpanKind spanKind; + private final boolean inheritContext; + + private JoinPointRequest( + JoinPoint joinPoint, + Method method, + String spanName, + SpanKind spanKind, + boolean inheritContext) { + if (spanName.isEmpty()) { + spanName = SpanNames.fromMethod(method); + } + + this.joinPoint = joinPoint; + this.method = method; + this.spanName = spanName; + this.spanKind = spanKind; + this.inheritContext = inheritContext; + } + + String spanName() { + return spanName; + } + + SpanKind spanKind() { + return spanKind; + } + + Method method() { + return method; + } + + Object[] args() { + return joinPoint.getArgs(); + } + + boolean inheritContext() { + return inheritContext; + } + + interface Factory { + + JoinPointRequest create(JoinPoint joinPoint); + } + + static final class InstrumentationAnnotationFactory implements Factory { + + @Override + public JoinPointRequest create(JoinPoint joinPoint) { + MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); + Method method = methodSignature.getMethod(); + + // in rare cases, when interface method does not have annotations but the implementation does, + // and the AspectJ factory is configured to proxy interfaces, this class will receive the + // abstract interface method (without annotations) instead of the implementation method (with + // annotations); these defaults prevent NPEs in this scenario + WithSpan annotation = method.getDeclaredAnnotation(WithSpan.class); + String spanName = annotation != null ? annotation.value() : ""; + SpanKind spanKind = annotation != null ? annotation.kind() : SpanKind.INTERNAL; + boolean inheritContext = annotation == null || annotation.inheritContext(); + + return new JoinPointRequest(joinPoint, method, spanName, spanKind, inheritContext); + } + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/JointPointCodeAttributesExtractor.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/JointPointCodeAttributesExtractor.java new file mode 100644 index 000000000000..c0dc1f8be230 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/JointPointCodeAttributesExtractor.java @@ -0,0 +1,22 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.annotations; + +import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeAttributesGetter; + +enum JointPointCodeAttributesExtractor implements CodeAttributesGetter { + INSTANCE; + + @Override + public Class getCodeClass(JoinPointRequest joinPointRequest) { + return joinPointRequest.method().getDeclaringClass(); + } + + @Override + public String getMethodName(JoinPointRequest joinPointRequest) { + return joinPointRequest.method().getName(); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/OpenTelemetryAnnotationsRuntimeHints.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/OpenTelemetryAnnotationsRuntimeHints.java new file mode 100644 index 000000000000..7bb5e2aa24bf --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/OpenTelemetryAnnotationsRuntimeHints.java @@ -0,0 +1,28 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.annotations; + +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.aot.hint.TypeReference; + +class OpenTelemetryAnnotationsRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + hints + .reflection() + .registerType( + TypeReference.of( + "io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.annotations.InstrumentationWithSpanAspect"), + hint -> hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS)) + .registerType( + TypeReference.of( + "io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.scheduling.SpringSchedulingInstrumentationAspect"), + hint -> hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS)); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/WithSpanAspect.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/WithSpanAspect.java new file mode 100644 index 000000000000..89ff79dd6ab9 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/WithSpanAspect.java @@ -0,0 +1,88 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.annotations; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.annotations.WithSpan; +import io.opentelemetry.instrumentation.api.annotation.support.MethodSpanAttributesExtractor; +import io.opentelemetry.instrumentation.api.annotation.support.ParameterAttributeNamesExtractor; +import io.opentelemetry.instrumentation.api.annotation.support.async.AsyncOperationEndSupport; +import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import org.aspectj.lang.ProceedingJoinPoint; +import org.springframework.core.ParameterNameDiscoverer; + +/** + * Uses Spring-AOP to wrap methods marked by {@link WithSpan} in a {@link Span}. + * + *

Ensure methods annotated with {@link WithSpan} are implemented on beans managed by the Spring + * container. + * + *

Note: This Aspect uses spring-aop to proxy beans. Therefore, the {@link WithSpan} annotation + * can not be applied to constructors. + */ +abstract class WithSpanAspect { + private static final String INSTRUMENTATION_NAME = "io.opentelemetry.spring-boot-autoconfigure"; + + private final Instrumenter instrumenter; + private final JoinPointRequest.Factory requestFactory; + + WithSpanAspect( + OpenTelemetry openTelemetry, + ParameterNameDiscoverer parameterNameDiscoverer, + JoinPointRequest.Factory requestFactory, + WithSpanAspectParameterAttributeNamesExtractor.SpanAttributeNameSupplier + spanAttributeNameSupplier) { + + ParameterAttributeNamesExtractor parameterAttributeNamesExtractor = + new WithSpanAspectParameterAttributeNamesExtractor( + parameterNameDiscoverer, spanAttributeNameSupplier); + + instrumenter = + Instrumenter.builder(openTelemetry, INSTRUMENTATION_NAME, JoinPointRequest::spanName) + .addAttributesExtractor( + CodeAttributesExtractor.create(JointPointCodeAttributesExtractor.INSTANCE)) + .addAttributesExtractor( + MethodSpanAttributesExtractor.create( + JoinPointRequest::method, + parameterAttributeNamesExtractor, + JoinPointRequest::args)) + .addContextCustomizer(WithSpanAspect::parentContext) + .buildInstrumenter(JoinPointRequest::spanKind); + this.requestFactory = requestFactory; + } + + private static Context parentContext( + Context parentContext, JoinPointRequest request, Attributes unused) { + return request.inheritContext() ? parentContext : Context.root(); + } + + public Object traceMethod(ProceedingJoinPoint pjp) throws Throwable { + JoinPointRequest request = requestFactory.create(pjp); + Context parentContext = Context.current(); + if (!instrumenter.shouldStart(parentContext, request)) { + return pjp.proceed(); + } + + Context context = instrumenter.start(parentContext, request); + AsyncOperationEndSupport asyncOperationEndSupport = + AsyncOperationEndSupport.create( + instrumenter, Object.class, request.method().getReturnType()); + + Object response; + try (Scope ignored = context.makeCurrent()) { + response = pjp.proceed(); + } catch (Throwable t) { + asyncOperationEndSupport.asyncEnd(context, request, null, t); + throw t; + } + return asyncOperationEndSupport.asyncEnd(context, request, response, null); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/WithSpanAspectParameterAttributeNamesExtractor.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/WithSpanAspectParameterAttributeNamesExtractor.java new file mode 100644 index 000000000000..7471986cf009 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/WithSpanAspectParameterAttributeNamesExtractor.java @@ -0,0 +1,76 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.annotations; + +import io.opentelemetry.instrumentation.annotations.SpanAttribute; +import io.opentelemetry.instrumentation.api.annotation.support.ParameterAttributeNamesExtractor; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import javax.annotation.Nullable; +import org.springframework.core.ParameterNameDiscoverer; + +class WithSpanAspectParameterAttributeNamesExtractor implements ParameterAttributeNamesExtractor { + + private final ParameterNameDiscoverer parameterNameDiscoverer; + private final SpanAttributeNameSupplier spanAttributeNameSupplier; + + public WithSpanAspectParameterAttributeNamesExtractor( + ParameterNameDiscoverer parameterNameDiscoverer, + SpanAttributeNameSupplier spanAttributeNameSupplier) { + this.parameterNameDiscoverer = parameterNameDiscoverer; + this.spanAttributeNameSupplier = spanAttributeNameSupplier; + } + + @Override + @Nullable + public String[] extract(Method method, Parameter[] parameters) { + String[] parameterNames = parameterNameDiscoverer.getParameterNames(method); + String[] attributeNames = new String[parameters.length]; + + for (int i = 0; i < parameters.length; i++) { + attributeNames[i] = attributeName(parameters[i], parameterNames, i); + } + return attributeNames; + } + + @Nullable + private String attributeName(Parameter parameter, String[] parameterNames, int index) { + String annotationValue = spanAttributeNameSupplier.spanAttributeName(parameter); + if (annotationValue == null) { + return null; + } + if (!annotationValue.isEmpty()) { + return annotationValue; + } + if (parameterNames != null && index < parameterNames.length) { + String parameterName = parameterNames[index]; + if (parameterName != null && !parameterName.isEmpty()) { + return parameterName; + } + } + if (parameter.isNamePresent()) { + return parameter.getName(); + } + return null; + } + + interface SpanAttributeNameSupplier { + + @Nullable + String spanAttributeName(Parameter parameter); + } + + static final class InstrumentationAnnotationAttributeNameSupplier + implements SpanAttributeNameSupplier { + + @Nullable + @Override + public String spanAttributeName(Parameter parameter) { + SpanAttribute annotation = parameter.getDeclaredAnnotation(SpanAttribute.class); + return annotation == null ? null : annotation.value(); + } + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/DataSourcePostProcessor.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/DataSourcePostProcessor.java new file mode 100644 index 000000000000..83ab3fae4a16 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/DataSourcePostProcessor.java @@ -0,0 +1,77 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.jdbc; + +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.jdbc.datasource.JdbcTelemetry; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.InstrumentationConfigUtil; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import javax.sql.DataSource; +import org.springframework.aop.scope.ScopedProxyUtils; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.core.Ordered; + +final class DataSourcePostProcessor implements BeanPostProcessor, Ordered { + + private static final Class ROUTING_DATA_SOURCE_CLASS = getRoutingDataSourceClass(); + + private final ObjectProvider openTelemetryProvider; + private final ObjectProvider configPropertiesProvider; + + DataSourcePostProcessor( + ObjectProvider openTelemetryProvider, + ObjectProvider configPropertiesProvider) { + this.openTelemetryProvider = openTelemetryProvider; + this.configPropertiesProvider = configPropertiesProvider; + } + + private static Class getRoutingDataSourceClass() { + try { + return Class.forName("org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource"); + } catch (ClassNotFoundException exception) { + return null; + } + } + + private static boolean isRoutingDatasource(Object bean) { + return ROUTING_DATA_SOURCE_CLASS != null && ROUTING_DATA_SOURCE_CLASS.isInstance(bean); + } + + @CanIgnoreReturnValue + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) { + // Exclude scoped proxy beans to avoid double wrapping + if (bean instanceof DataSource dataSource + && !isRoutingDatasource(bean) + && !ScopedProxyUtils.isScopedTarget(beanName)) { + return JdbcTelemetry.builder(openTelemetryProvider.getObject()) + .setStatementSanitizationEnabled( + InstrumentationConfigUtil.isStatementSanitizationEnabled( + configPropertiesProvider.getObject(), + "otel.instrumentation.jdbc.statement-sanitizer.enabled")) + .setCaptureQueryParameters( + configPropertiesProvider + .getObject() + .getBoolean( + "otel.instrumentation.jdbc.experimental.capture-query-parameters", false)) + .setTransactionInstrumenterEnabled( + configPropertiesProvider + .getObject() + .getBoolean("otel.instrumentation.jdbc.experimental.transaction.enabled", false)) + .build() + .wrap(dataSource); + } + return bean; + } + + // To be one of the first bean post-processors to be executed + @Override + public int getOrder() { + return Ordered.LOWEST_PRECEDENCE - 20; + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfiguration.java new file mode 100644 index 000000000000..af3a7fd009ee --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfiguration.java @@ -0,0 +1,39 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.jdbc; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import javax.sql.DataSource; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +@ConditionalOnEnabledInstrumentation(module = "jdbc") +@AutoConfiguration(after = DataSourceAutoConfiguration.class) +@ConditionalOnBean({DataSource.class}) +@Configuration(proxyBeanMethods = false) +public class JdbcInstrumentationAutoConfiguration { + + // For error prone + public JdbcInstrumentationAutoConfiguration() {} + + @Bean + // static to avoid "is not eligible for getting processed by all BeanPostProcessors" warning + static DataSourcePostProcessor dataSourcePostProcessor( + ObjectProvider openTelemetryProvider, + ObjectProvider configPropertiesProvider) { + return new DataSourcePostProcessor(openTelemetryProvider, configPropertiesProvider); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/ConcurrentKafkaListenerContainerFactoryPostProcessor.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/ConcurrentKafkaListenerContainerFactoryPostProcessor.java new file mode 100644 index 000000000000..d26c2d684526 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/ConcurrentKafkaListenerContainerFactoryPostProcessor.java @@ -0,0 +1,59 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka; + +import io.opentelemetry.instrumentation.spring.kafka.v2_7.SpringKafkaTelemetry; +import java.lang.reflect.Field; +import java.util.function.Supplier; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.kafka.config.AbstractKafkaListenerContainerFactory; +import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; +import org.springframework.kafka.listener.BatchInterceptor; +import org.springframework.kafka.listener.RecordInterceptor; + +class ConcurrentKafkaListenerContainerFactoryPostProcessor implements BeanPostProcessor { + + private final Supplier springKafkaTelemetry; + + ConcurrentKafkaListenerContainerFactoryPostProcessor( + Supplier springKafkaTelemetry) { + this.springKafkaTelemetry = springKafkaTelemetry; + } + + @SuppressWarnings("unchecked") + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) { + if (!(bean instanceof ConcurrentKafkaListenerContainerFactory)) { + return bean; + } + + ConcurrentKafkaListenerContainerFactory listenerContainerFactory = + (ConcurrentKafkaListenerContainerFactory) bean; + SpringKafkaTelemetry springKafkaTelemetry = this.springKafkaTelemetry.get(); + + // use reflection to read existing values to avoid overwriting user configured interceptors + BatchInterceptor batchInterceptor = + readField(listenerContainerFactory, "batchInterceptor", BatchInterceptor.class); + RecordInterceptor recordInterceptor = + readField(listenerContainerFactory, "recordInterceptor", RecordInterceptor.class); + listenerContainerFactory.setBatchInterceptor( + springKafkaTelemetry.createBatchInterceptor(batchInterceptor)); + listenerContainerFactory.setRecordInterceptor( + springKafkaTelemetry.createRecordInterceptor(recordInterceptor)); + + return listenerContainerFactory; + } + + private static T readField(Object container, String filedName, Class fieldType) { + try { + Field field = AbstractKafkaListenerContainerFactory.class.getDeclaredField(filedName); + field.setAccessible(true); + return fieldType.cast(field.get(container)); + } catch (Exception exception) { + return null; + } + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfiguration.java new file mode 100644 index 000000000000..0e7145dd76b7 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfiguration.java @@ -0,0 +1,63 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.kafkaclients.v2_6.KafkaTelemetry; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; +import io.opentelemetry.instrumentation.spring.kafka.v2_7.SpringKafkaTelemetry; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.kafka.autoconfigure.DefaultKafkaProducerFactoryCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; +import org.springframework.kafka.core.KafkaTemplate; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +@ConditionalOnEnabledInstrumentation(module = "kafka") +@ConditionalOnClass({KafkaTemplate.class, ConcurrentKafkaListenerContainerFactory.class}) +@Configuration +public class KafkaInstrumentationAutoConfiguration { + + @Bean + DefaultKafkaProducerFactoryCustomizer otelKafkaProducerFactoryCustomizer( + OpenTelemetry openTelemetry) { + KafkaTelemetry kafkaTelemetry = KafkaTelemetry.create(openTelemetry); + return producerFactory -> producerFactory.addPostProcessor(kafkaTelemetry::wrap); + } + + @Bean + static SpringKafkaTelemetry getTelemetry( + ObjectProvider openTelemetryProvider, + ObjectProvider configPropertiesProvider) { + return SpringKafkaTelemetry.builder(openTelemetryProvider.getObject()) + .setCaptureExperimentalSpanAttributes( + configPropertiesProvider + .getObject() + .getBoolean("otel.instrumentation.kafka.experimental-span-attributes", false)) + .build(); + } + + // static to avoid "is not eligible for getting processed by all BeanPostProcessors" warning + @Bean + @ConditionalOnProperty( + name = "otel.instrumentation.kafka.autoconfigure-interceptor", + havingValue = "true", + matchIfMissing = true) + static ConcurrentKafkaListenerContainerFactoryPostProcessor + otelKafkaListenerContainerFactoryBeanPostProcessor( + ObjectProvider openTelemetryProvider, + ObjectProvider configPropertiesProvider) { + return new ConcurrentKafkaListenerContainerFactoryPostProcessor( + () -> getTelemetry(openTelemetryProvider, configPropertiesProvider)); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/LogbackAppenderApplicationListener.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/LogbackAppenderApplicationListener.java new file mode 100644 index 000000000000..eb0f34714951 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/LogbackAppenderApplicationListener.java @@ -0,0 +1,76 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.logging; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; +import org.springframework.boot.context.logging.LoggingApplicationListener; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.event.GenericApplicationListener; +import org.springframework.core.ResolvableType; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public class LogbackAppenderApplicationListener implements GenericApplicationListener { + + private static final Class[] SOURCE_TYPES = { + SpringApplication.class, ApplicationContext.class + }; + private static final Class[] EVENT_TYPES = {ApplicationEnvironmentPreparedEvent.class}; + private static final boolean LOGBACK_PRESENT = isLogbackPresent(); + + @Override + public boolean supportsSourceType(Class sourceType) { + return isAssignableFrom(sourceType, SOURCE_TYPES); + } + + @Override + public boolean supportsEventType(ResolvableType resolvableType) { + return isAssignableFrom(resolvableType.getRawClass(), EVENT_TYPES); + } + + private static boolean isAssignableFrom(Class type, Class... supportedTypes) { + if (type != null) { + for (Class supportedType : supportedTypes) { + if (supportedType.isAssignableFrom(type)) { + return true; + } + } + } + return false; + } + + @Override + public void onApplicationEvent(ApplicationEvent event) { + if (!LOGBACK_PRESENT) { + return; + } + + // Event for which org.springframework.boot.context.logging.LoggingApplicationListener + // initializes logging + if (event instanceof ApplicationEnvironmentPreparedEvent preparedEvent) { + LogbackAppenderInstaller.install(preparedEvent); + } + } + + @Override + public int getOrder() { + return LoggingApplicationListener.DEFAULT_ORDER + 1; // To execute this listener just after + // org.springframework.boot.context.logging.LoggingApplicationListener + } + + private static boolean isLogbackPresent() { + try { + Class.forName("ch.qos.logback.core.Appender"); + return true; + } catch (ClassNotFoundException exception) { + return false; + } + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/LogbackAppenderInstaller.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/LogbackAppenderInstaller.java new file mode 100644 index 000000000000..5b5cf7474fa5 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/LogbackAppenderInstaller.java @@ -0,0 +1,260 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.logging; + +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.Appender; +import io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender; +import java.util.Iterator; +import java.util.Optional; +import org.slf4j.ILoggerFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; + +class LogbackAppenderInstaller { + + static void install(ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent) { + Optional + existingMdcAppender = + findAppender( + io.opentelemetry.instrumentation.logback.mdc.v1_0.OpenTelemetryAppender.class); + if (existingMdcAppender.isPresent()) { + initializeMdcAppenderFromProperties( + applicationEnvironmentPreparedEvent, existingMdcAppender.get()); + } else if (isLogbackMdcAppenderAddable(applicationEnvironmentPreparedEvent)) { + addMdcAppender(applicationEnvironmentPreparedEvent); + } + + Optional existingOpenTelemetryAppender = + findAppender(OpenTelemetryAppender.class); + if (existingOpenTelemetryAppender.isPresent()) { + reInitializeOpenTelemetryAppender( + existingOpenTelemetryAppender, applicationEnvironmentPreparedEvent); + } else if (isLogbackAppenderAddable(applicationEnvironmentPreparedEvent)) { + addOpenTelemetryAppender(applicationEnvironmentPreparedEvent); + } + } + + private static boolean isLogbackAppenderAddable( + ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent) { + return isAppenderAddable( + applicationEnvironmentPreparedEvent, "otel.instrumentation.logback-appender.enabled"); + } + + private static boolean isLogbackMdcAppenderAddable( + ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent) { + return isAppenderAddable( + applicationEnvironmentPreparedEvent, "otel.instrumentation.logback-mdc.enabled"); + } + + private static boolean isAppenderAddable( + ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent, String property) { + boolean otelSdkDisabled = + evaluateBooleanProperty(applicationEnvironmentPreparedEvent, "otel.sdk.disabled", false); + boolean logbackInstrumentationEnabled = + evaluateBooleanProperty(applicationEnvironmentPreparedEvent, property, true); + return !otelSdkDisabled && logbackInstrumentationEnabled; + } + + private static void reInitializeOpenTelemetryAppender( + Optional existingOpenTelemetryAppender, + ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent) { + OpenTelemetryAppender openTelemetryAppender = existingOpenTelemetryAppender.get(); + // The OpenTelemetry appender is stopped and restarted from the + // org.springframework.boot.context.logging.LoggingApplicationListener.initialize + // method. + // The OpenTelemetryAppender initializes the LoggingEventMapper in the start() method. So, here + // we stop the OpenTelemetry appender before its re-initialization and its restart. + openTelemetryAppender.stop(); + initializeOpenTelemetryAppenderFromProperties( + applicationEnvironmentPreparedEvent, openTelemetryAppender); + openTelemetryAppender.start(); + } + + private static void addOpenTelemetryAppender( + ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent) { + ch.qos.logback.classic.Logger logger = + (ch.qos.logback.classic.Logger) + LoggerFactory.getILoggerFactory().getLogger(Logger.ROOT_LOGGER_NAME); + OpenTelemetryAppender openTelemetryAppender = new OpenTelemetryAppender(); + initializeOpenTelemetryAppenderFromProperties( + applicationEnvironmentPreparedEvent, openTelemetryAppender); + openTelemetryAppender.start(); + logger.addAppender(openTelemetryAppender); + } + + private static void initializeOpenTelemetryAppenderFromProperties( + ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent, + OpenTelemetryAppender openTelemetryAppender) { + + // Implemented in the same way as the + // org.springframework.boot.context.logging.LoggingApplicationListener, config properties not + // available + Boolean codeAttribute = + evaluateBooleanProperty( + applicationEnvironmentPreparedEvent, + "otel.instrumentation.logback-appender.experimental.capture-code-attributes"); + if (codeAttribute != null) { + openTelemetryAppender.setCaptureCodeAttributes(codeAttribute.booleanValue()); + } + + Boolean markerAttribute = + evaluateBooleanProperty( + applicationEnvironmentPreparedEvent, + "otel.instrumentation.logback-appender.experimental.capture-marker-attribute"); + if (markerAttribute != null) { + openTelemetryAppender.setCaptureMarkerAttribute(markerAttribute.booleanValue()); + } + + Boolean keyValuePairAttributes = + evaluateBooleanProperty( + applicationEnvironmentPreparedEvent, + "otel.instrumentation.logback-appender.experimental.capture-key-value-pair-attributes"); + if (keyValuePairAttributes != null) { + openTelemetryAppender.setCaptureKeyValuePairAttributes(keyValuePairAttributes.booleanValue()); + } + + Boolean logAttributes = + evaluateBooleanProperty( + applicationEnvironmentPreparedEvent, + "otel.instrumentation.logback-appender.experimental-log-attributes"); + if (logAttributes != null) { + openTelemetryAppender.setCaptureExperimentalAttributes(logAttributes.booleanValue()); + } + + Boolean loggerContextAttributes = + evaluateBooleanProperty( + applicationEnvironmentPreparedEvent, + "otel.instrumentation.logback-appender.experimental.capture-logger-context-attributes"); + if (loggerContextAttributes != null) { + openTelemetryAppender.setCaptureLoggerContext(loggerContextAttributes.booleanValue()); + } + + Boolean captureArguments = + evaluateBooleanProperty( + applicationEnvironmentPreparedEvent, + "otel.instrumentation.logback-appender.experimental.capture-arguments"); + if (captureArguments != null) { + openTelemetryAppender.setCaptureArguments(captureArguments.booleanValue()); + } + + Boolean captureLogstashAttributes = + evaluateBooleanProperty( + applicationEnvironmentPreparedEvent, + "otel.instrumentation.logback-appender.experimental.capture-logstash-attributes"); + if (captureLogstashAttributes != null) { + openTelemetryAppender.setCaptureLogstashAttributes(captureLogstashAttributes.booleanValue()); + } + + String mdcAttributeProperty = + applicationEnvironmentPreparedEvent + .getEnvironment() + .getProperty( + "otel.instrumentation.logback-appender.experimental.capture-mdc-attributes", + String.class); + if (mdcAttributeProperty != null) { + openTelemetryAppender.setCaptureMdcAttributes(mdcAttributeProperty); + } + } + + private static void addMdcAppender( + ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent) { + ch.qos.logback.classic.Logger logger = + (ch.qos.logback.classic.Logger) + LoggerFactory.getILoggerFactory().getLogger(Logger.ROOT_LOGGER_NAME); + io.opentelemetry.instrumentation.logback.mdc.v1_0.OpenTelemetryAppender openTelemetryAppender = + new io.opentelemetry.instrumentation.logback.mdc.v1_0.OpenTelemetryAppender(); + initializeMdcAppenderFromProperties(applicationEnvironmentPreparedEvent, openTelemetryAppender); + openTelemetryAppender.start(); + logger.addAppender(openTelemetryAppender); + // move existing appenders under otel mdc appender, so they could observe the added mdc values + for (Iterator> i = logger.iteratorForAppenders(); i.hasNext(); ) { + Appender appender = i.next(); + if (appender != openTelemetryAppender) { + openTelemetryAppender.addAppender(appender); + logger.detachAppender(appender); + } + } + } + + private static void initializeMdcAppenderFromProperties( + ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent, + io.opentelemetry.instrumentation.logback.mdc.v1_0.OpenTelemetryAppender + openTelemetryAppender) { + + // Implemented in the same way as the + // org.springframework.boot.context.logging.LoggingApplicationListener, config properties not + // available + Boolean addBaggage = + evaluateBooleanProperty( + applicationEnvironmentPreparedEvent, "otel.instrumentation.logback-mdc.add-baggage"); + if (addBaggage != null) { + openTelemetryAppender.setAddBaggage(addBaggage); + } + + String traceIdKey = + applicationEnvironmentPreparedEvent + .getEnvironment() + .getProperty("otel.instrumentation.common.logging.trace-id", String.class); + if (traceIdKey != null) { + openTelemetryAppender.setTraceIdKey(traceIdKey); + } + + String spanIdKey = + applicationEnvironmentPreparedEvent + .getEnvironment() + .getProperty("otel.instrumentation.common.logging.span-id", String.class); + if (spanIdKey != null) { + openTelemetryAppender.setSpanIdKey(spanIdKey); + } + + String traceFlagsKey = + applicationEnvironmentPreparedEvent + .getEnvironment() + .getProperty("otel.instrumentation.common.logging.trace-flags", String.class); + if (traceFlagsKey != null) { + openTelemetryAppender.setTraceFlagsKey(traceFlagsKey); + } + } + + private static Boolean evaluateBooleanProperty( + ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent, String property) { + return applicationEnvironmentPreparedEvent + .getEnvironment() + .getProperty(property, Boolean.class); + } + + private static boolean evaluateBooleanProperty( + ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent, + String property, + boolean defaultValue) { + return applicationEnvironmentPreparedEvent + .getEnvironment() + .getProperty(property, Boolean.class, defaultValue); + } + + private static Optional findAppender(Class appenderClass) { + ILoggerFactory loggerFactorySpi = LoggerFactory.getILoggerFactory(); + if (!(loggerFactorySpi instanceof LoggerContext loggerContext)) { + return Optional.empty(); + } + for (ch.qos.logback.classic.Logger logger : loggerContext.getLoggerList()) { + Iterator> appenderIterator = logger.iteratorForAppenders(); + while (appenderIterator.hasNext()) { + Appender appender = appenderIterator.next(); + if (appenderClass.isInstance(appender)) { + T openTelemetryAppender = appenderClass.cast(appender); + return Optional.of(openTelemetryAppender); + } + } + } + return Optional.empty(); + } + + private LogbackAppenderInstaller() {} +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/OpenTelemetryAppenderAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/OpenTelemetryAppenderAutoConfiguration.java new file mode 100644 index 000000000000..7fae726c9fb8 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/OpenTelemetryAppenderAutoConfiguration.java @@ -0,0 +1,53 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.logging; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +@Configuration +@SuppressWarnings("OtelPrivateConstructorForUtilityClass") +public class OpenTelemetryAppenderAutoConfiguration { + + @ConditionalOnEnabledInstrumentation(module = "log4j-appender") + @ConditionalOnClass(org.apache.logging.log4j.core.LoggerContext.class) + @Configuration + static class Log4jAppenderConfig { + + @Bean + ApplicationListener log4jOtelAppenderInitializer( + OpenTelemetry openTelemetry) { + return event -> { + io.opentelemetry.instrumentation.log4j.appender.v2_17.OpenTelemetryAppender.install( + openTelemetry); + }; + } + } + + @ConditionalOnEnabledInstrumentation(module = "logback-appender") + @ConditionalOnClass(ch.qos.logback.classic.LoggerContext.class) + @Configuration + static class LogbackAppenderConfig { + + @Bean + ApplicationListener logbackOtelAppenderInitializer( + OpenTelemetry openTelemetry) { + return event -> { + io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender.install( + openTelemetry); + }; + } + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfiguration.java new file mode 100644 index 000000000000..44114889be6d --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfiguration.java @@ -0,0 +1,38 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.micrometer; + +import io.micrometer.core.instrument.Clock; +import io.micrometer.core.instrument.MeterRegistry; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.micrometer.v1_5.OpenTelemetryMeterRegistry; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; +import org.springframework.boot.metrics.autoconfigure.CompositeMeterRegistryAutoConfiguration; +import org.springframework.boot.metrics.autoconfigure.MetricsAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +@ConditionalOnEnabledInstrumentation(module = "micrometer", enabledByDefault = false) +@AutoConfigureAfter(MetricsAutoConfiguration.class) +@AutoConfigureBefore(CompositeMeterRegistryAutoConfiguration.class) +@ConditionalOnBean(Clock.class) +@ConditionalOnClass(MeterRegistry.class) +@Configuration +public class MicrometerBridgeAutoConfiguration { + + @Bean + MeterRegistry otelMeterRegistry(OpenTelemetry openTelemetry, Clock micrometerClock) { + return OpenTelemetryMeterRegistry.builder(openTelemetry).setClock(micrometerClock).build(); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/mongo/MongoClientInstrumentationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/mongo/MongoClientInstrumentationAutoConfiguration.java new file mode 100644 index 000000000000..ff00cbef37cc --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/mongo/MongoClientInstrumentationAutoConfiguration.java @@ -0,0 +1,42 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.mongo; + +import com.mongodb.MongoClientSettings; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.mongo.v3_1.MongoTelemetry; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.InstrumentationConfigUtil; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.mongodb.autoconfigure.MongoClientSettingsBuilderCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +@ConditionalOnBean(OpenTelemetry.class) +@ConditionalOnClass(MongoClientSettings.class) +@ConditionalOnEnabledInstrumentation(module = "mongo") +@Configuration +public class MongoClientInstrumentationAutoConfiguration { + + @Bean + MongoClientSettingsBuilderCustomizer customizer( + OpenTelemetry openTelemetry, ConfigProperties config) { + return builder -> + builder.addCommandListener( + MongoTelemetry.builder(openTelemetry) + .setStatementSanitizationEnabled( + InstrumentationConfigUtil.isStatementSanitizationEnabled( + config, "otel.instrumentation.mongo.statement-sanitizer.enabled")) + .build() + .newCommandListener()); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2dbcInstrumentationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2dbcInstrumentationAutoConfiguration.java new file mode 100644 index 000000000000..d744b8fe1dc4 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2dbcInstrumentationAutoConfiguration.java @@ -0,0 +1,37 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.r2dbc; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.r2dbc.spi.ConnectionFactory; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +@ConditionalOnBean(OpenTelemetry.class) +@ConditionalOnClass(ConnectionFactory.class) +@ConditionalOnEnabledInstrumentation(module = "r2dbc") +@Configuration(proxyBeanMethods = false) +public class R2dbcInstrumentationAutoConfiguration { + + public R2dbcInstrumentationAutoConfiguration() {} + + @Bean + // static to avoid "is not eligible for getting processed by all BeanPostProcessors" warning + static R2dbcInstrumentingPostProcessor r2dbcInstrumentingPostProcessor( + ObjectProvider openTelemetryProvider, + ObjectProvider configPropertiesProvider) { + return new R2dbcInstrumentingPostProcessor(openTelemetryProvider, configPropertiesProvider); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2dbcInstrumentingPostProcessor.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2dbcInstrumentingPostProcessor.java new file mode 100644 index 000000000000..8ee45bc4faa1 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2dbcInstrumentingPostProcessor.java @@ -0,0 +1,57 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.r2dbc; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.r2dbc.v1_0.internal.shaded.R2dbcTelemetry; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.InstrumentationConfigUtil; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.r2dbc.spi.ConnectionFactory; +import io.r2dbc.spi.ConnectionFactoryOptions; +import org.springframework.aop.scope.ScopedProxyUtils; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.boot.r2dbc.OptionsCapableConnectionFactory; + +class R2dbcInstrumentingPostProcessor implements BeanPostProcessor { + + private final ObjectProvider openTelemetryProvider; + private final ObjectProvider configPropertiesProvider; + + R2dbcInstrumentingPostProcessor( + ObjectProvider openTelemetryProvider, + ObjectProvider configPropertiesProvider) { + this.openTelemetryProvider = openTelemetryProvider; + this.configPropertiesProvider = configPropertiesProvider; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) { + if (bean instanceof ConnectionFactory connectionFactory && !ScopedProxyUtils.isScopedTarget(beanName)) { + return R2dbcTelemetry.builder(openTelemetryProvider.getObject()) + .setStatementSanitizationEnabled( + InstrumentationConfigUtil.isStatementSanitizationEnabled( + configPropertiesProvider.getObject(), + "otel.instrumentation.r2dbc.statement-sanitizer.enabled")) + .build() + .wrapConnectionFactory(connectionFactory, getConnectionFactoryOptions(connectionFactory)); + } + return bean; + } + + private static ConnectionFactoryOptions getConnectionFactoryOptions( + ConnectionFactory connectionFactory) { + OptionsCapableConnectionFactory optionsCapableConnectionFactory = + OptionsCapableConnectionFactory.unwrapFrom(connectionFactory); + if (optionsCapableConnectionFactory != null) { + return optionsCapableConnectionFactory.getOptions(); + } else { + // in practice should never happen + // fall back to empty options; or reconstruct them from the R2dbcProperties + return ConnectionFactoryOptions.builder().build(); + } + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/Java17RuntimeMetricsProvider.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/Java17RuntimeMetricsProvider.java new file mode 100644 index 000000000000..8f258004a853 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/Java17RuntimeMetricsProvider.java @@ -0,0 +1,34 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import io.opentelemetry.instrumentation.runtimemetrics.java17.RuntimeMetrics; +import io.opentelemetry.instrumentation.runtimemetrics.java17.internal.RuntimeMetricsConfigUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Configures runtime metrics collection for Java 17+. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public class Java17RuntimeMetricsProvider implements RuntimeMetricsProvider { + private static final Logger logger = LoggerFactory.getLogger(Java17RuntimeMetricsProvider.class); + + @Override + public int minJavaVersion() { + return 17; + } + + @Override + public AutoCloseable start(OpenTelemetry openTelemetry, InstrumentationConfig config) { + logger.debug("Use runtime metrics instrumentation for Java 17+"); + return RuntimeMetricsConfigUtil.configure(RuntimeMetrics.builder(openTelemetry), config); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/Java8RuntimeMetricsProvider.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/Java8RuntimeMetricsProvider.java new file mode 100644 index 000000000000..fbb10d9e935b --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/Java8RuntimeMetricsProvider.java @@ -0,0 +1,34 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import io.opentelemetry.instrumentation.runtimemetrics.java8.RuntimeMetrics; +import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.RuntimeMetricsConfigUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Configures runtime metrics collection for Java 8. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public class Java8RuntimeMetricsProvider implements RuntimeMetricsProvider { + private static final Logger logger = LoggerFactory.getLogger(Java8RuntimeMetricsProvider.class); + + @Override + public int minJavaVersion() { + return 8; + } + + @Override + public AutoCloseable start(OpenTelemetry openTelemetry, InstrumentationConfig config) { + logger.debug("Use runtime metrics instrumentation for Java 8"); + return RuntimeMetricsConfigUtil.configure(RuntimeMetrics.builder(openTelemetry), config); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/RuntimeMetricsAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/RuntimeMetricsAutoConfiguration.java new file mode 100644 index 000000000000..de4c8cf1fb16 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/RuntimeMetricsAutoConfiguration.java @@ -0,0 +1,65 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import java.util.Comparator; +import java.util.Optional; +import jakarta.annotation.PreDestroy; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.event.EventListener; + +/** + * Configures runtime metrics collection. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +@ConditionalOnEnabledInstrumentation(module = "runtime-telemetry") +@Configuration +public class RuntimeMetricsAutoConfiguration { + + private static final Logger logger = + LoggerFactory.getLogger(RuntimeMetricsAutoConfiguration.class); + + private AutoCloseable closeable; + + @PreDestroy + public void stopMetrics() throws Exception { + if (closeable != null) { + closeable.close(); + } + } + + @EventListener + public void handleApplicationReadyEvent(ApplicationReadyEvent event) { + ConfigurableApplicationContext applicationContext = event.getApplicationContext(); + OpenTelemetry openTelemetry = applicationContext.getBean(OpenTelemetry.class); + ConfigPropertiesBridge config = + new ConfigPropertiesBridge(applicationContext.getBean(ConfigProperties.class)); + + double version = + Math.max(8, Double.parseDouble(System.getProperty("java.specification.version"))); + Optional metricsProvider = + applicationContext.getBeanProvider(RuntimeMetricsProvider.class).stream() + .sorted(Comparator.comparing(RuntimeMetricsProvider::minJavaVersion).reversed()) + .filter(provider -> provider.minJavaVersion() <= version) + .findFirst(); + + if (metricsProvider.isPresent()) { + this.closeable = metricsProvider.get().start(openTelemetry, config); + } else { + logger.debug("No runtime metrics instrumentation available for Java {}", version); + } + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/RuntimeMetricsBeanRegistrationExcludeFilter.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/RuntimeMetricsBeanRegistrationExcludeFilter.java new file mode 100644 index 000000000000..25b0cc5e378b --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/RuntimeMetricsBeanRegistrationExcludeFilter.java @@ -0,0 +1,26 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics; + +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics.Java17RuntimeMetricsProvider; +import org.springframework.beans.factory.aot.BeanRegistrationExcludeFilter; +import org.springframework.beans.factory.support.RegisteredBean; + +/** + * Configures runtime metrics collection for Java 17+. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public class RuntimeMetricsBeanRegistrationExcludeFilter implements BeanRegistrationExcludeFilter { + @Override + public boolean isExcludedFromAotProcessing(RegisteredBean registeredBean) { + // The JFR-based runtime metric code is excluded from the Spring AOT processing step. + // That way, this code is not included in a Spring native image application. + + return Java17RuntimeMetricsProvider.class.getName().equals(registeredBean.getBeanName()); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/RuntimeMetricsProvider.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/RuntimeMetricsProvider.java new file mode 100644 index 000000000000..8549f9394e61 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/RuntimeMetricsProvider.java @@ -0,0 +1,23 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import javax.annotation.Nullable; + +/** + * Configures runtime metrics collection. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public interface RuntimeMetricsProvider { + int minJavaVersion(); + + @Nullable + AutoCloseable start(OpenTelemetry openTelemetry, InstrumentationConfig config); +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SpringSchedulingInstrumentationAspect.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SpringSchedulingInstrumentationAspect.java new file mode 100644 index 000000000000..5dfec2280e8e --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SpringSchedulingInstrumentationAspect.java @@ -0,0 +1,92 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.scheduling; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeAttributesExtractor; +import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeAttributesGetter; +import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeSpanNameExtractor; +import io.opentelemetry.instrumentation.api.incubator.semconv.util.ClassAndMethod; +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.aop.framework.AopProxyUtils; + +/** + * Spring Scheduling instrumentation aop. + * + *

This aspect would intercept all methods annotated with {@link + * org.springframework.scheduling.annotation.Scheduled} and {@link + * org.springframework.scheduling.annotation.Schedules}. + * + *

Normally this would cover most of the Spring Scheduling use cases, but if you register jobs + * programmatically such as {@link + * org.springframework.scheduling.config.ScheduledTaskRegistrar#addCronTask}, this aspect would not + * cover them. You may use {@link io.opentelemetry.instrumentation.annotations.WithSpan} to trace + * these jobs manually. + */ +@Aspect +final class SpringSchedulingInstrumentationAspect { + private static final String INSTRUMENTATION_NAME = "io.opentelemetry.spring-boot-autoconfigure"; + private final Instrumenter instrumenter; + + public SpringSchedulingInstrumentationAspect( + OpenTelemetry openTelemetry, ConfigProperties configProperties) { + CodeAttributesGetter codedAttributesGetter = + ClassAndMethod.codeAttributesGetter(); + InstrumenterBuilder builder = + Instrumenter.builder( + openTelemetry, + INSTRUMENTATION_NAME, + CodeSpanNameExtractor.create(codedAttributesGetter)) + .addAttributesExtractor(CodeAttributesExtractor.create(codedAttributesGetter)); + if (configProperties.getBoolean( + "otel.instrumentation.spring-scheduling.experimental-span-attributes", false)) { + builder.addAttributesExtractor( + AttributesExtractor.constant(AttributeKey.stringKey("job.system"), "spring_scheduling")); + } + instrumenter = builder.buildInstrumenter(); + } + + @Pointcut( + "@annotation(org.springframework.scheduling.annotation.Scheduled)" + + "|| @annotation(org.springframework.scheduling.annotation.Schedules)") + public void pointcut() { + // ignored + } + + @Around("pointcut()") + public Object execution(ProceedingJoinPoint joinPoint) throws Throwable { + Context parent = Context.current(); + ClassAndMethod request = + ClassAndMethod.create( + AopProxyUtils.ultimateTargetClass(joinPoint.getTarget()), + ((MethodSignature) joinPoint.getSignature()).getMethod().getName()); + if (!instrumenter.shouldStart(parent, request)) { + return joinPoint.proceed(); + } + Context context = instrumenter.start(parent, request); + + Object object; + try (Scope ignored = context.makeCurrent()) { + object = joinPoint.proceed(); + } catch (Throwable t) { + instrumenter.end(context, request, null, t); + throw t; + } + instrumenter.end(context, request, object, null); + return object; + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SpringSchedulingInstrumentationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SpringSchedulingInstrumentationAutoConfiguration.java new file mode 100644 index 000000000000..8135bead9c0b --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SpringSchedulingInstrumentationAutoConfiguration.java @@ -0,0 +1,34 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.scheduling; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.Scheduled; + +/** + * Configures an aspect for tracing. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +@ConditionalOnBean(OpenTelemetry.class) +@ConditionalOnEnabledInstrumentation(module = "spring-scheduling") +@ConditionalOnClass({Scheduled.class, Aspect.class}) +@Configuration +class SpringSchedulingInstrumentationAutoConfiguration { + @Bean + SpringSchedulingInstrumentationAspect springSchedulingInstrumentationAspect( + OpenTelemetry openTelemetry, ConfigProperties configProperties) { + return new SpringSchedulingInstrumentationAspect(openTelemetry, configProperties); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestClientBeanPostProcessor.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestClientBeanPostProcessor.java new file mode 100644 index 000000000000..8ebefdd655f6 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestClientBeanPostProcessor.java @@ -0,0 +1,66 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.web; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.InstrumentationConfigUtil; +import io.opentelemetry.instrumentation.spring.web.v3_1.SpringWebTelemetry; +import io.opentelemetry.instrumentation.spring.web.v3_1.internal.WebTelemetryUtil; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.web.client.RestClient; + +final class RestClientBeanPostProcessor implements BeanPostProcessor { + + private final ObjectProvider openTelemetryProvider; + private final ObjectProvider configPropertiesProvider; + + public RestClientBeanPostProcessor( + ObjectProvider openTelemetryProvider, + ObjectProvider configPropertiesProvider) { + this.openTelemetryProvider = openTelemetryProvider; + this.configPropertiesProvider = configPropertiesProvider; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) { + if (bean instanceof RestClient restClient) { + return addRestClientInterceptorIfNotPresent( + restClient, openTelemetryProvider.getObject(), configPropertiesProvider.getObject()); + } + return bean; + } + + private static RestClient addRestClientInterceptorIfNotPresent( + RestClient restClient, OpenTelemetry openTelemetry, ConfigProperties config) { + ClientHttpRequestInterceptor instrumentationInterceptor = getInterceptor(openTelemetry, config); + + return restClient + .mutate() + .requestInterceptors( + interceptors -> { + if (interceptors.stream() + .noneMatch( + interceptor -> + interceptor.getClass() == instrumentationInterceptor.getClass())) { + interceptors.add(0, instrumentationInterceptor); + } + }) + .build(); + } + + static ClientHttpRequestInterceptor getInterceptor( + OpenTelemetry openTelemetry, ConfigProperties config) { + return InstrumentationConfigUtil.configureClientBuilder( + config, + SpringWebTelemetry.builder(openTelemetry), + WebTelemetryUtil.getBuilderExtractor()) + .build() + .newInterceptor(); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestClientInstrumentationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestClientInstrumentationAutoConfiguration.java new file mode 100644 index 000000000000..a96ae353fc85 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestClientInstrumentationAutoConfiguration.java @@ -0,0 +1,50 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.web; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; +import org.springframework.boot.restclient.RestClientCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestClient; + +/** + * Configures {@link RestClient} for tracing. + * + *

Adds OpenTelemetry instrumentation to {@link RestClient} beans after initialization. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +@ConditionalOnEnabledInstrumentation(module = "spring-web") +@ConditionalOnClass(RestClient.class) +@AutoConfiguration(after = RestClientAutoConfiguration.class) +@Configuration +public class RestClientInstrumentationAutoConfiguration { + + @Bean + static RestClientBeanPostProcessor otelRestClientBeanPostProcessor( + ObjectProvider openTelemetryProvider, + ObjectProvider configPropertiesProvider) { + return new RestClientBeanPostProcessor(openTelemetryProvider, configPropertiesProvider); + } + + @Bean + RestClientCustomizer otelRestClientCustomizer( + ObjectProvider openTelemetryProvider, + ObjectProvider configPropertiesProvider) { + return builder -> + builder.requestInterceptor( + RestClientBeanPostProcessor.getInterceptor( + openTelemetryProvider.getObject(), configPropertiesProvider.getObject())); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestTemplateBeanPostProcessor.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestTemplateBeanPostProcessor.java new file mode 100644 index 000000000000..f294db9a27eb --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestTemplateBeanPostProcessor.java @@ -0,0 +1,38 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.web; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.web.client.RestTemplate; + +final class RestTemplateBeanPostProcessor implements BeanPostProcessor { + + private final ObjectProvider openTelemetryProvider; + + private final ObjectProvider configPropertiesProvider; + + RestTemplateBeanPostProcessor( + ObjectProvider openTelemetryProvider, + ObjectProvider configPropertiesProvider) { + this.openTelemetryProvider = openTelemetryProvider; + this.configPropertiesProvider = configPropertiesProvider; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) { + if (!(bean instanceof RestTemplate restTemplate)) { + return bean; + } + + return RestTemplateInstrumentation.addIfNotPresent( + restTemplate, + openTelemetryProvider.getObject(), + configPropertiesProvider.getObject()); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestTemplateInstrumentation.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestTemplateInstrumentation.java new file mode 100644 index 000000000000..9465c534d0ce --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestTemplateInstrumentation.java @@ -0,0 +1,42 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.web; + +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.InstrumentationConfigUtil; +import io.opentelemetry.instrumentation.spring.web.v3_1.SpringWebTelemetry; +import io.opentelemetry.instrumentation.spring.web.v3_1.internal.WebTelemetryUtil; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import java.util.List; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.web.client.RestTemplate; + +class RestTemplateInstrumentation { + + private RestTemplateInstrumentation() {} + + @CanIgnoreReturnValue + static RestTemplate addIfNotPresent( + RestTemplate restTemplate, OpenTelemetry openTelemetry, ConfigProperties config) { + + ClientHttpRequestInterceptor instrumentationInterceptor = + InstrumentationConfigUtil.configureClientBuilder( + config, + SpringWebTelemetry.builder(openTelemetry), + WebTelemetryUtil.getBuilderExtractor()) + .build() + .newInterceptor(); + + List restTemplateInterceptors = restTemplate.getInterceptors(); + if (restTemplateInterceptors.stream() + .noneMatch( + interceptor -> interceptor.getClass() == instrumentationInterceptor.getClass())) { + restTemplateInterceptors.add(0, instrumentationInterceptor); + } + return restTemplate; + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfiguration.java new file mode 100644 index 000000000000..f740849baebf --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfiguration.java @@ -0,0 +1,49 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.web; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.restclient.RestTemplateCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +/** + * Configures {@link RestTemplate} for tracing. + * + *

Adds OpenTelemetry instrumentation to RestTemplate beans after initialization. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +@ConditionalOnEnabledInstrumentation(module = "spring-web") +@ConditionalOnClass(RestTemplate.class) +@Configuration +public class SpringWebInstrumentationAutoConfiguration { + + public SpringWebInstrumentationAutoConfiguration() {} + + // static to avoid "is not eligible for getting processed by all BeanPostProcessors" warning + @Bean + static RestTemplateBeanPostProcessor otelRestTemplateBeanPostProcessor( + ObjectProvider openTelemetryProvider, + ObjectProvider configPropertiesProvider) { + return new RestTemplateBeanPostProcessor(openTelemetryProvider, configPropertiesProvider); + } + + @Bean + RestTemplateCustomizer otelRestTemplateCustomizer( + ObjectProvider openTelemetryProvider, + ObjectProvider configPropertiesProvider) { + return restTemplate -> + RestTemplateInstrumentation.addIfNotPresent( + restTemplate, openTelemetryProvider.getObject(), configPropertiesProvider.getObject()); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webflux/SpringWebfluxInstrumentationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webflux/SpringWebfluxInstrumentationAutoConfiguration.java new file mode 100644 index 000000000000..ff6d8f9a5d0b --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webflux/SpringWebfluxInstrumentationAutoConfiguration.java @@ -0,0 +1,46 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.webflux; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.server.WebFilter; + +/** + * Configures {@link WebClient} for tracing. + * + *

Adds OpenTelemetry instrumentation to WebClient beans after initialization. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +@ConditionalOnEnabledInstrumentation(module = "spring-webflux") +@ConditionalOnClass(WebClient.class) +@Configuration +public class SpringWebfluxInstrumentationAutoConfiguration { + + public SpringWebfluxInstrumentationAutoConfiguration() {} + + // static to avoid "is not eligible for getting processed by all BeanPostProcessors" warning + @Bean + static WebClientBeanPostProcessor otelWebClientBeanPostProcessor( + ObjectProvider openTelemetryProvider, + ObjectProvider configPropertiesProvider) { + return new WebClientBeanPostProcessor(openTelemetryProvider, configPropertiesProvider); + } + + @Bean + WebFilter telemetryFilter(OpenTelemetry openTelemetry, ConfigProperties config) { + return WebClientBeanPostProcessor.getWebfluxServerTelemetry(openTelemetry, config) + .createWebFilterAndRegisterReactorHook(); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webflux/WebClientBeanPostProcessor.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webflux/WebClientBeanPostProcessor.java new file mode 100644 index 000000000000..9f34e9e7a351 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webflux/WebClientBeanPostProcessor.java @@ -0,0 +1,69 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.webflux; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.InstrumentationConfigUtil; +import io.opentelemetry.instrumentation.spring.webflux.v5_3.SpringWebfluxClientTelemetry; +import io.opentelemetry.instrumentation.spring.webflux.v5_3.SpringWebfluxServerTelemetry; +import io.opentelemetry.instrumentation.spring.webflux.v5_3.internal.SpringWebfluxBuilderUtil; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.web.reactive.function.client.WebClient; + +/** + * Inspired by Spring + * Cloud Sleuth. + */ +final class WebClientBeanPostProcessor implements BeanPostProcessor { + + private final ObjectProvider openTelemetryProvider; + private final ObjectProvider configPropertiesProvider; + + WebClientBeanPostProcessor( + ObjectProvider openTelemetryProvider, + ObjectProvider configPropertiesProvider) { + this.openTelemetryProvider = openTelemetryProvider; + this.configPropertiesProvider = configPropertiesProvider; + } + + static SpringWebfluxClientTelemetry getWebfluxClientTelemetry( + OpenTelemetry openTelemetry, ConfigProperties config) { + return InstrumentationConfigUtil.configureClientBuilder( + config, + SpringWebfluxClientTelemetry.builder(openTelemetry), + SpringWebfluxBuilderUtil.getClientBuilderExtractor()) + .build(); + } + + static SpringWebfluxServerTelemetry getWebfluxServerTelemetry( + OpenTelemetry openTelemetry, ConfigProperties config) { + return InstrumentationConfigUtil.configureServerBuilder( + config, + SpringWebfluxServerTelemetry.builder(openTelemetry), + SpringWebfluxBuilderUtil.getServerBuilderExtractor()) + .build(); + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) { + if (bean instanceof WebClient webClient) { + return wrapBuilder(webClient.mutate()).build(); + } else if (bean instanceof WebClient.Builder webClientBuilder) { + return wrapBuilder(webClientBuilder); + } + return bean; + } + + private WebClient.Builder wrapBuilder(WebClient.Builder webClientBuilder) { + SpringWebfluxClientTelemetry instrumentation = + getWebfluxClientTelemetry( + openTelemetryProvider.getObject(), configPropertiesProvider.getObject()); + return webClientBuilder.filters(instrumentation::addFilter); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webmvc/SpringWebMvc7InstrumentationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webmvc/SpringWebMvc7InstrumentationAutoConfiguration.java new file mode 100644 index 000000000000..03a266390db8 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webmvc/SpringWebMvc7InstrumentationAutoConfiguration.java @@ -0,0 +1,39 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.webmvc; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.InstrumentationConfigUtil; +import io.opentelemetry.instrumentation.spring.webmvc.v6_0.SpringWebMvcTelemetry; +import io.opentelemetry.instrumentation.spring.webmvc.v6_0.internal.SpringMvcBuilderUtil; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import jakarta.servlet.Filter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.web.servlet.DispatcherServlet; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +@ConditionalOnEnabledInstrumentation(module = "spring-webmvc") +@ConditionalOnClass({Filter.class, OncePerRequestFilter.class, DispatcherServlet.class}) +@Configuration +public class SpringWebMvc7InstrumentationAutoConfiguration { + + @Bean + Filter otelWebMvcFilter(OpenTelemetry openTelemetry, ConfigProperties config) { + return InstrumentationConfigUtil.configureServerBuilder( + config, + SpringWebMvcTelemetry.builder(openTelemetry), + SpringMvcBuilderUtil.getBuilderExtractor()) + .build() + .createServletFilter(); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/ConfigPropertiesBridge.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/ConfigPropertiesBridge.java new file mode 100644 index 000000000000..200bd30c73d6 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/ConfigPropertiesBridge.java @@ -0,0 +1,119 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties; + +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; +import java.time.Duration; +import java.util.List; +import java.util.Map; +import javax.annotation.Nullable; + +/** + * Support for {@link ConfigProperties} in {@link InstrumentationConfig}. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public final class ConfigPropertiesBridge implements InstrumentationConfig { + + private final ConfigProperties configProperties; + + public ConfigPropertiesBridge(ConfigProperties configProperties) { + this.configProperties = configProperties; + } + + @Nullable + @Override + public String getString(String name) { + try { + return configProperties.getString(name); + } catch (ConfigurationException ignored) { + return null; + } + } + + @Override + public String getString(String name, String defaultValue) { + try { + return configProperties.getString(name, defaultValue); + } catch (ConfigurationException ignored) { + return defaultValue; + } + } + + @Override + public boolean getBoolean(String name, boolean defaultValue) { + try { + return configProperties.getBoolean(name, defaultValue); + } catch (ConfigurationException ignored) { + return defaultValue; + } + } + + @Override + public int getInt(String name, int defaultValue) { + try { + return configProperties.getInt(name, defaultValue); + } catch (ConfigurationException ignored) { + return defaultValue; + } + } + + @Override + public long getLong(String name, long defaultValue) { + try { + return configProperties.getLong(name, defaultValue); + } catch (ConfigurationException ignored) { + return defaultValue; + } + } + + @Override + public double getDouble(String name, double defaultValue) { + try { + return configProperties.getDouble(name, defaultValue); + } catch (ConfigurationException ignored) { + return defaultValue; + } + } + + @Override + public Duration getDuration(String name, Duration defaultValue) { + try { + return configProperties.getDuration(name, defaultValue); + } catch (ConfigurationException ignored) { + return defaultValue; + } + } + + @Override + public List getList(String name, List defaultValue) { + try { + return configProperties.getList(name, defaultValue); + } catch (ConfigurationException ignored) { + return defaultValue; + } + } + + @Override + public Map getMap(String name, Map defaultValue) { + try { + return configProperties.getMap(name, defaultValue); + } catch (ConfigurationException ignored) { + return defaultValue; + } + } + + @Nullable + @Override + public DeclarativeConfigProperties getDeclarativeConfig(String instrumentationName) { + // create a spring boot bridge for DeclarativeConfigProperties + return null; + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/InstrumentationConfigUtil.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/InstrumentationConfigUtil.java new file mode 100644 index 000000000000..fb5e582bfaa5 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/InstrumentationConfigUtil.java @@ -0,0 +1,48 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties; + +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import io.opentelemetry.instrumentation.api.incubator.builder.internal.DefaultHttpClientInstrumenterBuilder; +import io.opentelemetry.instrumentation.api.incubator.builder.internal.DefaultHttpServerInstrumenterBuilder; +import io.opentelemetry.instrumentation.api.incubator.config.internal.CommonConfig; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import java.util.function.Function; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public final class InstrumentationConfigUtil { + private InstrumentationConfigUtil() {} + + @CanIgnoreReturnValue + public static T configureClientBuilder( + ConfigProperties config, + T builder, + Function> getBuilder) { + getBuilder.apply(builder).configure(getConfig(config)); + return builder; + } + + @CanIgnoreReturnValue + public static T configureServerBuilder( + ConfigProperties config, + T builder, + Function> getBuilder) { + getBuilder.apply(builder).configure(getConfig(config)); + return builder; + } + + private static CommonConfig getConfig(ConfigProperties config) { + return new CommonConfig(new ConfigPropertiesBridge(config)); + } + + public static boolean isStatementSanitizationEnabled(ConfigProperties config, String key) { + return config.getBoolean( + key, config.getBoolean("otel.instrumentation.common.db-statement-sanitizer.enabled", true)); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/OtelResourceProperties.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/OtelResourceProperties.java new file mode 100644 index 000000000000..0bb967079537 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/OtelResourceProperties.java @@ -0,0 +1,27 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties; + +import java.util.Collections; +import java.util.Map; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +@ConfigurationProperties(prefix = "otel.resource") +public class OtelResourceProperties { + private Map attributes = Collections.emptyMap(); + + public Map getAttributes() { + return attributes; + } + + public void setAttributes(Map attributes) { + this.attributes = attributes; + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/OtelSpringProperties.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/OtelSpringProperties.java new file mode 100644 index 000000000000..4f50d260f7ca --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/OtelSpringProperties.java @@ -0,0 +1,433 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties; + +import java.util.Collections; +import java.util.List; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +// yaml lists only work if you create a @ConfigurationProperties object +@ConfigurationProperties(prefix = "otel") +public final class OtelSpringProperties { + + /** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ + public static final class Java { + /** + * This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ + public static final class Enabled { + /** + * This class is internal and is hence not for public use. Its APIs are unstable and can + * change at any time. + */ + public static final class Resource { + private List providers = Collections.emptyList(); + + public List getProviders() { + return providers; + } + + public void setProviders(List providers) { + this.providers = providers; + } + } + + private Enabled.Resource resource = new Enabled.Resource(); + + public Enabled.Resource getResource() { + return resource; + } + + public void setResource(Enabled.Resource resource) { + this.resource = resource; + } + } + + /** + * This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ + public static final class Disabled { + /** + * This class is internal and is hence not for public use. Its APIs are unstable and can + * change at any time. + */ + public static final class Resource { + private List providers = Collections.emptyList(); + + public List getProviders() { + return providers; + } + + public void setProviders(List providers) { + this.providers = providers; + } + } + + private Disabled.Resource resource = new Disabled.Resource(); + + public Disabled.Resource getResource() { + return resource; + } + + public void setResource(Disabled.Resource resource) { + this.resource = resource; + } + } + + private Enabled enabled = new Enabled(); + private Java.Disabled disabled = new Java.Disabled(); + + public Enabled getEnabled() { + return enabled; + } + + public void setEnabled(Enabled enabled) { + this.enabled = enabled; + } + + public Java.Disabled getDisabled() { + return disabled; + } + + public void setDisabled(Java.Disabled disabled) { + this.disabled = disabled; + } + } + + /** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ + public static final class Experimental { + /** + * This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ + public static final class Metrics { + /** + * This class is internal and is hence not for public use. Its APIs are unstable and can + * change at any time. + */ + public static final class View { + private List config = Collections.emptyList(); + + public List getConfig() { + return config; + } + + public void setConfig(List config) { + this.config = config; + } + } + + private View view = new View(); + + public View getView() { + return view; + } + + public void setView(View view) { + this.view = view; + } + } + + /** + * This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ + public static final class Resource { + /** + * This class is internal and is hence not for public use. Its APIs are unstable and can + * change at any time. + */ + public static final class Disabled { + private List keys = Collections.emptyList(); + + public List getKeys() { + return keys; + } + + public void setKeys(List keys) { + this.keys = keys; + } + } + + private Resource.Disabled disabled = new Resource.Disabled(); + + public Resource.Disabled getDisabled() { + return disabled; + } + + public void setDisabled(Resource.Disabled disabled) { + this.disabled = disabled; + } + } + + private Metrics metrics = new Metrics(); + private Resource resource = new Resource(); + + public Metrics getMetrics() { + return metrics; + } + + public void setMetrics(Metrics metrics) { + this.metrics = metrics; + } + + public Resource getResource() { + return resource; + } + + public void setResource(Resource resource) { + this.resource = resource; + } + } + + /** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ + public static final class HasExporters { + private List exporter = Collections.emptyList(); + + public List getExporter() { + return exporter; + } + + public void setExporter(List exporter) { + this.exporter = exporter; + } + } + + /** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ + public static final class Instrumentation { + /** + * This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ + public static final class Http { + /** + * This class is internal and is hence not for public use. Its APIs are unstable and can + * change at any time. + */ + public static final class Client { + private List captureRequestHeaders = Collections.emptyList(); + private List captureResponseHeaders = Collections.emptyList(); + + public List getCaptureRequestHeaders() { + return captureRequestHeaders; + } + + public void setCaptureRequestHeaders(List captureRequestHeaders) { + this.captureRequestHeaders = captureRequestHeaders; + } + + public List getCaptureResponseHeaders() { + return captureResponseHeaders; + } + + public void setCaptureResponseHeaders(List captureResponseHeaders) { + this.captureResponseHeaders = captureResponseHeaders; + } + } + + /** + * This class is internal and is hence not for public use. Its APIs are unstable and can + * change at any time. + */ + public static final class Server { + private List captureRequestHeaders = Collections.emptyList(); + private List captureResponseHeaders = Collections.emptyList(); + + public List getCaptureRequestHeaders() { + return captureRequestHeaders; + } + + public void setCaptureRequestHeaders(List captureRequestHeaders) { + this.captureRequestHeaders = captureRequestHeaders; + } + + public List getCaptureResponseHeaders() { + return captureResponseHeaders; + } + + public void setCaptureResponseHeaders(List captureResponseHeaders) { + this.captureResponseHeaders = captureResponseHeaders; + } + } + + private Client client = new Client(); + + private Server server = new Server(); + + private List knownMethods = Collections.emptyList(); + + public Client getClient() { + return client; + } + + public void setClient(Client client) { + this.client = client; + } + + public Server getServer() { + return server; + } + + public void setServer(Server server) { + this.server = server; + } + + public List getKnownMethods() { + return knownMethods; + } + + public void setKnownMethods(List knownMethods) { + this.knownMethods = knownMethods; + } + } + + private Http http = new Http(); + + public Http getHttp() { + return http; + } + + public void setHttp(Http http) { + this.http = http; + } + } + + private List propagators = Collections.emptyList(); + + private Java java = new Java(); + + private Experimental experimental = new Experimental(); + + private HasExporters logs = new HasExporters(); + + private HasExporters metrics = new HasExporters(); + + private HasExporters traces = new HasExporters(); + + private Instrumentation instrumentation = new Instrumentation(); + + public List getPropagators() { + return propagators; + } + + public void setPropagators(List propagators) { + this.propagators = propagators; + } + + public Java getJava() { + return java; + } + + public void setJava(Java java) { + this.java = java; + } + + public Experimental getExperimental() { + return experimental; + } + + public void setExperimental(Experimental experimental) { + this.experimental = experimental; + } + + public HasExporters getLogs() { + return logs; + } + + public void setLogs(HasExporters logs) { + this.logs = logs; + } + + public HasExporters getMetrics() { + return metrics; + } + + public void setMetrics(HasExporters metrics) { + this.metrics = metrics; + } + + public HasExporters getTraces() { + return traces; + } + + public void setTraces(HasExporters traces) { + this.traces = traces; + } + + public Instrumentation getInstrumentation() { + return instrumentation; + } + + public void setInstrumentation(Instrumentation instrumentation) { + this.instrumentation = instrumentation; + } + + public List getJavaEnabledResourceProviders() { + return java.getEnabled().getResource().getProviders(); + } + + public List getJavaDisabledResourceProviders() { + return java.getDisabled().getResource().getProviders(); + } + + public List getExperimentalMetricsViewConfig() { + return experimental.getMetrics().getView().getConfig(); + } + + public List getExperimentalResourceDisabledKeys() { + return experimental.getResource().getDisabled().getKeys(); + } + + public List getLogsExporter() { + return logs.getExporter(); + } + + public List getMetricsExporter() { + return metrics.getExporter(); + } + + public List getTracesExporter() { + return traces.getExporter(); + } + + public List getHttpClientCaptureRequestHeaders() { + return instrumentation.getHttp().getClient().getCaptureRequestHeaders(); + } + + public List getHttpClientCaptureResponseHeaders() { + return instrumentation.getHttp().getClient().getCaptureResponseHeaders(); + } + + public List getHttpServerCaptureRequestHeaders() { + return instrumentation.getHttp().getServer().getCaptureRequestHeaders(); + } + + public List getHttpServerCaptureResponseHeaders() { + return instrumentation.getHttp().getServer().getCaptureResponseHeaders(); + } + + public List getHttpKnownMethods() { + return instrumentation.getHttp().getKnownMethods(); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/OtlpExporterProperties.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/OtlpExporterProperties.java new file mode 100644 index 000000000000..b159d10b80f3 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/OtlpExporterProperties.java @@ -0,0 +1,52 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties; + +import java.util.HashMap; +import java.util.Map; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +@ConfigurationProperties(prefix = "otel.exporter.otlp") +public final class OtlpExporterProperties { + + private final Map headers = new HashMap<>(); + + private final SignalProperties traces = new SignalProperties(); + private final SignalProperties metrics = new SignalProperties(); + private final SignalProperties logs = new SignalProperties(); + + public Map getHeaders() { + return headers; + } + + public SignalProperties getTraces() { + return traces; + } + + public SignalProperties getMetrics() { + return metrics; + } + + public SignalProperties getLogs() { + return logs; + } + + /** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ + public static class SignalProperties { + private final Map headers = new HashMap<>(); + + public Map getHeaders() { + return headers; + } + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigProperties.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigProperties.java new file mode 100644 index 000000000000..1cd49ddaac1b --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigProperties.java @@ -0,0 +1,275 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties; + +import io.opentelemetry.api.internal.ConfigUtil; +import io.opentelemetry.exporter.otlp.internal.OtlpConfigUtil; +import io.opentelemetry.instrumentation.resources.ResourceProviderPropertiesCustomizer; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; +import java.time.Duration; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.annotation.Nullable; +import org.springframework.core.env.Environment; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public class SpringConfigProperties implements ConfigProperties { + private final Environment environment; + + private final ExpressionParser parser; + private final OtlpExporterProperties otlpExporterProperties; + private final OtelResourceProperties resourceProperties; + private final ConfigProperties otelSdkProperties; + private final ConfigProperties customizedListProperties; + private final Map> listPropertyValues; + + static final String DISABLED_KEY = "otel.java.disabled.resource.providers"; + static final String ENABLED_KEY = "otel.java.enabled.resource.providers"; + + public SpringConfigProperties( + Environment environment, + ExpressionParser parser, + OtlpExporterProperties otlpExporterProperties, + OtelResourceProperties resourceProperties, + OtelSpringProperties otelSpringProperties, + ConfigProperties otelSdkProperties) { + this.environment = environment; + this.parser = parser; + this.otlpExporterProperties = otlpExporterProperties; + this.resourceProperties = resourceProperties; + this.otelSdkProperties = otelSdkProperties; + this.customizedListProperties = + createCustomizedListProperties(otelSdkProperties, otelSpringProperties); + + listPropertyValues = createListPropertyValues(otelSpringProperties); + } + + private static Map> createListPropertyValues( + OtelSpringProperties otelSpringProperties) { + Map> values = new HashMap<>(); + + // SDK + values.put(ENABLED_KEY, otelSpringProperties.getJavaEnabledResourceProviders()); + values.put(DISABLED_KEY, otelSpringProperties.getJavaDisabledResourceProviders()); + values.put( + "otel.experimental.metrics.view.config", + otelSpringProperties.getExperimentalMetricsViewConfig()); + values.put( + "otel.experimental.resource.disabled.keys", + otelSpringProperties.getExperimentalResourceDisabledKeys()); + values.put("otel.propagators", otelSpringProperties.getPropagators()); + + // exporters + values.put("otel.logs.exporter", otelSpringProperties.getLogsExporter()); + values.put("otel.metrics.exporter", otelSpringProperties.getMetricsExporter()); + values.put("otel.traces.exporter", otelSpringProperties.getTracesExporter()); + + // instrumentations + values.put( + "otel.instrumentation.http.client.capture-request-headers", + otelSpringProperties.getHttpClientCaptureRequestHeaders()); + values.put( + "otel.instrumentation.http.client.capture-response-headers", + otelSpringProperties.getHttpClientCaptureResponseHeaders()); + values.put( + "otel.instrumentation.http.server.capture-request-headers", + otelSpringProperties.getHttpServerCaptureRequestHeaders()); + values.put( + "otel.instrumentation.http.server.capture-response-headers", + otelSpringProperties.getHttpServerCaptureResponseHeaders()); + values.put( + "otel.instrumentation.http.known-methods", otelSpringProperties.getHttpKnownMethods()); + + return values; + } + + private static Map createMapForListProperty( + String key, List springList, ConfigProperties configProperties) { + if (!springList.isEmpty()) { + return Collections.singletonMap(key, String.join(",", springList)); + } else { + String otelList = configProperties.getString(key); + if (otelList != null) { + return Collections.singletonMap(key, otelList); + } + } + return Collections.emptyMap(); + } + + private static ConfigProperties createCustomizedListProperties( + ConfigProperties configProperties, OtelSpringProperties otelSpringProperties) { + // io.opentelemetry.instrumentation.resources.ResourceProviderPropertiesCustomizer + // has already been applied before this point, so we have to apply the same logic here + // the logic is implemented here: + // https://github.com/open-telemetry/opentelemetry-java/blob/325822ce8527b83a09274c86a5123a214db80c1d/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkBuilder.java#L634-L641 + // ResourceProviderPropertiesCustomizer gets applied by "propertiesCustomizers" + // and spring properties by "configPropertiesCustomizer", which is later + Map map = + new HashMap<>( + createMapForListProperty( + ENABLED_KEY, + otelSpringProperties.getJavaEnabledResourceProviders(), + configProperties)); + map.putAll( + createMapForListProperty( + DISABLED_KEY, + otelSpringProperties.getJavaDisabledResourceProviders(), + configProperties)); + + return DefaultConfigProperties.createFromMap( + new ResourceProviderPropertiesCustomizer() + .customize(DefaultConfigProperties.createFromMap(map))); + } + + // visible for testing + public static ConfigProperties create( + Environment env, + OtlpExporterProperties otlpExporterProperties, + OtelResourceProperties resourceProperties, + OtelSpringProperties otelSpringProperties, + ConfigProperties fallback) { + return new SpringConfigProperties( + env, + new SpelExpressionParser(), + otlpExporterProperties, + resourceProperties, + otelSpringProperties, + fallback); + } + + @Nullable + @Override + public String getString(String name) { + String normalizedName = ConfigUtil.normalizeEnvironmentVariableKey(name); + String value = environment.getProperty(normalizedName, String.class); + if (value == null && normalizedName.equals("otel.exporter.otlp.protocol")) { + // SDK autoconfigure module defaults to `grpc`, but this module aligns with recommendation + // in specification to default to `http/protobuf` + return OtlpConfigUtil.PROTOCOL_HTTP_PROTOBUF; + } + return or(value, otelSdkProperties.getString(name)); + } + + @Nullable + @Override + public Boolean getBoolean(String name) { + return or( + environment.getProperty(ConfigUtil.normalizeEnvironmentVariableKey(name), Boolean.class), + otelSdkProperties.getBoolean(name)); + } + + @Nullable + @Override + public Integer getInt(String name) { + return or( + environment.getProperty(ConfigUtil.normalizeEnvironmentVariableKey(name), Integer.class), + otelSdkProperties.getInt(name)); + } + + @Nullable + @Override + public Long getLong(String name) { + return or( + environment.getProperty(ConfigUtil.normalizeEnvironmentVariableKey(name), Long.class), + otelSdkProperties.getLong(name)); + } + + @Nullable + @Override + public Double getDouble(String name) { + return or( + environment.getProperty(ConfigUtil.normalizeEnvironmentVariableKey(name), Double.class), + otelSdkProperties.getDouble(name)); + } + + @SuppressWarnings("unchecked") + @Override + public List getList(String name) { + + String normalizedName = ConfigUtil.normalizeEnvironmentVariableKey(name); + + List list = listPropertyValues.get(normalizedName); + if (list != null) { + List c = customizedListProperties.getList(name); + if (!c.isEmpty()) { + return c; + } + if (!list.isEmpty()) { + return list; + } + } + + return or(environment.getProperty(normalizedName, List.class), otelSdkProperties.getList(name)); + } + + @Nullable + @Override + public Duration getDuration(String name) { + String value = getString(name); + if (value == null) { + return otelSdkProperties.getDuration(name); + } + return DefaultConfigProperties.createFromMap(Collections.singletonMap(name, value)) + .getDuration(name); + } + + @SuppressWarnings({ "unchecked", "StatementSwitchToExpressionSwitch" }) + @Override + public Map getMap(String name) { + Map otelSdkMap = otelSdkProperties.getMap(name); + + String normalizedName = ConfigUtil.normalizeEnvironmentVariableKey(name); + // maps from config properties are not supported by Environment, so we have to fake it + switch (normalizedName) { + case "otel.resource.attributes": + return mergeWithOtel(resourceProperties.getAttributes(), otelSdkMap); + case "otel.exporter.otlp.headers": + return mergeWithOtel(otlpExporterProperties.getHeaders(), otelSdkMap); + case "otel.exporter.otlp.logs.headers": + return mergeWithOtel(otlpExporterProperties.getLogs().getHeaders(), otelSdkMap); + case "otel.exporter.otlp.metrics.headers": + return mergeWithOtel(otlpExporterProperties.getMetrics().getHeaders(), otelSdkMap); + case "otel.exporter.otlp.traces.headers": + return mergeWithOtel(otlpExporterProperties.getTraces().getHeaders(), otelSdkMap); + default: + break; + } + + String value = environment.getProperty(normalizedName); + if (value == null) { + return otelSdkMap; + } + return (Map) parser.parseExpression(value).getValue(); + } + + /** + * If you specify the environment variable OTEL_RESOURCE_ATTRIBUTES_POD_NAME, then + * Spring Boot will ignore OTEL_RESOURCE_ATTRIBUTES, which violates the principle of + * least surprise. This method merges the two maps, giving precedence to + * OTEL_RESOURCE_ATTRIBUTES_POD_NAME, which is more specific and which is also the value + * that Spring Boot will use (and which will honor SpEL). + */ + private static Map mergeWithOtel( + Map springMap, Map otelSdkMap) { + Map merged = new HashMap<>(otelSdkMap); + merged.putAll(springMap); + return merged; + } + + @Nullable + private static T or(@Nullable T first, @Nullable T second) { + return first != null ? first : second; + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/resources/DistroVersionResourceProvider.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/resources/DistroVersionResourceProvider.java new file mode 100644 index 000000000000..8ba185f97b98 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/resources/DistroVersionResourceProvider.java @@ -0,0 +1,38 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.resources; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.instrumentation.api.internal.EmbeddedInstrumentationProperties; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider; +import io.opentelemetry.sdk.resources.Resource; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public class DistroVersionResourceProvider implements ResourceProvider { + + public static final String VERSION = + EmbeddedInstrumentationProperties.findVersion("io.opentelemetry.spring-boot-autoconfigure"); + + private static final AttributeKey TELEMETRY_DISTRO_NAME = + AttributeKey.stringKey("telemetry.distro.name"); + private static final AttributeKey TELEMETRY_DISTRO_VERSION = + AttributeKey.stringKey("telemetry.distro.version"); + + @Override + public Resource createResource(ConfigProperties config) { + return Resource.create( + Attributes.of( + TELEMETRY_DISTRO_NAME, + "opentelemetry-spring-boot-starter", + TELEMETRY_DISTRO_VERSION, + VERSION)); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/resources/SpringResourceProvider.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/resources/SpringResourceProvider.java new file mode 100644 index 000000000000..5f562025764b --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/resources/SpringResourceProvider.java @@ -0,0 +1,47 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.resources; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.semconv.ServiceAttributes; +import java.util.Optional; +import org.springframework.boot.info.BuildProperties; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public class SpringResourceProvider implements ResourceProvider { + + private final Optional buildProperties; + + public SpringResourceProvider(Optional buildProperties) { + this.buildProperties = buildProperties; + } + + @Override + public Resource createResource(ConfigProperties configProperties) { + AttributesBuilder attributesBuilder = Attributes.builder(); + buildProperties + .map(BuildProperties::getName) + .ifPresent(v -> attributesBuilder.put(ServiceAttributes.SERVICE_NAME, v)); + + String springApplicationName = configProperties.getString("spring.application.name"); + if (springApplicationName != null) { + attributesBuilder.put(ServiceAttributes.SERVICE_NAME, springApplicationName); + } + + buildProperties + .map(BuildProperties::getVersion) + .ifPresent(v -> attributesBuilder.put(ServiceAttributes.SERVICE_VERSION, v)); + + return Resource.create(attributesBuilder.build()); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 000000000000..75d3ef14f304 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,857 @@ +{ + "groups": [ + { + "name": "otel" + } + ], + "properties": [ + { + "name": "otel.attribute.count.limit", + "type": "java.lang.Integer", + "description": "The maximum number of attributes. Applies to spans, span events, span links, and logs.", + "defaultValue": 128 + }, + { + "name": "otel.attribute.value.length.limit", + "type": "java.lang.String", + "description": "The maximum length of attribute values. Applies to spans and logs. By default, there is no limit." + }, + { + "name": "otel.blrp.export.timeout", + "type": "java.lang.String", + "description": "The maximum allowed time, in milliseconds, to export OTLP log batch data.
Durations can be of the form {number}{unit}, where unit is one of:

If no unit is specified, milliseconds is the assumed duration unit.", + "defaultValue": 30000 + }, + { + "name": "otel.blrp.max.export.batch.size", + "type": "java.lang.Integer", + "description": "The maximum OTLP log batch size.", + "defaultValue": 512 + }, + { + "name": "otel.blrp.max.queue.size", + "type": "java.lang.Integer", + "description": "The maximum OTLP log batch queue size.", + "defaultValue": 2048 + }, + { + "name": "otel.blrp.schedule.delay", + "type": "java.lang.String", + "description": "The interval, in milliseconds, between two consecutive OTLP log batch exports.
Durations can be of the form {number}{unit}, where unit is one of:

If no unit is specified, milliseconds is the assumed duration unit.", + "defaultValue": 1000 + }, + { + "name": "otel.bsp.schedule.delay", + "type": "java.lang.String", + "description": "The interval, in milliseconds, between two consecutive OTLP span batch exports.
Durations can be of the form {number}{unit}, where unit is one of:

If no unit is specified, milliseconds is the assumed duration unit.", + "defaultValue": 5000 + }, + { + "name": "otel.bsp.export.timeout", + "type": "java.lang.String", + "description": "The maximum allowed time, in milliseconds, to export OTLP span batch data.
Durations can be of the form {number}{unit}, where unit is one of:

If no unit is specified, milliseconds is the assumed duration unit.", + "defaultValue": 30000 + }, + { + "name": "otel.bsp.max.export.batch.size", + "type": "java.lang.Integer", + "description": "The maximum OTLP span batch size.", + "defaultValue": 512 + }, + { + "name": "otel.bsp.max.queue.size", + "type": "java.lang.Integer", + "description": "The maximum OTLP span batch queue size.", + "defaultValue": 2048 + }, + { + "name": "otel.experimental.exporter.otlp.retry.enabled", + "type": "java.lang.Boolean", + "description": "Enable experimental retry support. See https://github.com/open-telemetry/opentelemetry-java/blob/main/sdk-extensions/autoconfigure/README.md#otlp-exporter-retry.", + "defaultValue": false + }, + { + "name": "otel.experimental.metrics.cardinality.limit", + "type": "java.lang.Integer", + "description": "If set, configure experimental cardinality limit. The value dictates the maximum number of distinct points per metric.", + "defaultValue": 2000 + }, + { + "name": "otel.experimental.metrics.view.config", + "type": "java.util.List", + "description": "View file configuration See https://github.com/open-telemetry/opentelemetry-java/blob/main/sdk-extensions/incubator/README.md#view-file-configuration." + }, + { + "name": "otel.experimental.resource.disabled.keys", + "type": "java.util.List", + "description": "Filter out resource entries with these keys." + }, + { + "name": "otel.exporter.otlp.certificate", + "type": "java.lang.String", + "description": "The path to the file containing trusted certificates to use when verifying an OTLP trace, metric, or log server's TLS credentials.
The file should contain one or more X.509 certificates in PEM format.
By default the host platform's trusted root certificates are used." + }, + { + "name": "otel.exporter.otlp.client.certificate", + "type": "java.lang.String", + "description": "The path to the file containing trusted certificates to use when verifying an OTLP trace, metric, or log client's TLS credentials.
The file should contain one or more X.509 certificates in PEM format.
By default, no chain file is used." + }, + { + "name": "otel.exporter.otlp.client.key", + "type": "java.lang.String", + "description": "The path to the file containing private client key to use when verifying an OTLP trace, metric, or log client's TLS credentials.
The file should contain one private key PKCS8 PEM format.
By default, no client key is used." + }, + { + "name": "otel.exporter.otlp.compression", + "type": "java.lang.String", + "description": "The compression type to use on OTLP trace, metric, and log requests.
Options include gzip.
By default, no compression will be used." + }, + { + "name": "otel.exporter.otlp.endpoint", + "type": "java.lang.String", + "description": "The OTLP traces, metrics, and logs endpoint to connect to.
Must be a URL with a scheme of either http or https based on the use of TLS. If protocol is http/protobuf the version and signal will be appended to the path (e.g. v1/traces, v1/metrics, or v1/logs).
Default is http://localhost:4317 when protocol is grpc, and http://localhost:4318/v1/{signal} when protocol is http/protobuf." + }, + { + "name": "otel.exporter.otlp.headers", + "type": "java.util.Map", + "description": "Request headers for OTLP trace, metric, and log requests.
Can be either a Spring map or a key-value separated String, e.g. key1=value1,key2=value2." + }, + { + "name": "otel.exporter.otlp.logs.certificate", + "type": "java.lang.String", + "description": " The path to the file containing trusted certificates to use when verifying an OTLP log server's TLS credentials.
The file should contain one or more X.509 certificates in PEM format.
By default, the host platform's trusted root certificates are used." + }, + { + "name": "otel.exporter.otlp.logs.client.certificate", + "type": "java.lang.String", + "description": "The path to the file containing trusted certificates to use when verifying an OTLP log server's TLS credentials.
The file should contain one or more X.509 certificates in PEM format.
By default, no chain file is used." + }, + { + "name": "otel.exporter.otlp.logs.client.key", + "type": "java.lang.String", + "description": "The path to the file containing private client key to use when verifying an OTLP log client's TLS credentials.
The file should contain one private key PKCS8 PEM format.
By default, no client key file is used." + }, + { + "name": "otel.exporter.otlp.logs.compression", + "type": "java.lang.String", + "description": "The compression type to use on OTLP log requests.
Options include gzip.
By default, no compression will be used." + }, + { + "name": "otel.exporter.otlp.logs.endpoint", + "type": "java.lang.String", + "description": "The OTLP logs endpoint to connect to.
Must be a URL with a scheme of either http or https based on the use of TLS.
Default is http://localhost:4317 when protocol is grpc, and http://localhost:4318/v1/logs when protocol is http/protobuf." + }, + { + "name": "otel.exporter.otlp.logs.headers", + "type": "java.util.Map", + "description": "Request headers for OTLP log requests.
Can be either a Spring map or a key-value separated String, e.g. key1=value1,key2=value2." + }, + { + "name": "otel.exporter.otlp.logs.protocol", + "type": "java.lang.String", + "description": "The transport protocol to use on OTLP log requests.", + "defaultValue": "http/protobuf" + }, + { + "name": "otel.exporter.otlp.logs.timeout", + "type": "java.lang.String", + "description": "The maximum waiting time, in milliseconds, allowed to send each OTLP log batch.
Durations can be of the form {number}{unit}, where unit is one of:

If no unit is specified, milliseconds is the assumed duration unit.", + "defaultValue": "10000" + }, + { + "name": "otel.exporter.otlp.metrics.certificate", + "type": "java.lang.String", + "description": "The path to the file containing trusted certificates to use when verifying an OTLP metric server's TLS credentials.
The file should contain one or more X.509 certificates in PEM format.
By default, the host platform's trusted root certificates are used." + }, + { + "name": "otel.exporter.otlp.metrics.client.certificate", + "type": "java.lang.String", + "description": "The path to the file containing trusted certificates to use when verifying an OTLP metric server's TLS credentials.
The file should contain one or more X.509 certificates in PEM format.
By default, no chain file is used." + }, + { + "name": "otel.exporter.otlp.metrics.client.key", + "type": "java.lang.String", + "description": "The path to the file containing private client key to use when verifying an OTLP metric client's TLS credentials.
The file should contain one private key PKCS8 PEM format.
By default, no client key file is used." + }, + { + "name": "otel.exporter.otlp.metrics.compression", + "type": "java.lang.String", + "description": "The compression type to use on OTLP metric requests.
Options include gzip.
By default, no compression will be used." + }, + { + "name": "otel.exporter.otlp.metrics.default.histogram.aggregation", + "type": "java.lang.String", + "description": "The preferred default histogram aggregation.", + "defaultValue": "EXPLICIT_BUCKET_HISTOGRAM" + }, + { + "name": "otel.exporter.otlp.metrics.endpoint", + "type": "java.lang.String", + "description": "The OTLP metrics endpoint to connect to.
Must be a URL with a scheme of either http or https based on the use of TLS.
Default is http://localhost:4317 when protocol is grpc, and http://localhost:4318/v1/metrics when protocol is http/protobuf." + }, + { + "name": "otel.exporter.otlp.metrics.headers", + "type": "java.util.Map", + "description": "Request headers for OTLP metric requests.
Can be either a Spring map or a key-value separated String, e.g. key1=value1,key2=value2." + }, + { + "name": "otel.exporter.otlp.metrics.protocol", + "type": "java.lang.String", + "description": "The transport protocol to use on OTLP metric requests.", + "defaultValue": "http/protobuf" + }, + { + "name": "otel.exporter.otlp.metrics.temporality.preference", + "type": "java.lang.String", + "description": "The preferred output aggregation temporality.", + "defaultValue": "CUMULATIVE" + }, + { + "name": "otel.exporter.otlp.metrics.timeout", + "type": "java.lang.String", + "description": "The maximum waiting time, in milliseconds, allowed to send each OTLP metric batch.
Durations can be of the form {number}{unit}, where unit is one of:

If no unit is specified, milliseconds is the assumed duration unit.", + "defaultValue": "10000" + }, + { + "name": "otel.exporter.otlp.protocol", + "type": "java.lang.String", + "description": "The transport protocol to use on OTLP trace, metric, and log requests.", + "defaultValue": "http/protobuf" + }, + { + "name": "otel.exporter.otlp.timeout", + "type": "java.lang.String", + "description": "The maximum waiting time, in milliseconds, allowed to send each OTLP trace, metric, and log batch.
Durations can be of the form {number}{unit}, where unit is one of:

If no unit is specified, milliseconds is the assumed duration unit.", + "defaultValue": "10000" + }, + { + "name": "otel.exporter.otlp.traces.certificate", + "type": "java.lang.String", + "description": "The path to the file containing trusted certificates to use when verifying an OTLP trace server's TLS credentials.
The file should contain one or more X.509 certificates in PEM format.
By default, the host platform's trusted root certificates are used." + }, + { + "name": "otel.exporter.otlp.traces.client.certificate", + "type": "java.lang.String", + "description": "The path to the file containing trusted certificates to use when verifying an OTLP trace server's TLS credentials.
The file should contain one or more X.509 certificates in PEM format.
By default no chain file is used." + }, + { + "name": "otel.exporter.otlp.traces.client.key", + "type": "java.lang.String", + "description": "The path to the file containing private client key to use when verifying an OTLP trace client's TLS credentials.
The file should contain one private key PKCS8 PEM format.
By default, no client key file is used." + }, + { + "name": "otel.exporter.otlp.traces.compression", + "type": "java.lang.String", + "description": "The compression type to use on OTLP trace requests.
Options include gzip.
By default, no compression will be used." + }, + { + "name": "otel.exporter.otlp.traces.endpoint", + "type": "java.lang.String", + "description": "The OTLP traces endpoint to connect to.
Must be a URL with a scheme of either http or https based on the use of TLS.
Default is http://localhost:4317 when protocol is grpc, and http://localhost:4318/v1/traces when protocol is http/protobuf." + }, + { + "name": "otel.exporter.otlp.traces.headers", + "type": "java.util.Map", + "description": "Request headers for OTLP trace requests.
Can be either a Spring map or a key-value separated String, e.g. key1=value1,key2=value2." + }, + { + "name": "otel.exporter.otlp.traces.protocol", + "type": "java.lang.String", + "description": "The transport protocol to use on OTLP trace requests.", + "defaultValue": "http/protobuf" + }, + { + "name": "otel.exporter.otlp.traces.timeout", + "type": "java.lang.String", + "description": "The maximum waiting time, in milliseconds, allowed to send each OTLP trace batch.
Durations can be of the form {number}{unit}, where unit is one of:

If no unit is specified, milliseconds is the assumed duration unit.", + "defaultValue": "10000" + }, + { + "name": "otel.exporter.zipkin.endpoint", + "type": "java.lang.String", + "description": "The Zipkin endpoint to connect to.
Currently only HTTP is supported.", + "defaultValue": "http://localhost:9411/api/v2/spans" + }, + { + "name": "otel.instrumentation.annotations.enabled", + "type": "java.lang.Boolean", + "description": "Enable the @WithSpan annotation.", + "defaultValue": true + }, + { + "name": "otel.instrumentation.common.db-statement-sanitizer.enabled", + "type": "java.lang.Boolean", + "description": "Enables the DB statement sanitization.", + "defaultValue": true + }, + { + "name": "otel.instrumentation.common.default-enabled", + "type": "java.lang.Boolean", + "description": "Enables all instrumentations. Set to false to disable all instrumentations and then enable specific modules individually, e.g. otel.instrumentation.jdbc.enabled=true.", + "defaultValue": true + }, + { + "name": "otel.instrumentation.common.peer-service-mapping", + "type": "java.util.Map", + "description": "Used to specify a mapping from host names or IP addresses to peer services, as a comma-separated list of host_or_ip=user_assigned_name pairs. The peer service is added as an attribute to a span whose host or IP address match the mapping. See https://opentelemetry.io/docs/zero-code/java/agent/configuration/#peer-service-name." + }, + { + "name": "otel.instrumentation.http.client.capture-request-headers", + "type": "java.util.List", + "description": "List of HTTP request headers to capture in HTTP clients." + }, + { + "name": "otel.instrumentation.http.client.capture-response-headers", + "type": "java.util.List", + "description": "List of HTTP response headers to capture in HTTP clients." + }, + { + "name": "otel.instrumentation.http.client.emit-experimental-telemetry", + "type": "java.lang.Boolean", + "description": "Enable the capture of experimental HTTP client telemetry. Add the http.request.body.size and http.response.body.size> attributes to spans, and record the http.client.request.size and http.client.response.size metrics.", + "defaultValue": false + }, + { + "name": "otel.instrumentation.http.client.experimental.redact-query-parameters", + "type": "java.lang.Boolean", + "description": "Redact sensitive URL parameters. See https://opentelemetry.io/docs/specs/semconv/http/http-spans.", + "defaultValue": true + }, + { + "name": "otel.instrumentation.http.known-methods", + "type": "java.util.List", + "description": "Configures the instrumentation to recognize an alternative set of HTTP request methods. All other methods will be treated as _OTHER.", + "defaultValue": "CONNECT,DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT,TRACE" + }, + { + "name": "otel.instrumentation.http.server.capture-request-headers", + "type": "java.util.List", + "description": "List of HTTP request headers to capture in HTTP servers." + }, + { + "name": "otel.instrumentation.http.server.capture-response-headers", + "type": "java.util.List", + "description": "List of HTTP response headers to capture in HTTP servers." + }, + { + "name": "otel.instrumentation.http.server.emit-experimental-telemetry", + "type": "java.lang.Boolean", + "description": "Enable the capture of experimental HTTP server telemetry. Add the http.request.body.size and http.response.body.size attributes to spans, and record the http.server.request.body.size and http.server.response.body.size metrics.", + "defaultValue": false + }, + { + "name": "otel.instrumentation.jdbc.enabled", + "type": "java.lang.Boolean", + "description": "Enable the JDBC instrumentation.", + "defaultValue": true + }, + { + "name": "otel.instrumentation.jdbc.statement-sanitizer.enabled", + "type": "java.lang.Boolean", + "description": "Enables the DB statement sanitization.", + "defaultValue": true + }, + { + "name": "otel.instrumentation.jdbc.experimental.capture-query-parameters", + "type": "java.lang.Boolean", + "description": "Sets whether the query parameters should be captured as span attributes named db.query.parameter.<key>. Enabling this option disables the statement sanitization.

WARNING: captured query parameters may contain sensitive information such as passwords, personally identifiable information or protected health info.", + "defaultValue": false + }, + { + "name": "otel.instrumentation.jdbc.experimental.transaction.enabled", + "type": "java.lang.Boolean", + "description": "Enables experimental instrumentation to create spans for COMMIT and ROLLBACK operations.", + "defaultValue": false + }, + { + "name": "otel.instrumentation.kafka.enabled", + "type": "java.lang.Boolean", + "description": "Enable the Kafka instrumentation.", + "defaultValue": true + }, + { + "name": "otel.instrumentation.kafka.experimental-span-attributes", + "type": "java.lang.Boolean", + "description": "Enable the capture of experimental Kafka span attributes.", + "defaultValue": false + }, + { + "name": "otel.instrumentation.kafka.autoconfigure-interceptor", + "type": "java.lang.Boolean", + "description": "Enable automatic configuration of tracing interceptors on ConcurrentKafkaListenerContainerFactory using a BeanPostProcessor. You may disable this if you wish to manually configure these interceptors.", + "defaultValue": true + }, + { + "name": "otel.instrumentation.mongo.enabled", + "type": "java.lang.Boolean", + "description": "Enable the Mongo client instrumentation.", + "defaultValue": true + }, + { + "name": "otel.instrumentation.mongo.statement-sanitizer.enabled", + "type": "java.lang.Boolean", + "description": "Enables the DB statement sanitization.", + "defaultValue": true + }, + { + "name": "otel.instrumentation.log4j-appender.enabled", + "type": "java.lang.Boolean", + "description": "Enable the Log4J2 appender instrumentation.", + "defaultValue": true + }, + { + "name": "otel.instrumentation.logback-appender.enabled", + "type": "java.lang.Boolean", + "description": "Enable the Logback appender instrumentation.", + "defaultValue": true + }, + { + "name": "otel.instrumentation.logback-appender.experimental.capture-code-attributes", + "type": "java.lang.Boolean", + "description": "Enable the capture of source code attributes. Note that capturing source code attributes at logging sites might add a performance overhead.", + "defaultValue": false + }, + { + "name": "otel.instrumentation.logback-appender.experimental.capture-marker-attribute", + "type": "java.lang.Boolean", + "description": "Enable the capture of Logback markers as attributes.", + "defaultValue": false + }, + { + "name": "otel.instrumentation.logback-appender.experimental.capture-key-value-pair-attributes", + "type": "java.lang.Boolean", + "description": "Enable the capture of Logback key value pairs as attributes.", + "defaultValue": false + }, + { + "name": "otel.instrumentation.logback-appender.experimental-log-attributes", + "type": "java.lang.Boolean", + "description": "Enable the capture of experimental log attributes thread.name and thread.id.", + "defaultValue": false + }, + { + "name": "otel.instrumentation.logback-appender.experimental.capture-logger-context-attributes", + "type": "java.lang.Boolean", + "description": "Enable the capture of Logback logger context properties as attributes.", + "defaultValue": false + }, + { + "name": "otel.instrumentation.logback-appender.experimental.capture-logstash-attributes", + "type": "java.lang.Boolean", + "description": "Enable the capture of Logstash attributes, supported are those added to logs via `Markers.append()`, `Markers.appendEntries()`, `Markers.appendArray()` and `Markers.appendRaw()` methods.", + "defaultValue": false + }, + { + "name": "otel.instrumentation.logback-appender.experimental.capture-mdc-attributes", + "type": "java.lang.String", + "description": "MDC attributes to capture. Use the wildcard character * to capture all attributes. This is a comma-separated list of attribute names." + }, + { + "name": "otel.instrumentation.logback-mdc.enabled", + "type": "java.lang.Boolean", + "description": "Enable the Logback MDC instrumentation.", + "defaultValue": true + }, + { + "name": "otel.instrumentation.logback-mdc.add-baggage", + "type": "java.lang.Boolean", + "description": "Enable exposing baggage attributes through MDC.", + "defaultValue": false + }, + { + "name": "otel.instrumentation.common.logging.trace-id", + "type": "java.lang.String", + "description": "Customize MDC key name for the trace id.", + "defaultValue": "trace_id" + }, + { + "name": "otel.instrumentation.common.logging.span-id", + "type": "java.lang.String", + "description": "Customize MDC key name for the span id.", + "defaultValue": "span_id" + }, + { + "name": "otel.instrumentation.common.logging.trace-flags", + "type": "java.lang.String", + "description": "Customize MDC key name for the trace flags.", + "defaultValue": "trace_flags" + }, + { + "name": "otel.instrumentation.micrometer.enabled", + "type": "java.lang.Boolean", + "description": "Enable the Micrometer instrumentation.", + "defaultValue": false + }, + { + "name": "otel.instrumentation.r2dbc.enabled", + "type": "java.lang.Boolean", + "description": "Enable the R2DBC (reactive JDBC) instrumentation.", + "defaultValue": true + }, + { + "name": "otel.instrumentation.r2dbc.statement-sanitizer.enabled", + "type": "java.lang.Boolean", + "description": "Enables the DB statement sanitization.", + "defaultValue": true + }, + { + "name": "otel.instrumentation.runtime-telemetry.capture-gc-cause", + "type": "java.lang.Boolean", + "description": "Enable the capture of the jvm.gc.cause attribute with the jvm.gc.duration metric.", + "defaultValue": false + }, + { + "name": "otel.instrumentation.runtime-telemetry.enabled", + "type": "java.lang.Boolean", + "description": "Enable runtime telemetry metrics.", + "defaultValue": true + }, + { + "name": "otel.instrumentation.runtime-telemetry.emit-experimental-telemetry", + "type": "java.lang.Boolean", + "description": "Enable the capture of experimental metrics.", + "defaultValue": false + }, + { + "name": "otel.instrumentation.runtime-telemetry-java17.enable-all", + "type": "java.lang.Boolean", + "description": "Enable the capture of all JFR based metrics.", + "defaultValue": false + }, + { + "name": "otel.instrumentation.runtime-telemetry-java17.enabled", + "type": "java.lang.Boolean", + "description": "Enable the capture of JFR based metrics.", + "defaultValue": false + }, + { + "name": "otel.instrumentation.spring-web.enabled", + "type": "java.lang.Boolean", + "description": "Enable the RestTemplate instrumentation.", + "defaultValue": true + }, + { + "name": "otel.instrumentation.spring-webflux.enabled", + "type": "java.lang.Boolean", + "description": "Enable the WebClient instrumentation.", + "defaultValue": true + }, + { + "name": "otel.instrumentation.spring-webmvc.enabled", + "type": "java.lang.Boolean", + "description": "Enable the Servlet instrumentation.", + "defaultValue": true + }, + { + "name": "otel.instrumentation.spring-scheduling.enabled", + "type": "java.lang.Boolean", + "description": "Enable the Spring Scheduling instrumentation.", + "defaultValue": true + }, + { + "name": "otel.java.enabled.resource.providers", + "type": "java.util.List", + "description": "Enables one or more ResourceProvider types. If unset, all resource providers are enabled. Each entry is the fully qualified classname of a ResourceProvider." + }, + { + "name": "otel.java.disabled.resource.providers", + "type": "java.util.List", + "description": " Disables one or more ResourceProvider types. Each entry is the fully qualified classname of a ResourceProvider." + }, + { + "name": "otel.logs.exporter", + "type": "java.util.List", + "description": "List of exporters to be used for logs.", + "defaultValue": "otlp" + }, + { + "name": "otel.metric.export.interval", + "type": "java.lang.String", + "description": "The interval, in milliseconds, between the start of two export attempts.
Durations can be of the form {number}{unit}, where unit is one of:

If no unit is specified, milliseconds is the assumed duration unit.", + "defaultValue": "60000" + }, + { + "name": "otel.metrics.exemplar.filter", + "type": "java.lang.String", + "description": "The filter for exemplar sampling.", + "defaultValue": "TRACE_BASED" + }, + { + "name": "otel.metrics.exporter", + "type": "java.util.List", + "description": "List of exporters to be used for metrics.", + "defaultValue": "otlp" + }, + { + "name": "otel.propagators", + "type": "java.util.List", + "description": "List of propagators to be used for context propagation.", + "defaultValue": "tracecontext,baggage" + }, + { + "name": "otel.resource.attributes", + "type": "java.util.Map", + "description": "Resource attributes to be added to all spans. In addition to these attributes, the resource will also include attributes discovered from the runtime, such as host.name and process.id." + }, + { + "name": "otel.sdk.disabled", + "type": "java.lang.Boolean", + "description": "Disable the OpenTelemetry Spring Starter.", + "defaultValue": false + }, + { + "name": "otel.service.name", + "type": "java.lang.String", + "description": "Specify logical service name. Takes precedence over service.name defined with otel.resource.attributes." + }, + { + "name": "otel.span.attribute.value.length.limit", + "type": "java.lang.Integer", + "description": "The maximum length of span attribute values. Takes precedence over otel.attribute.value.length.limit. By default, there is no limit." + }, + { + "name": "otel.span.attribute.count.limit", + "type": "java.lang.Integer", + "description": "The maximum number of attributes per span. Takes precedence over otel.attribute.count.limit.", + "defaultValue": 128 + }, + { + "name": "otel.span.event.count.limit", + "type": "java.lang.Integer", + "description": "The maximum number of events per span.", + "defaultValue": 128 + }, + { + "name": "otel.span.link.count.limit", + "type": "java.lang.Integer", + "description": "The maximum number of links per span.", + "defaultValue": 128 + }, + { + "name": "otel.traces.exporter", + "type": "java.util.List", + "description": "List of exporters to be used for tracing.", + "defaultValue": "otlp" + }, + { + "name": "otel.traces.sampler", + "type": "java.lang.String", + "description": "The sampler to use for tracing.", + "defaultValue": "parentbased_always_on" + }, + { + "name": "otel.traces.sampler.arg", + "type": "java.lang.Double", + "description": "An argument to the configured tracer if supported, for example a ratio.", + "defaultValue": 1.0 + } + ], + "hints": [ + { + "name": "otel.exporter.otlp.logs.protocol", + "values": [ + { + "value": "http/protobuf" + }, + { + "value": "grpc" + } + ] + }, + { + "name": "otel.exporter.otlp.metrics.protocol", + "values": [ + { + "value": "http/protobuf" + }, + { + "value": "grpc" + } + ] + }, + { + "name": "otel.exporter.otlp.protocol", + "values": [ + { + "value": "http/protobuf" + }, + { + "value": "grpc" + } + ] + }, + { + "name": "otel.exporter.otlp.traces.protocol", + "values": [ + { + "value": "http/protobuf" + }, + { + "value": "grpc" + } + ] + }, + { + "name": "otel.exporter.otlp.metrics.default.histogram.aggregation", + "values": [ + { + "value": "BASE2_EXPONENTIAL_BUCKET_HISTOGRAM" + }, + { + "value": "EXPLICIT_BUCKET_HISTOGRAM" + } + ] + }, + { + "name": "otel.exporter.otlp.metrics.temporality.preference", + "values": [ + { + "value": "CUMULATIVE", + "description": "All instruments will have cumulative temporality." + }, + { + "value": "DELTA", + "description": "Counter (sync and async) and histograms will be delta, up down counters (sync and async) will be cumulative." + }, + { + "value": "LOWMEMORY", + "description": "Sync counter and histograms will be delta, async counter and up down counters (sync and async) will be cumulative." + } + ] + }, + { + "name": "otel.logs.exporter", + "values": [ + { + "value": "console", + "description": "The console exporter prints exported logs to stdout. It's mainly used for testing and debugging." + }, + { + "value": "none", + "description": "No autoconfigured exporter." + }, + { + "value": "otlp", + "description": "OpenTelemetry Protocol (OTLP) exporter." + } + ] + }, + { + "name": "otel.metrics.exemplar.filter", + "values": [ + { + "value": "ALWAYS_ON", + "description": "Take all exemplars." + }, + { + "value": "ALWAYS_OFF", + "description": "Drop all exemplars." + }, + { + "value": "TRACE_BASED", + "description": "Choose exemplars that correspond to a sampled span." + } + ] + }, + { + "name": "otel.metrics.exporter", + "values": [ + { + "value": "console", + "description": "The console exporter prints exported metrics to stdout. It's mainly used for testing and debugging." + }, + { + "value": "none", + "description": "No autoconfigured exporter." + }, + { + "value": "otlp", + "description": "OpenTelemetry Protocol (OTLP) exporter." + } + ] + }, + { + "name": "otel.propagators", + "values": [ + { + "value": "baggage", + "description": "The Baggage propagator propagates baggage using the W3C Baggage format. See https://www.w3.org/TR/baggage/." + }, + { + "value": "b3", + "description": "The B3 propagator propagates trace context using the B3 single-header format: See https://github.com/openzipkin/b3-propagation#single-header." + }, + { + "value": "b3multi", + "description": "The B3 propagator propagates trace context using the B3 multi-header format: See https://github.com/openzipkin/b3-propagation#multiple-headers." + }, + { + "value": "jaeger", + "description": "The Jaeger propagator propagates trace context using the Jaeger format. See https://www.jaegertracing.io/docs/1.21/client-libraries/#propagation-format." + }, + { + "value": "ottrace", + "description": "The OpenTelemetry Trace Context propagator propagates trace context using the OpenTelemetry format. See https://github.com/opentracing/specification/blob/master/rfc/trace_identifiers.md." + }, + { + "value": "tracecontext", + "description": "The Trace Context propagator propagates trace context using the W3C Trace Context format (add `baggage` as well to include W3C baggage). See https://www.w3.org/TR/trace-context/." + }, + { + "value": "xray", + "description": "The AWS X-Ray propagator propagates trace context using the AWS X-Ray format. See https://docs.aws.amazon.com/xray/latest/devguide/xray-concepts.html#xray-concepts-tracingheader." + } + ] + }, + { + "name": "otel.traces.exporter", + "values": [ + { + "value": "console", + "description": "The console exporter prints the name of the span along with its attributes to stdout. It's mainly used for testing and debugging." + }, + { + "value": "none", + "description": "No autoconfigured exporter." + }, + { + "value": "otlp", + "description": "OpenTelemetry Protocol (OTLP) exporter." + }, + { + "value": "zipkin", + "description": "Zipkin exporter." + } + ] + }, + { + "name": "otel.traces.sampler", + "values": [ + { + "value": "always_on", + "description": "Keep all spans." + }, + { + "value": "always_off", + "description": "Drop all spans." + }, + { + "value": "traceidratio", + "description": "Keep a ratio of otel.traces.sampler.arg of all spans." + }, + { + "value": "parentbased_always_on", + "description": "Keep all spans where the parent is also kept. If there is no parent, keep all spans." + }, + { + "value": "parentbased_always_off", + "description": "Keep all spans where the parent is also kept. If there is no parent, drop all spans." + }, + { + "value": "parentbased_traceidratio", + "description": "Keep all spans where the parent is also kept. If there is no parent, keep a ratio of otel.traces.sampler.arg of all spans." + } + ] + } + ] +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/resources/META-INF/native-image/io.opentelemetry.instrumentation/opentelemetry-spring-boot/resource-config.json b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/resources/META-INF/native-image/io.opentelemetry.instrumentation/opentelemetry-spring-boot/resource-config.json new file mode 100644 index 000000000000..58a8e28876bc --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/resources/META-INF/native-image/io.opentelemetry.instrumentation/opentelemetry-spring-boot/resource-config.json @@ -0,0 +1,9 @@ +{ + "resources": { + "includes": [ + { + "pattern": "META-INF/io/opentelemetry/instrumentation/.*.properties" + } + ] + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/resources/META-INF/spring/aot.factories b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/resources/META-INF/spring/aot.factories new file mode 100644 index 000000000000..e97c4b46ae9c --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/resources/META-INF/spring/aot.factories @@ -0,0 +1,5 @@ +org.springframework.aot.hint.RuntimeHintsRegistrar=\ +io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.annotations.OpenTelemetryAnnotationsRuntimeHints + +org.springframework.beans.factory.aot.BeanRegistrationExcludeFilter=\ +io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics.RuntimeMetricsBeanRegistrationExcludeFilter diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 000000000000..32e07cae0ae1 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,16 @@ +io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration +io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.annotations.InstrumentationAnnotationsAutoConfiguration +io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka.KafkaInstrumentationAutoConfiguration +io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.mongo.MongoClientInstrumentationAutoConfiguration +io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.logging.OpenTelemetryAppenderAutoConfiguration +io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.jdbc.JdbcInstrumentationAutoConfiguration +io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.micrometer.MicrometerBridgeAutoConfiguration +io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.r2dbc.R2dbcInstrumentationAutoConfiguration +io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.web.SpringWebInstrumentationAutoConfiguration +io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.webflux.SpringWebfluxInstrumentationAutoConfiguration +io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.web.RestClientInstrumentationAutoConfiguration +io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.webmvc.SpringWebMvc7InstrumentationAutoConfiguration +io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.scheduling.SpringSchedulingInstrumentationAutoConfiguration +io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics.RuntimeMetricsAutoConfiguration +io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics.Java8RuntimeMetricsProvider +io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics.Java17RuntimeMetricsProvider diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfigurationTest.java new file mode 100644 index 000000000000..b37f074c34ac --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfigurationTest.java @@ -0,0 +1,172 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.withSettings; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.exporter.otlp.internal.OtlpSpanExporterProvider; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.internal.AutoConfigureListener; +import io.opentelemetry.sdk.trace.export.SpanExporter; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; + +/** Spring Boot auto configuration test for {@link OpenTelemetryAutoConfiguration}. */ +class OpenTelemetryAutoConfigurationTest { + @TestConfiguration + static class CustomTracerConfiguration { + @Bean + public OpenTelemetry customOpenTelemetry() { + return OpenTelemetry.noop(); + } + } + + private final ApplicationContextRunner contextRunner = + new ApplicationContextRunner() + .withPropertyValues( + "otel.traces.exporter=none", + "otel.metrics.exporter=none", + "otel.logs.exporter=none", + "otel.propagators=b3", + "otel.experimental.resource.disabled.keys=a,b", + "otel.java.disabled.resource.providers=d"); + + @Test + @DisplayName( + "when Application Context contains OpenTelemetry bean should NOT initialize openTelemetry") + void customOpenTelemetry() { + this.contextRunner + .withUserConfiguration(CustomTracerConfiguration.class) + .withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class)) + .run( + context -> + assertThat(context) + .hasBean("customOpenTelemetry") + .doesNotHaveBean("openTelemetry") + .hasBean("otelProperties")); + } + + @Test + @DisplayName( + "when Application Context DOES NOT contain OpenTelemetry bean should initialize openTelemetry") + void initializeProvidersAndOpenTelemetry() { + this.contextRunner + .withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class)) + .run(context -> assertThat(context).hasBean("openTelemetry").hasBean("otelProperties")); + } + + @Test + void specialListProperties() { + this.contextRunner + .withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class)) + .run( + context -> + assertThat(context) + .getBean("otelProperties") + .satisfies( + p -> { + ConfigProperties configProperties = (ConfigProperties) p; + assertThat(configProperties.getList("otel.propagators")) + .containsExactly("b3"); + assertThat( + configProperties.getList( + "otel.experimental.resource.disabled.keys")) + .containsExactly("a", "b"); + assertThat( + configProperties.getList("otel.java.disabled.resource.providers")) + .containsExactlyInAnyOrder( + "d", + "io.opentelemetry.contrib.azure.resource.AzureAksResourceProvider", + "io.opentelemetry.contrib.azure.resource.AzureAppServiceResourceProvider", + "io.opentelemetry.contrib.azure.resource.AzureContainersResourceProvider", + "io.opentelemetry.contrib.azure.resource.AzureFunctionsResourceProvider", + "io.opentelemetry.contrib.azure.resource.AzureVmResourceProvider", + "io.opentelemetry.contrib.aws.resource.BeanstalkResourceProvider", + "io.opentelemetry.contrib.aws.resource.Ec2ResourceProvider", + "io.opentelemetry.contrib.aws.resource.EcsResourceProvider", + "io.opentelemetry.contrib.aws.resource.EksResourceProvider", + "io.opentelemetry.contrib.aws.resource.LambdaResourceProvider", + "io.opentelemetry.contrib.gcp.resource.GCPResourceProvider", + "io.opentelemetry.contrib.cloudfoundry.resources.CloudFoundryResourceProvider", + "io.opentelemetry.instrumentation.resources.ResourceProviderPropertiesCustomizerTest$Provider"); + })); + } + + @Test + void enabledProviders() { + this.contextRunner + .withPropertyValues("otel.java.enabled.resource.providers=e1,e2") + .withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class)) + .run( + context -> + assertThat(context) + .getBean("otelProperties", ConfigProperties.class) + .satisfies( + configProperties -> + assertThat( + configProperties.getList( + "otel.java.enabled.resource.providers")) + .containsExactly("e1", "e2"))); + } + + @Test + @DisplayName( + "when Application Context DOES NOT contain OpenTelemetry bean but SpanExporter should initialize openTelemetry") + void initializeOpenTelemetryWithCustomProviders() { + OtlpSpanExporterProvider spanExporterProvider = + Mockito.mock( + OtlpSpanExporterProvider.class, + withSettings().extraInterfaces(AutoConfigureListener.class)); + Mockito.when(spanExporterProvider.getName()).thenReturn("custom"); + Mockito.when(spanExporterProvider.createExporter(any())) + .thenReturn(Mockito.mock(SpanExporter.class)); + + this.contextRunner + .withBean( + "customSpanExporter", + OtlpSpanExporterProvider.class, + () -> spanExporterProvider, + bd -> bd.setDestroyMethodName("")) + .withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class)) + .withPropertyValues("otel.traces.exporter=custom") + .run(context -> assertThat(context).hasBean("openTelemetry")); + + Mockito.verify(spanExporterProvider).afterAutoConfigure(any()); + } + + @Test + void shouldInitializeSdkWhenNotDisabled() { + this.contextRunner + .withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class)) + .withPropertyValues("otel.sdk.disabled=false") + .run( + context -> { + assertThat(context).getBean("openTelemetry").isInstanceOf(OpenTelemetrySdk.class); + assertThat(context).hasBean("openTelemetry"); + }); + } + + @Test + void shouldInitializeNoopOpenTelemetryWhenSdkIsDisabled() { + this.contextRunner + .withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class)) + .withPropertyValues( + "otel.sdk.disabled=true", + "otel.resource.attributes=service.name=workflow-backend-dev,service.version=3c8f9ce9") + .run( + context -> + assertThat(context).getBean("openTelemetry").isEqualTo(OpenTelemetry.noop())); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/InstrumentationAnnotationsAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/InstrumentationAnnotationsAutoConfigurationTest.java new file mode 100644 index 000000000000..f55e5c13f406 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/InstrumentationAnnotationsAutoConfigurationTest.java @@ -0,0 +1,41 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.annotations; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.OpenTelemetry; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +class InstrumentationAnnotationsAutoConfigurationTest { + + private final ApplicationContextRunner contextRunner = + new ApplicationContextRunner() + .withBean(OpenTelemetry.class, OpenTelemetry::noop) + .withConfiguration( + AutoConfigurations.of(InstrumentationAnnotationsAutoConfiguration.class)); + + @Test + void instrumentationEnabled() { + contextRunner + .withPropertyValues("otel.instrumentation.annotations.enabled=true") + .run(context -> assertThat(context).hasBean("otelInstrumentationWithSpanAspect")); + } + + @Test + void instrumentationDisabled() { + contextRunner + .withPropertyValues("otel.instrumentation.annotations.enabled=false") + .run(context -> assertThat(context).doesNotHaveBean("otelInstrumentationWithSpanAspect")); + } + + @Test + void defaultConfiguration() { + contextRunner.run(context -> assertThat(context).hasBean("otelInstrumentationWithSpanAspect")); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/InstrumentationWithSpanAspectTest.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/InstrumentationWithSpanAspectTest.java new file mode 100644 index 000000000000..c2265549b734 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/annotations/InstrumentationWithSpanAspectTest.java @@ -0,0 +1,479 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.annotations; + +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.api.trace.SpanKind.CLIENT; +import static io.opentelemetry.api.trace.SpanKind.INTERNAL; +import static io.opentelemetry.instrumentation.testing.junit.code.SemconvCodeStabilityUtil.codeFunctionAssertions; +import static io.opentelemetry.instrumentation.testing.util.TelemetryDataUtil.orderByRootSpanName; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.sdk.testing.assertj.TracesAssert.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.annotations.SpanAttribute; +import io.opentelemetry.instrumentation.annotations.WithSpan; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.data.StatusData; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.aop.aspectj.annotation.AspectJProxyFactory; +import org.springframework.core.ParameterNameDiscoverer; + +class InstrumentationWithSpanAspectTest { + + @RegisterExtension + static final LibraryInstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + private InstrumentationWithSpanTester withSpanTester; + private String unproxiedTesterSimpleClassName; + private String unproxiedTesterClassName; + + WithSpanAspect newWithSpanAspect( + OpenTelemetry openTelemetry, ParameterNameDiscoverer parameterNameDiscoverer) { + return new InstrumentationWithSpanAspect(openTelemetry, parameterNameDiscoverer); + } + + @BeforeEach + void setup() { + InstrumentationWithSpanTester unproxiedTester = new InstrumentationWithSpanTester(); + unproxiedTesterSimpleClassName = unproxiedTester.getClass().getSimpleName(); + unproxiedTesterClassName = unproxiedTester.getClass().getName(); + + AspectJProxyFactory factory = new AspectJProxyFactory(); + factory.setTarget(unproxiedTester); + ParameterNameDiscoverer parameterNameDiscoverer = + new ParameterNameDiscoverer() { + @Override + public String[] getParameterNames(Method method) { + return new String[] {"discoveredName", null, "parameter", "nullAttribute", "notTraced"}; + } + + @Override + public String[] getParameterNames(Constructor constructor) { + return null; + } + }; + + WithSpanAspect aspect = newWithSpanAspect(testing.getOpenTelemetry(), parameterNameDiscoverer); + factory.addAspect(aspect); + + withSpanTester = factory.getProxy(); + } + + @Test + @DisplayName("when method is annotated with @WithSpan should wrap method execution in a Span") + void withSpanWithDefaults() { + // when + testing.runWithSpan("parent", withSpanTester::testWithSpan); + + // then + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL), + span -> + span.hasName(unproxiedTesterSimpleClassName + ".testWithSpan") + .hasKind(INTERNAL) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + codeFunctionAssertions(unproxiedTesterClassName, "testWithSpan")))); + } + + @Test + @DisplayName( + "when @WithSpan value is set should wrap method execution in a Span with custom name") + void withSpanName() { + // when + testing.runWithSpan("parent", () -> withSpanTester.testWithSpanWithValue()); + + // then + List> traces = testing.waitForTraces(1); + assertThat(traces) + .hasTracesSatisfyingExactly( + trace -> + trace.hasSpansSatisfyingExactly( + parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL), + span -> + span.hasName("greatestSpanEver") + .hasKind(INTERNAL) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + codeFunctionAssertions( + unproxiedTesterClassName, "testWithSpanWithValue")))); + } + + @Test + @DisplayName( + "when method is annotated with @WithSpan AND an exception is thrown span should record the exception") + void withSpanError() { + assertThatThrownBy(() -> withSpanTester.testWithSpanWithException()) + .isInstanceOf(Exception.class); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName(unproxiedTesterSimpleClassName + ".testWithSpanWithException") + .hasKind(INTERNAL) + .hasStatus(StatusData.error()) + .hasAttributesSatisfyingExactly( + codeFunctionAssertions( + unproxiedTesterClassName, "testWithSpanWithException")))); + } + + @Test + @DisplayName( + "when method is annotated with @WithSpan(kind=CLIENT) should build span with the declared SpanKind") + void withSpanKind() { + // when + testing.runWithSpan("parent", () -> withSpanTester.testWithClientSpan()); + + // then + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL), + span -> + span.hasName(unproxiedTesterSimpleClassName + ".testWithClientSpan") + .hasKind(CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + codeFunctionAssertions( + unproxiedTesterClassName, "testWithClientSpan")))); + } + + @Test + void withSpanAttributes() { + // when + testing.runWithSpan( + "parent", () -> withSpanTester.withSpanAttributes("foo", "bar", "baz", null, "fizz")); + + // then + List assertions = + codeFunctionAssertions(unproxiedTesterClassName, "withSpanAttributes"); + assertions.add(equalTo(stringKey("discoveredName"), "foo")); + assertions.add(equalTo(stringKey("implicitName"), "bar")); + assertions.add(equalTo(stringKey("explicitName"), "baz")); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL), + span -> + span.hasName(unproxiedTesterSimpleClassName + ".withSpanAttributes") + .hasKind(INTERNAL) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly(assertions))); + } + + @Test + @DisplayName( + "when method is annotated with @WithSpan(inheritContext=false) should build span without parent") + void withSpanWithoutParent() { + // when + testing.runWithSpan("parent", withSpanTester::testWithoutParentSpan); + + // then + testing.waitAndAssertSortedTraces( + orderByRootSpanName("parent", unproxiedTesterSimpleClassName + ".testWithoutParentSpan"), + trace -> trace.hasSpansSatisfyingExactly(span -> span.hasName("parent").hasKind(INTERNAL)), + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName(unproxiedTesterSimpleClassName + ".testWithoutParentSpan") + .hasKind(INTERNAL) + .hasNoParent() + .hasAttributesSatisfyingExactly( + codeFunctionAssertions( + unproxiedTesterClassName, "testWithoutParentSpan")))); + } + + static class InstrumentationWithSpanTester { + @WithSpan + public String testWithSpan() { + return "Span with name testWithSpan was created"; + } + + @WithSpan("greatestSpanEver") + public String testWithSpanWithValue() { + return "Span with name greatestSpanEver was created"; + } + + @WithSpan + public String testWithSpanWithException() throws Exception { + throw new Exception("Test @WithSpan With Exception"); + } + + @WithSpan(kind = CLIENT) + public String testWithClientSpan() { + return "Span with name testWithClientSpan and SpanKind.CLIENT was created"; + } + + @WithSpan + public CompletionStage testAsyncCompletionStage(CompletionStage stage) { + return stage; + } + + @WithSpan + public CompletableFuture testAsyncCompletableFuture(CompletableFuture stage) { + return stage; + } + + @WithSpan + public String withSpanAttributes( + @SpanAttribute String discoveredName, + @SpanAttribute String implicitName, + @SpanAttribute("explicitName") String parameter, + @SpanAttribute("nullAttribute") String nullAttribute, + String notTraced) { + + return "hello!"; + } + + @WithSpan(inheritContext = false) + public String testWithoutParentSpan() { + return "Span without parent span was created"; + } + } + + @Nested + @DisplayName("with a method annotated with @WithSpan returns CompletionStage") + class WithCompletionStage { + + @Test + @DisplayName("should end Span on complete") + void onComplete() { + CompletableFuture future = new CompletableFuture<>(); + + // when + testing.runWithSpan("parent", () -> withSpanTester.testAsyncCompletionStage(future)); + + // then + testing.waitAndAssertTraces( + trace -> + trace + .hasSize(1) + .hasSpansSatisfyingExactly(span -> span.hasName("parent").hasKind(INTERNAL))); + + // when + future.complete("DONE"); + + // then + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL), + span -> + span.hasName(unproxiedTesterSimpleClassName + ".testAsyncCompletionStage") + .hasKind(INTERNAL) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + codeFunctionAssertions( + unproxiedTesterClassName, "testAsyncCompletionStage")))); + } + + @Test + @DisplayName("should end Span on completeException AND should record the exception") + void onCompleteExceptionally() { + CompletableFuture future = new CompletableFuture<>(); + + // when + testing.runWithSpan("parent", () -> withSpanTester.testAsyncCompletionStage(future)); + + // then + testing.waitAndAssertTraces( + trace -> + trace + .hasSize(1) + .hasSpansSatisfyingExactly(span -> span.hasName("parent").hasKind(INTERNAL))); + + // when + future.completeExceptionally(new Exception("Test @WithSpan With completeExceptionally")); + + // then + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL), + span -> + span.hasName(unproxiedTesterSimpleClassName + ".testAsyncCompletionStage") + .hasKind(INTERNAL) + .hasStatus(StatusData.error()) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + codeFunctionAssertions( + unproxiedTesterClassName, "testAsyncCompletionStage")))); + } + + @Test + @DisplayName("should end Span on incompatible return value") + void onIncompatibleReturnValue() { + // when + testing.runWithSpan("parent", () -> withSpanTester.testAsyncCompletionStage(null)); + + // then + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL), + span -> + span.hasName(unproxiedTesterSimpleClassName + ".testAsyncCompletionStage") + .hasKind(INTERNAL) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + codeFunctionAssertions( + unproxiedTesterClassName, "testAsyncCompletionStage")))); + } + } + + @Nested + @DisplayName("with a method annotated with @WithSpan returns CompletableFuture") + class WithCompletableFuture { + + @Test + @DisplayName("should end Span on complete") + void onComplete() { + CompletableFuture future = new CompletableFuture<>(); + + // when + testing.runWithSpan("parent", () -> withSpanTester.testAsyncCompletableFuture(future)); + + // then + testing.waitAndAssertTraces( + trace -> + trace + .hasSize(1) + .hasSpansSatisfyingExactly(span -> span.hasName("parent").hasKind(INTERNAL))); + + // when + future.complete("DONE"); + + // then + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL), + span -> + span.hasName(unproxiedTesterSimpleClassName + ".testAsyncCompletableFuture") + .hasKind(INTERNAL) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + codeFunctionAssertions( + unproxiedTesterClassName, "testAsyncCompletableFuture")))); + } + + @Test + @DisplayName("should end Span on completeException AND should record the exception") + void onCompleteExceptionally() { + CompletableFuture future = new CompletableFuture<>(); + + // when + testing.runWithSpan("parent", () -> withSpanTester.testAsyncCompletableFuture(future)); + + // then + testing.waitAndAssertTraces( + trace -> + trace + .hasSize(1) + .hasSpansSatisfyingExactly(span -> span.hasName("parent").hasKind(INTERNAL))); + + // when + future.completeExceptionally(new Exception("Test @WithSpan With completeExceptionally")); + + // then + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL), + span -> + span.hasName(unproxiedTesterSimpleClassName + ".testAsyncCompletableFuture") + .hasKind(INTERNAL) + .hasStatus(StatusData.error()) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + codeFunctionAssertions( + unproxiedTesterClassName, "testAsyncCompletableFuture")))); + } + + @Test + @DisplayName("should end the Span when already complete") + void onCompletedFuture() { + CompletableFuture future = CompletableFuture.completedFuture("Done"); + + // when + testing.runWithSpan("parent", () -> withSpanTester.testAsyncCompletableFuture(future)); + + // then + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL), + span -> + span.hasName(unproxiedTesterSimpleClassName + ".testAsyncCompletableFuture") + .hasKind(INTERNAL) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + codeFunctionAssertions( + unproxiedTesterClassName, "testAsyncCompletableFuture")))); + } + + @Test + @DisplayName("should end the Span when already failed") + void onFailedFuture() { + CompletableFuture future = new CompletableFuture<>(); + future.completeExceptionally(new Exception("Test @WithSpan With completeExceptionally")); + + // when + testing.runWithSpan("parent", () -> withSpanTester.testAsyncCompletableFuture(future)); + + // then + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL), + span -> + span.hasName(unproxiedTesterSimpleClassName + ".testAsyncCompletableFuture") + .hasKind(INTERNAL) + .hasStatus(StatusData.error()) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + codeFunctionAssertions( + unproxiedTesterClassName, "testAsyncCompletableFuture")))); + } + + @Test + @DisplayName("should end Span on incompatible return value") + void onIncompatibleReturnValue() { + // when + testing.runWithSpan("parent", () -> withSpanTester.testAsyncCompletableFuture(null)); + + // then + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + parentSpan -> parentSpan.hasName("parent").hasKind(INTERNAL), + span -> + span.hasName(unproxiedTesterSimpleClassName + ".testAsyncCompletableFuture") + .hasKind(INTERNAL) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + codeFunctionAssertions( + unproxiedTesterClassName, "testAsyncCompletableFuture")))); + } + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java new file mode 100644 index 000000000000..5a32e2951d98 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfigurationTest.java @@ -0,0 +1,58 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.micrometer; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.micrometer.core.instrument.MeterRegistry; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.micrometer.v1_5.OpenTelemetryMeterRegistry; +import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +class MicrometerBridgeAutoConfigurationTest { + + private final ApplicationContextRunner runner = + new ApplicationContextRunner() + .withBean(OpenTelemetry.class, OpenTelemetry::noop) + .withConfiguration(AutoConfigurations.of(MicrometerBridgeAutoConfiguration.class)); + + @Test + void metricsEnabled() { + runner + .withConfiguration(AutoConfigurations.of(MetricsAutoConfiguration.class)) + .withPropertyValues("otel.instrumentation.micrometer.enabled = true") + .run( + context -> + assertThat(context.getBean("otelMeterRegistry", MeterRegistry.class)) + .isNotNull() + .isInstanceOf(OpenTelemetryMeterRegistry.class)); + } + + @Test + void metricsDisabledByDefault() { + runner + .withConfiguration(AutoConfigurations.of(MetricsAutoConfiguration.class)) + .run(context -> assertThat(context.containsBean("otelMeterRegistry")).isFalse()); + } + + @Test + void metricsDisabled() { + runner + .withConfiguration(AutoConfigurations.of(MetricsAutoConfiguration.class)) + .withPropertyValues("otel.instrumentation.micrometer.enabled = false") + .run(context -> assertThat(context.containsBean("otelMeterRegistry")).isFalse()); + } + + @Test + void noActuatorAutoConfiguration() { + runner + .withPropertyValues("otel.instrumentation.micrometer.enabled = true") + .run(context -> assertThat(context.containsBean("otelMeterRegistry")).isFalse()); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java new file mode 100644 index 000000000000..ffdd3c7d0181 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java @@ -0,0 +1,65 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.r2dbc; + +import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStable; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_STATEMENT; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; +import java.util.Collections; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.r2dbc.core.DatabaseClient; + +class R2DbcInstrumentationAutoConfigurationTest { + + @RegisterExtension + static final LibraryInstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + private final ApplicationContextRunner runner = + new ApplicationContextRunner() + .withBean( + ConfigProperties.class, + () -> DefaultConfigProperties.createFromMap(Collections.emptyMap())) + .withConfiguration( + AutoConfigurations.of( + R2dbcInstrumentationAutoConfiguration.class, R2dbcAutoConfiguration.class)) + .withBean("openTelemetry", OpenTelemetry.class, testing::getOpenTelemetry); + + @SuppressWarnings("deprecation") // using deprecated semconv + @Test + void statementSanitizerEnabledByDefault() { + runner.run( + context -> { + DatabaseClient client = context.getBean(DatabaseClient.class); + client + .sql( + "CREATE TABLE IF NOT EXISTS player(id INT NOT NULL AUTO_INCREMENT, name VARCHAR(255), age INT, PRIMARY KEY (id))") + .fetch() + .all() + .blockLast(); + client.sql("SELECT * FROM player WHERE id = 1").fetch().all().blockLast(); + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasAttribute( + maybeStable(DB_STATEMENT), + "CREATE TABLE IF NOT EXISTS player(id INT NOT NULL AUTO_INCREMENT, name VARCHAR(?), age INT, PRIMARY KEY (id))")), + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasAttribute( + maybeStable(DB_STATEMENT), "SELECT * FROM player WHERE id = ?"))); + }); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SchedulingInstrumentationAspectTest.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SchedulingInstrumentationAspectTest.java new file mode 100644 index 000000000000..d5fdb35b2bb8 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SchedulingInstrumentationAspectTest.java @@ -0,0 +1,192 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.scheduling; + +import static io.opentelemetry.api.trace.SpanKind.INTERNAL; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.code.SemconvCodeStabilityUtil; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; +import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; +import io.opentelemetry.sdk.trace.data.StatusData; +import java.util.Collections; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.aop.aspectj.annotation.AspectJProxyFactory; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.scheduling.annotation.Schedules; + +class SchedulingInstrumentationAspectTest { + + @RegisterExtension + static final LibraryInstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + private InstrumentationSchedulingTester schedulingTester; + private String unproxiedTesterSimpleClassName; + private String unproxiedTesterClassName; + + SpringSchedulingInstrumentationAspect newAspect( + OpenTelemetry openTelemetry, ConfigProperties configProperties) { + return new SpringSchedulingInstrumentationAspect(openTelemetry, configProperties); + } + + @BeforeEach + void setup() { + InstrumentationSchedulingTester unproxiedTester = + new InstrumentationSchedulingTester(testing.getOpenTelemetry()); + unproxiedTesterSimpleClassName = unproxiedTester.getClass().getSimpleName(); + unproxiedTesterClassName = unproxiedTester.getClass().getName(); + + AspectJProxyFactory factory = new AspectJProxyFactory(); + factory.setTarget(unproxiedTester); + + SpringSchedulingInstrumentationAspect aspect = + newAspect( + testing.getOpenTelemetry(), + DefaultConfigProperties.createFromMap(Collections.emptyMap())); + factory.addAspect(aspect); + + schedulingTester = factory.getProxy(); + } + + protected List assertCodeFunction(String method) { + return SemconvCodeStabilityUtil.codeFunctionAssertions(unproxiedTesterClassName, method); + } + + @Test + @DisplayName("when method is annotated with @Scheduled should start a new span.") + void scheduled() { + // when + schedulingTester.testScheduled(); + + // then + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName(unproxiedTesterSimpleClassName + ".testScheduled") + .hasKind(INTERNAL) + .hasAttributesSatisfyingExactly(assertCodeFunction("testScheduled")))); + } + + @Test + @DisplayName("when method is annotated with multiple @Scheduled should start a new span.") + void multiScheduled() { + // when + schedulingTester.testMultiScheduled(); + + // then + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName(unproxiedTesterSimpleClassName + ".testMultiScheduled") + .hasKind(INTERNAL) + .hasAttributesSatisfyingExactly(assertCodeFunction("testMultiScheduled")))); + } + + @Test + @DisplayName("when method is annotated with @Schedules should start a new span.") + void schedules() { + // when + schedulingTester.testSchedules(); + + // then + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName(unproxiedTesterSimpleClassName + ".testSchedules") + .hasKind(INTERNAL) + .hasAttributesSatisfyingExactly(assertCodeFunction("testSchedules")))); + } + + @Test + @DisplayName( + "when method is annotated with @Scheduled and it starts nested span, spans should be nested.") + void nestedSpanInScheduled() { + // when + schedulingTester.testNestedSpan(); + + // then + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName(unproxiedTesterSimpleClassName + ".testNestedSpan") + .hasKind(INTERNAL) + .hasAttributesSatisfyingExactly(assertCodeFunction("testNestedSpan")), + nestedSpan -> + nestedSpan.hasParent(trace.getSpan(0)).hasKind(INTERNAL).hasName("test"))); + } + + @Test + @DisplayName( + "when method is annotated with @Scheduled AND an exception is thrown span should record the exception") + void scheduledError() { + assertThatThrownBy(() -> schedulingTester.testScheduledWithException()) + .isInstanceOf(Exception.class); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName(unproxiedTesterSimpleClassName + ".testScheduledWithException") + .hasKind(INTERNAL) + .hasStatus(StatusData.error()) + .hasAttributesSatisfyingExactly( + assertCodeFunction("testScheduledWithException")))); + } + + static class InstrumentationSchedulingTester { + private final OpenTelemetry openTelemetry; + + InstrumentationSchedulingTester(OpenTelemetry openTelemetry) { + this.openTelemetry = openTelemetry; + } + + @Scheduled(fixedRate = 1L) + public void testScheduled() { + // no-op + } + + @Scheduled(fixedRate = 1L) + @Scheduled(fixedRate = 2L) + public void testMultiScheduled() { + // no-op + } + + @Schedules({@Scheduled(fixedRate = 1L), @Scheduled(fixedRate = 2L)}) + public void testSchedules() { + // no-op + } + + @Scheduled(fixedRate = 1L) + public void testNestedSpan() { + Context current = Context.current(); + Tracer tracer = openTelemetry.getTracer("test"); + try (Scope ignored = current.makeCurrent()) { + Span span = tracer.spanBuilder("test").startSpan(); + span.end(); + } + } + + @Scheduled(fixedRate = 1L) + public void testScheduledWithException() { + throw new IllegalStateException("something went wrong"); + } + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SchedulingInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SchedulingInstrumentationAutoConfigurationTest.java new file mode 100644 index 000000000000..37d91b411d5a --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SchedulingInstrumentationAutoConfigurationTest.java @@ -0,0 +1,46 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.scheduling; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; +import java.util.Collections; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +class SchedulingInstrumentationAutoConfigurationTest { + private final ApplicationContextRunner runner = + new ApplicationContextRunner() + .withBean(OpenTelemetry.class, OpenTelemetry::noop) + .withBean( + ConfigProperties.class, + () -> DefaultConfigProperties.createFromMap(Collections.emptyMap())) + .withConfiguration( + AutoConfigurations.of(SpringSchedulingInstrumentationAutoConfiguration.class)); + + @Test + void instrumentationEnabled() { + runner + .withPropertyValues("otel.instrumentation.spring-scheduling.enabled=true") + .run( + context -> + assertThat(context.containsBean("springSchedulingInstrumentationAspect")).isTrue()); + } + + @Test + void instrumentationDisabled() { + runner + .withPropertyValues("otel.instrumentation.spring-scheduling.enabled=false") + .run( + context -> + assertThat(context.containsBean("springSchedulingInstrumentationAspect")) + .isFalse()); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfigurationTest.java new file mode 100644 index 000000000000..53bbd882a6d2 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfigurationTest.java @@ -0,0 +1,101 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.web; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; +import java.util.Collections; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.web.client.RestTemplate; + +class SpringWebInstrumentationAutoConfigurationTest { + + private final ApplicationContextRunner contextRunner = + new ApplicationContextRunner() + .withBean(OpenTelemetry.class, OpenTelemetry::noop) + .withBean( + ConfigProperties.class, + () -> DefaultConfigProperties.createFromMap(Collections.emptyMap())) + .withBean(RestTemplate.class, RestTemplate::new) + .withConfiguration( + AutoConfigurations.of(SpringWebInstrumentationAutoConfiguration.class)); + + /** + * Tests that users create {@link RestTemplate} bean is instrumented. + * + *

{@code
+   * @Bean public RestTemplate restTemplate() {
+   *     return new RestTemplate();
+   * }
+   * }
+ */ + @Test + void instrumentationEnabled() { + contextRunner + .withPropertyValues("otel.instrumentation.spring-web.enabled=true") + .withPropertyValues("otel.instrumentation.common.default-enabled=false") + .run( + context -> { + assertThat( + context.getBean( + "otelRestTemplateBeanPostProcessor", RestTemplateBeanPostProcessor.class)) + .isNotNull(); + + assertThat( + context.getBean(RestTemplate.class).getInterceptors().stream() + .filter( + rti -> + rti.getClass() + .getName() + .startsWith("io.opentelemetry.instrumentation")) + .count()) + .isEqualTo(1); + }); + } + + @Test + void instrumentationDisabled() { + contextRunner + .withPropertyValues("otel.instrumentation.spring-web.enabled=false") + .run( + context -> + assertThat(context.containsBean("otelRestTemplateBeanPostProcessor")).isFalse()); + } + + @Test + void instrumentationDisabledButAllEnabled() { + contextRunner + .withPropertyValues("otel.instrumentation.spring-web.enabled=false") + .withPropertyValues("otel.instrumentation.common.default-enabled=true") + .run( + context -> + assertThat(context.containsBean("otelRestTemplateBeanPostProcessor")).isFalse()); + } + + @Test + void allInstrumentationDisabled() { + contextRunner + .withPropertyValues("otel.instrumentation.common.default-enabled=false") + .run( + context -> + assertThat(context.containsBean("otelRestTemplateBeanPostProcessor")).isFalse()); + } + + @Test + void defaultConfiguration() { + contextRunner.run( + context -> + assertThat( + context.getBean( + "otelRestTemplateBeanPostProcessor", RestTemplateBeanPostProcessor.class)) + .isNotNull()); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webflux/SpringWebfluxInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webflux/SpringWebfluxInstrumentationAutoConfigurationTest.java new file mode 100644 index 000000000000..d75814732785 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webflux/SpringWebfluxInstrumentationAutoConfigurationTest.java @@ -0,0 +1,59 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.webflux; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; +import java.util.Collections; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +class SpringWebfluxInstrumentationAutoConfigurationTest { + + private final ApplicationContextRunner contextRunner = + new ApplicationContextRunner() + .withBean(OpenTelemetry.class, OpenTelemetry::noop) + .withBean( + ConfigProperties.class, + () -> DefaultConfigProperties.createFromMap(Collections.emptyMap())) + .withConfiguration( + AutoConfigurations.of(SpringWebfluxInstrumentationAutoConfiguration.class)); + + @Test + void instrumentationEnabled() { + contextRunner + .withPropertyValues("otel.instrumentation.spring-webflux.enabled=true") + .run( + context -> + assertThat( + context.getBean( + "otelWebClientBeanPostProcessor", WebClientBeanPostProcessor.class)) + .isNotNull()); + } + + @Test + void instrumentationDisabled() { + contextRunner + .withPropertyValues("otel.instrumentation.spring-webflux.enabled=false") + .run( + context -> + assertThat(context.containsBean("otelWebClientBeanPostProcessor")).isFalse()); + } + + @Test + void defaultConfiguration() { + contextRunner.run( + context -> + assertThat( + context.getBean( + "otelWebClientBeanPostProcessor", WebClientBeanPostProcessor.class)) + .isNotNull()); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webflux/WebClientBeanPostProcessorTest.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webflux/WebClientBeanPostProcessorTest.java new file mode 100644 index 000000000000..821f0fbfb647 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webflux/WebClientBeanPostProcessorTest.java @@ -0,0 +1,118 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.webflux; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; +import java.util.Collections; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.web.reactive.function.client.ExchangeFilterFunction; +import org.springframework.web.reactive.function.client.WebClient; + +class WebClientBeanPostProcessorTest { + private static final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + + static { + beanFactory.registerSingleton("openTelemetry", OpenTelemetry.noop()); + beanFactory.registerSingleton( + "configProperties", DefaultConfigProperties.createFromMap(Collections.emptyMap())); + } + + @Test + @DisplayName( + "when processed bean is NOT of type WebClient or WebClientBuilder should return Object") + void returnsObject() { + BeanPostProcessor underTest = + new WebClientBeanPostProcessor( + beanFactory.getBeanProvider(OpenTelemetry.class), + beanFactory.getBeanProvider(ConfigProperties.class)); + + assertThat(underTest.postProcessAfterInitialization(new Object(), "testObject")) + .isExactlyInstanceOf(Object.class); + } + + @Test + @DisplayName("when processed bean is of type WebClient should return WebClient") + void returnsWebClient() { + BeanPostProcessor underTest = + new WebClientBeanPostProcessor( + beanFactory.getBeanProvider(OpenTelemetry.class), + beanFactory.getBeanProvider(ConfigProperties.class)); + + assertThat(underTest.postProcessAfterInitialization(WebClient.create(), "testWebClient")) + .isInstanceOf(WebClient.class); + } + + @Test + @DisplayName("when processed bean is of type WebClientBuilder should return WebClientBuilder") + void returnsWebClientBuilder() { + BeanPostProcessor underTest = + new WebClientBeanPostProcessor( + beanFactory.getBeanProvider(OpenTelemetry.class), + beanFactory.getBeanProvider(ConfigProperties.class)); + + assertThat( + underTest.postProcessAfterInitialization(WebClient.builder(), "testWebClientBuilder")) + .isInstanceOf(WebClient.Builder.class); + } + + @Test + @DisplayName("when processed bean is of type WebClient should add exchange filter to WebClient") + void addsExchangeFilterWebClient() { + BeanPostProcessor underTest = + new WebClientBeanPostProcessor( + beanFactory.getBeanProvider(OpenTelemetry.class), + beanFactory.getBeanProvider(ConfigProperties.class)); + + WebClient webClient = WebClient.create(); + Object processedWebClient = + underTest.postProcessAfterInitialization(webClient, "testWebClient"); + + assertThat(processedWebClient).isInstanceOf(WebClient.class); + ((WebClient) processedWebClient) + .mutate() + .filters( + functions -> + assertThat( + functions.stream() + .filter(WebClientBeanPostProcessorTest::isOtelExchangeFilter) + .count()) + .isEqualTo(1)); + } + + @Test + @DisplayName( + "when processed bean is of type WebClientBuilder should add ONE exchange filter to WebClientBuilder") + void addsExchangeFilterWebClientBuilder() { + BeanPostProcessor underTest = + new WebClientBeanPostProcessor( + beanFactory.getBeanProvider(OpenTelemetry.class), + beanFactory.getBeanProvider(ConfigProperties.class)); + + WebClient.Builder webClientBuilder = WebClient.builder(); + underTest.postProcessAfterInitialization(webClientBuilder, "testWebClientBuilder"); + underTest.postProcessAfterInitialization(webClientBuilder, "testWebClientBuilder"); + underTest.postProcessAfterInitialization(webClientBuilder, "testWebClientBuilder"); + + webClientBuilder.filters( + functions -> + assertThat( + functions.stream() + .filter(WebClientBeanPostProcessorTest::isOtelExchangeFilter) + .count()) + .isEqualTo(1)); + } + + private static boolean isOtelExchangeFilter(ExchangeFilterFunction wctf) { + return wctf.getClass().getName().startsWith("io.opentelemetry.instrumentation"); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webmvc/SpringWebMvcInstrumentation5AutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webmvc/SpringWebMvcInstrumentation5AutoConfigurationTest.java new file mode 100644 index 000000000000..62112774516e --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webmvc/SpringWebMvcInstrumentation5AutoConfigurationTest.java @@ -0,0 +1,56 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.webmvc; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assumptions.assumeFalse; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; +import java.util.Collections; +import javax.servlet.Filter; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +class SpringWebMvcInstrumentation5AutoConfigurationTest { + + private final ApplicationContextRunner contextRunner = + new ApplicationContextRunner() + .withBean(OpenTelemetry.class, OpenTelemetry::noop) + .withBean( + ConfigProperties.class, + () -> DefaultConfigProperties.createFromMap(Collections.emptyMap())) + .withConfiguration( + AutoConfigurations.of(SpringWebMvc5InstrumentationAutoConfiguration.class)); + + @BeforeEach + void setUp() { + assumeFalse(Boolean.getBoolean("testLatestDeps")); + } + + @Test + void instrumentationEnabled() { + contextRunner + .withPropertyValues("otel.instrumentation.spring-webmvc.enabled=true") + .run(context -> assertThat(context.getBean("otelWebMvcFilter", Filter.class)).isNotNull()); + } + + @Test + void instrumentationDisabled() { + contextRunner + .withPropertyValues("otel.instrumentation.spring-webmvc.enabled=false") + .run(context -> assertThat(context.containsBean("otelWebMvcFilter")).isFalse()); + } + + @Test + void defaultConfiguration() { + contextRunner.run( + context -> assertThat(context.getBean("otelWebMvcFilter", Filter.class)).isNotNull()); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigPropertiesTest.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigPropertiesTest.java new file mode 100644 index 000000000000..9754ccb3b7ad --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigPropertiesTest.java @@ -0,0 +1,180 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.ConfigDataApplicationContextInitializer; +import org.springframework.boot.test.context.assertj.AssertableApplicationContext; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.core.env.Environment; +import org.springframework.expression.spel.standard.SpelExpressionParser; + +class SpringConfigPropertiesTest { + private final ApplicationContextRunner contextRunner = + new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class)) + .withPropertyValues( + "otel.traces.exporter=none", "otel.metrics.exporter=none", "otel.logs.exporter=none"); + + public static Stream headerKeys() { + return Arrays.stream( + new String[] { + "otel.exporter.otlp.traces.headers", + "otel.exporter.otlp.metrics.headers", + "otel.exporter.otlp.logs.headers", + "otel.exporter.otlp.headers", + }) + .map(Arguments::of); + } + + @Test + @DisplayName("test all property types") + void allTypes() { + this.contextRunner + .withPropertyValues( + "otel.exporter.otlp.enabled=true", + "otel.exporter.otlp.timeout=1s", + "otel.exporter.otlp.compression=gzip") + .run( + context -> { + ConfigProperties config = getConfig(context); + assertThat(config.getString("otel.exporter.otlp.compression")).isEqualTo("gzip"); + assertThat(config.getBoolean("otel.exporter.otlp.enabled")).isTrue(); + assertThat(config.getDuration("otel.exporter.otlp.timeout")) + .isEqualByComparingTo(java.time.Duration.ofSeconds(1)); + }); + } + + @ParameterizedTest + @MethodSource("headerKeys") + @DisplayName("should map headers from spring properties") + void mapFlatHeaders(String key) { + this.contextRunner + .withSystemProperties(key + "=a=1,b=2") + .run( + context -> + assertThat(getConfig(context).getMap(key)) + .containsExactly(entry("a", "1"), entry("b", "2"))); + } + + @ParameterizedTest + @MethodSource("headerKeys") + @DisplayName("should map headers from spring properties with user supplied OpenTelemetry bean") + void mapFlatHeadersWithUserSuppliedOtelBean(String key) { + this.contextRunner + .withSystemProperties(key + "=a=1,b=2") + .withBean(OpenTelemetry.class, OpenTelemetry::noop) + .run( + context -> + assertThat(getConfig(context).getMap(key)) + .containsExactly(entry("a", "1"), entry("b", "2"))); + } + + @ParameterizedTest + @MethodSource("headerKeys") + @DisplayName("should map headers from spring application.yaml") + void mapObjectHeaders(String key) { + this.contextRunner + .withPropertyValues(key + ".a=1", key + ".b=2") + .run( + context -> + assertThat(getConfig(context).getMap(key)) + .containsExactly(entry("a", "1"), entry("b", "2"))); + } + + public static Stream listProperties() { + return Stream.of( + Arguments.of("otel.experimental.metrics.view.config", Arrays.asList("a", "b")), + Arguments.of("otel.experimental.resource.disabled.keys", Arrays.asList("a", "b")), + Arguments.of("otel.propagators", Arrays.asList("baggage", "b3")), + Arguments.of("otel.logs.exporter", Collections.singletonList("console")), + Arguments.of("otel.metrics.exporter", Collections.singletonList("console")), + Arguments.of("otel.traces.exporter", Collections.singletonList("console")), + Arguments.of( + "otel.instrumentation.http.client.capture-request-headers", Arrays.asList("a", "b")), + Arguments.of( + "otel.instrumentation.http.client.capture-response-headers", Arrays.asList("a", "b")), + Arguments.of( + "otel.instrumentation.http.server.capture-request-headers", Arrays.asList("a", "b")), + Arguments.of( + "otel.instrumentation.http.server.capture-response-headers", Arrays.asList("a", "b")), + Arguments.of("otel.instrumentation.http.known-methods", Arrays.asList("a", "b"))); + } + + @ParameterizedTest + @MethodSource("listProperties") + @DisplayName("should map list from application.yaml list") + // See the application.yaml file + void listsShouldWorkWithYaml(String key, List expected) { + new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class)) + .withInitializer(new ConfigDataApplicationContextInitializer()) + .run( + context -> + assertThat(getConfig(context).getList(key)) + .containsExactlyInAnyOrderElementsOf(expected)); + } + + @Test + @DisplayName("when map is set in properties in a row it should be available in config") + void shouldInitializeAttributesByMap() { + this.contextRunner + .withPropertyValues( + "otel.resource.attributes.environment=dev", + "otel.resource.attributes.xyz=foo", + "otel.resource.attributes.service.instance.id=id-example") + .run( + context -> { + Map fallback = new HashMap<>(); + fallback.put("fallback", "fallbackVal"); + fallback.put("otel.resource.attributes", "foo=fallback"); + + SpringConfigProperties config = getConfig(context, fallback); + + assertThat(config.getMap("otel.resource.attributes")) + .contains( + entry("environment", "dev"), + entry("xyz", "foo"), + entry("service.instance.id", "id-example"), + entry("foo", "fallback")); + + assertThat(config.getString("fallback")).isEqualTo("fallbackVal"); + }); + } + + private static ConfigProperties getConfig(AssertableApplicationContext context) { + return getConfig(context, Collections.emptyMap()); + } + + private static SpringConfigProperties getConfig( + AssertableApplicationContext context, Map fallback) { + return new SpringConfigProperties( + context.getBean("environment", Environment.class), + new SpelExpressionParser(), + context.getBean(OtlpExporterProperties.class), + context.getBean(OtelResourceProperties.class), + context.getBean(OtelSpringProperties.class), + DefaultConfigProperties.createFromMap(fallback)); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/resources/DistroVersionResourceProviderTest.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/resources/DistroVersionResourceProviderTest.java new file mode 100644 index 000000000000..947866cc0480 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/resources/DistroVersionResourceProviderTest.java @@ -0,0 +1,51 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.resources; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; +import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; +import java.util.Collections; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +public class DistroVersionResourceProviderTest { + private final ApplicationContextRunner contextRunner = + new ApplicationContextRunner() + .withPropertyValues( + "otel.traces.exporter=none", "otel.metrics.exporter=none", "otel.logs.exporter=none") + .withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class)); + + @Test + @DisplayName("distro version should be set") + void hasAttributes() { + + this.contextRunner.run( + context -> { + ResourceProvider resource = + context.getBean("otelDistroVersionResourceProvider", ResourceProvider.class); + + assertThat( + resource + .createResource(DefaultConfigProperties.createFromMap(Collections.emptyMap())) + .getAttributes() + .asMap()) + .containsEntry( + AttributeKey.stringKey("telemetry.distro.name"), + "opentelemetry-spring-boot-starter") + .anySatisfy( + (key, val) -> { + assertThat(key.getKey()).isEqualTo("telemetry.distro.version"); + assertThat(val).asString().isNotBlank(); + }); + }); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/resources/SpringResourceProviderTest.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/resources/SpringResourceProviderTest.java new file mode 100644 index 000000000000..5de21d9bcc9d --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/resources/SpringResourceProviderTest.java @@ -0,0 +1,80 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.resources; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.semconv.ServiceAttributes.SERVICE_NAME; +import static io.opentelemetry.semconv.ServiceAttributes.SERVICE_VERSION; + +import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.OtelResourceProperties; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.OtelSpringProperties; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.OtlpExporterProperties; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.SpringConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; +import io.opentelemetry.sdk.testing.assertj.AttributesAssert; +import java.util.Collections; +import java.util.Properties; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.info.BuildProperties; +import org.springframework.boot.test.context.assertj.AssertableApplicationContext; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.core.env.Environment; + +public class SpringResourceProviderTest { + + private final ApplicationContextRunner contextRunner = + new ApplicationContextRunner() + .withPropertyValues( + "otel.traces.exporter=none", "otel.metrics.exporter=none", "otel.logs.exporter=none") + .withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class)); + + @Test + @DisplayName( + "when spring.application.name is set value should be passed to service name attribute") + void shouldDetermineServiceNameBySpringApplicationName() { + this.contextRunner + .withPropertyValues("spring.application.name=myapp-backend") + .run( + context -> + assertResourceAttributes(context).containsEntry(SERVICE_NAME, "myapp-backend")); + } + + @Test + @DisplayName( + "when spring.application.name is set value should be passed to service name attribute") + void shouldDetermineServiceNameAndVersionBySpringApplicationVersion() { + Properties properties = new Properties(); + properties.put("name", "demo"); + properties.put("version", "0.3"); + this.contextRunner + .withBean("buildProperties", BuildProperties.class, () -> new BuildProperties(properties)) + .run( + context -> + assertResourceAttributes(context) + .containsEntry(SERVICE_NAME, "demo") + .containsEntry(SERVICE_VERSION, "0.3")); + } + + private static AttributesAssert assertResourceAttributes(AssertableApplicationContext context) { + ConfigProperties configProperties = + SpringConfigProperties.create( + context.getBean(Environment.class), + new OtlpExporterProperties(), + new OtelResourceProperties(), + new OtelSpringProperties(), + DefaultConfigProperties.createFromMap(Collections.emptyMap())); + + return assertThat( + context + .getBean(SpringResourceProvider.class) + .createResource(configProperties) + .getAttributes()); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/resources/application.yaml b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/resources/application.yaml new file mode 100644 index 000000000000..c9485005a6a7 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/test/resources/application.yaml @@ -0,0 +1,25 @@ +otel: + experimental: + metrics: + view: + config: [ a,b ] + resource: + disabled: + keys: [ a,b ] + propagators: [ baggage, b3 ] + logs: + exporter: [ console ] + metrics: + exporter: [ console ] + traces: + exporter: [ console ] + + instrumentation: + http: + known-methods: [ a,b ] + client: + capture-request-headers: [ a,b ] + capture-response-headers: [ a,b ] + server: + capture-request-headers: [ a,b ] + capture-response-headers: [ a,b ] diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/testLogbackAppender/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/CustomListAppender.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/testLogbackAppender/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/CustomListAppender.java new file mode 100644 index 000000000000..c2ecb450282b --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/testLogbackAppender/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/CustomListAppender.java @@ -0,0 +1,24 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.logging; + +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.read.ListAppender; + +@SuppressWarnings("OtelInternalJavadoc") +public class CustomListAppender extends ListAppender { + public static boolean lastLogHadTraceId; + + @Override + protected void append(ILoggingEvent event) { + // Since list appender just captures the event object it is possible that the trace_id is not + // present when list appender was called but is added at a later time. Here we record whether + // trace_id was present in mdc at the time when the event was processed by the list appender. + // https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/13383 + lastLogHadTraceId = event.getMDCPropertyMap().get("trace_id") != null; + super.append(event); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/testLogbackAppender/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/LogbackAppenderTest.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/testLogbackAppender/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/LogbackAppenderTest.java new file mode 100644 index 000000000000..53d841d30bec --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/testLogbackAppender/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/LogbackAppenderTest.java @@ -0,0 +1,266 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.logging; + +import static org.assertj.core.api.Assertions.assertThat; + +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.Appender; +import ch.qos.logback.core.read.ListAppender; +import ch.qos.logback.core.spi.AppenderAttachable; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.baggage.Baggage; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender; +import io.opentelemetry.instrumentation.testing.internal.AutoCleanupExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import io.opentelemetry.sdk.logs.data.LogRecordData; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; +import org.springframework.boot.SpringApplication; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +class LogbackAppenderTest { + + @RegisterExtension + static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @RegisterExtension static final AutoCleanupExtension cleanup = AutoCleanupExtension.create(); + + @BeforeEach + void setUp() { + // reset the appender + OpenTelemetryAppender.install(null); + } + + @Configuration + static class TestingOpenTelemetryConfiguration { + + @Bean + public OpenTelemetry openTelemetry() { + return testing.getOpenTelemetry(); + } + } + + @Test + void shouldInitializeAppender() { + Map properties = new HashMap<>(); + properties.put("logging.config", "classpath:logback-test.xml"); + properties.put( + "otel.instrumentation.logback-appender.experimental.capture-mdc-attributes", "*"); + properties.put( + "otel.instrumentation.logback-appender.experimental.capture-code-attributes", false); + + SpringApplication app = + new SpringApplication( + TestingOpenTelemetryConfiguration.class, OpenTelemetryAppenderAutoConfiguration.class); + app.setDefaultProperties(properties); + ConfigurableApplicationContext context = app.run(); + cleanup.deferCleanup(context); + + ListAppender listAppender = getListAppender(); + listAppender.list.clear(); + + MDC.put("key1", "val1"); + MDC.put("key2", "val2"); + try { + LoggerFactory.getLogger("test").info("test log message"); + } finally { + MDC.clear(); + } + + List logRecords = testing.logRecords(); + assertThat(logRecords) + .satisfiesOnlyOnce( + // OTel appender automatically added or from an XML file, it should not + // be added a second time by LogbackAppenderApplicationListener + logRecord -> { + assertThat(logRecord.getInstrumentationScopeInfo().getName()).isEqualTo("test"); + assertThat(logRecord.getBodyValue().asString()).contains("test log message"); + + Attributes attributes = logRecord.getAttributes(); + // key1 and key2, the code attributes should not be present because they are enabled + // in the logback.xml file but are disabled with a property + assertThat(attributes.size()).isEqualTo(2); + assertThat(attributes.asMap()) + .containsEntry(AttributeKey.stringKey("key1"), "val1") + .containsEntry(AttributeKey.stringKey("key2"), "val2"); + }); + + assertThat(listAppender.list) + .satisfiesExactly( + event -> + assertThat(event) + .satisfies( + e -> assertThat(e.getMessage()).isEqualTo("test log message"), + e -> assertThat(e.getMDCPropertyMap()).containsOnlyKeys("key1", "key2"))); + } + + @Test + void shouldNotInitializeAppenderWhenDisabled() { + Map properties = new HashMap<>(); + properties.put("logging.config", "classpath:logback-test.xml"); + properties.put("otel.instrumentation.logback-appender.enabled", "false"); + + SpringApplication app = + new SpringApplication( + TestingOpenTelemetryConfiguration.class, OpenTelemetryAppenderAutoConfiguration.class); + app.setDefaultProperties(properties); + ConfigurableApplicationContext context = app.run(); + cleanup.deferCleanup(context); + + LoggerFactory.getLogger("test").info("test log message"); + + assertThat(testing.logRecords()).isEmpty(); + } + + @Test + void mdcAppender() { + Map properties = new HashMap<>(); + properties.put("logging.config", "classpath:logback-test.xml"); + properties.put("otel.instrumentation.logback-appender.enabled", "false"); + properties.put("otel.instrumentation.logback-mdc.add-baggage", "true"); + properties.put("otel.instrumentation.common.logging.trace-id", "traceid"); + properties.put("otel.instrumentation.common.logging.span-id", "spanid"); + properties.put("otel.instrumentation.common.logging.trace-flags", "traceflags"); + + SpringApplication app = + new SpringApplication( + TestingOpenTelemetryConfiguration.class, OpenTelemetryAppenderAutoConfiguration.class); + app.setDefaultProperties(properties); + ConfigurableApplicationContext context = app.run(); + cleanup.deferCleanup(context); + + ListAppender listAppender = getListAppender(); + listAppender.list.clear(); + + try (Scope ignore = Baggage.current().toBuilder().put("key", "value").build().makeCurrent()) { + Span span = testing.getOpenTelemetry().getTracer("test").spanBuilder("test").startSpan(); + try (Scope ignore2 = span.makeCurrent()) { + LoggerFactory.getLogger("test").info("test log message"); + } + } + + assertThat(testing.logRecords()).isEmpty(); + assertThat(listAppender.list) + .satisfiesExactly( + event -> + assertThat(event) + .satisfies( + e -> assertThat(e.getMessage()).isEqualTo("test log message"), + e -> + assertThat(e.getMDCPropertyMap()) + .containsOnlyKeys( + "traceid", "spanid", "traceflags", "baggage.key"))); + } + + @Test + void shouldInitializeMdcAppender() { + Map properties = new HashMap<>(); + properties.put("logging.config", "classpath:logback-no-otel-appenders.xml"); + properties.put("otel.instrumentation.logback-appender.enabled", "false"); + + SpringApplication app = + new SpringApplication( + TestingOpenTelemetryConfiguration.class, OpenTelemetryAppenderAutoConfiguration.class); + app.setDefaultProperties(properties); + ConfigurableApplicationContext context = app.run(); + cleanup.deferCleanup(context); + + ListAppender listAppender = getListAppender(); + listAppender.list.clear(); + + Span span = testing.getOpenTelemetry().getTracer("test").spanBuilder("test").startSpan(); + try (Scope ignore = span.makeCurrent()) { + LoggerFactory.getLogger("test").info("test log message"); + } + + assertThat(testing.logRecords()).isEmpty(); + assertThat(CustomListAppender.lastLogHadTraceId).isTrue(); + assertThat(listAppender.list) + .satisfiesExactly( + event -> + assertThat(event) + .satisfies( + e -> assertThat(e.getMessage()).isEqualTo("test log message"), + e -> + assertThat(e.getMDCPropertyMap()) + .containsOnlyKeys("trace_id", "span_id", "trace_flags"))); + } + + @Test + void shouldNotInitializeMdcAppenderWhenDisabled() { + Map properties = new HashMap<>(); + properties.put("logging.config", "classpath:logback-no-otel-appenders.xml"); + properties.put("otel.instrumentation.logback-appender.enabled", "false"); + properties.put("otel.instrumentation.logback-mdc.enabled", "false"); + + SpringApplication app = + new SpringApplication( + TestingOpenTelemetryConfiguration.class, OpenTelemetryAppenderAutoConfiguration.class); + app.setDefaultProperties(properties); + ConfigurableApplicationContext context = app.run(); + cleanup.deferCleanup(context); + + ListAppender listAppender = getListAppender(); + listAppender.list.clear(); + + Span span = testing.getOpenTelemetry().getTracer("test").spanBuilder("test").startSpan(); + try (Scope ignore = span.makeCurrent()) { + LoggerFactory.getLogger("test").info("test log message"); + } + + assertThat(testing.logRecords()).isEmpty(); + assertThat(listAppender.list) + .satisfiesExactly( + event -> + assertThat(event) + .satisfies( + e -> assertThat(e.getMessage()).isEqualTo("test log message"), + e -> assertThat(e.getMDCPropertyMap()).isEmpty())); + } + + @SuppressWarnings("unchecked") + private static ListAppender getListAppender() { + Logger logger = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + ch.qos.logback.classic.Logger logbackLogger = (ch.qos.logback.classic.Logger) logger; + + ListAppender listAppender = + (ListAppender) logbackLogger.getAppender("List"); + if (listAppender != null) { + return listAppender; + } + + AppenderAttachable mdcAppender = + (AppenderAttachable) logbackLogger.getAppender("OpenTelemetryMdc"); + if (mdcAppender == null) { + for (Iterator> i = logbackLogger.iteratorForAppenders(); + i.hasNext(); ) { + Appender appender = i.next(); + if (appender + instanceof io.opentelemetry.instrumentation.logback.mdc.v1_0.OpenTelemetryAppender) { + mdcAppender = (AppenderAttachable) appender; + break; + } + } + } + return (ListAppender) mdcAppender.getAppender("List"); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/testLogbackAppender/resources/logback-no-otel-appenders.xml b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/testLogbackAppender/resources/logback-no-otel-appenders.xml new file mode 100644 index 000000000000..4d160ba2a8a7 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/testLogbackAppender/resources/logback-no-otel-appenders.xml @@ -0,0 +1,18 @@ + + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/testLogbackAppender/resources/logback-test.xml b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/testLogbackAppender/resources/logback-test.xml new file mode 100644 index 000000000000..f4f1d7e47070 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/testLogbackAppender/resources/logback-test.xml @@ -0,0 +1,27 @@ + + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + true + + + + + + + + + + + + + diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/testLogbackMissing/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/LogbackMissingTest.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/testLogbackMissing/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/LogbackMissingTest.java new file mode 100644 index 000000000000..c40d881bc94b --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/testLogbackMissing/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/LogbackMissingTest.java @@ -0,0 +1,26 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.logging; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.boot.SpringApplication; +import org.springframework.context.ConfigurableApplicationContext; + +class LogbackMissingTest { + + @Test + void applicationStartsWhenLogbackIsMissing() { + // verify that logback is not present + Assertions.assertThrows( + ClassNotFoundException.class, () -> Class.forName("ch.qos.logback.core.Appender")); + + SpringApplication app = new SpringApplication(OpenTelemetryAppenderAutoConfiguration.class); + try (ConfigurableApplicationContext ignore = app.run()) { + // do nothing + } + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/testSpring3/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestClientInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/testSpring3/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestClientInstrumentationAutoConfigurationTest.java new file mode 100644 index 000000000000..bae340b948ef --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/testSpring3/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestClientInstrumentationAutoConfigurationTest.java @@ -0,0 +1,87 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.web; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; +import java.util.Collections; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.web.client.RestClient; + +class RestClientInstrumentationAutoConfigurationTest { + + private final ApplicationContextRunner contextRunner = + new ApplicationContextRunner() + .withBean(OpenTelemetry.class, OpenTelemetry::noop) + .withBean( + ConfigProperties.class, + () -> DefaultConfigProperties.createFromMap(Collections.emptyMap())) + .withBean(RestClient.class, RestClient::create) + .withConfiguration( + AutoConfigurations.of(RestClientInstrumentationAutoConfiguration.class)); + + /** + * Tests the case that users create a {@link RestClient} bean themselves. + * + *
{@code
+   * @Bean public RestClient restClient() {
+   *     return new RestClient();
+   * }
+   * }
+ */ + @Test + void instrumentationEnabled() { + contextRunner + .withPropertyValues("otel.instrumentation.spring-web.enabled=true") + .run( + context -> { + assertThat( + context.getBean( + "otelRestClientBeanPostProcessor", RestClientBeanPostProcessor.class)) + .isNotNull(); + + context + .getBean(RestClient.class) + .mutate() + .requestInterceptors( + interceptors -> { + long count = + interceptors.stream() + .filter( + rti -> + rti.getClass() + .getName() + .startsWith("io.opentelemetry.instrumentation")) + .count(); + assertThat(count).isEqualTo(1); + }); + }); + } + + @Test + void instrumentationDisabled() { + contextRunner + .withPropertyValues("otel.instrumentation.spring-web.enabled=false") + .run( + context -> + assertThat(context.containsBean("otelRestClientBeanPostProcessor")).isFalse()); + } + + @Test + void defaultConfiguration() { + contextRunner.run( + context -> + assertThat( + context.getBean( + "otelRestClientBeanPostProcessor", RestClientBeanPostProcessor.class)) + .isNotNull()); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/testSpring3/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webmvc/SpringWebMvcInstrumentation6AutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/testSpring3/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webmvc/SpringWebMvcInstrumentation6AutoConfigurationTest.java new file mode 100644 index 000000000000..d064262095c6 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/testSpring3/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webmvc/SpringWebMvcInstrumentation6AutoConfigurationTest.java @@ -0,0 +1,56 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.webmvc; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; +import jakarta.servlet.Filter; +import java.util.Collections; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +class SpringWebMvcInstrumentation6AutoConfigurationTest { + + private final ApplicationContextRunner contextRunner = + new ApplicationContextRunner() + .withBean(OpenTelemetry.class, OpenTelemetry::noop) + .withBean( + ConfigProperties.class, + () -> DefaultConfigProperties.createFromMap(Collections.emptyMap())) + .withConfiguration( + AutoConfigurations.of(SpringWebMvc6InstrumentationAutoConfiguration.class)); + + @BeforeEach + void setUp() { + assumeTrue(Boolean.getBoolean("testLatestDeps")); + } + + @Test + void instrumentationEnabled() { + this.contextRunner + .withPropertyValues("otel.instrumentation.spring-webmvc.enabled=true") + .run(context -> assertThat(context.getBean("otelWebMvcFilter", Filter.class)).isNotNull()); + } + + @Test + void instrumentationDisabled() { + this.contextRunner + .withPropertyValues("otel.instrumentation.spring-webmvc.enabled=false") + .run(context -> assertThat(context.containsBean("otelWebMvcFilter")).isFalse()); + } + + @Test + void defaultConfiguration() { + this.contextRunner.run( + context -> assertThat(context.getBean("otelWebMvcFilter", Filter.class)).isNotNull()); + } +} diff --git a/instrumentation/spring/starters/spring-boot-starter-4.0/README.md b/instrumentation/spring/starters/spring-boot-starter-4.0/README.md new file mode 100644 index 000000000000..ce00f4fe00f6 --- /dev/null +++ b/instrumentation/spring/starters/spring-boot-starter-4.0/README.md @@ -0,0 +1,3 @@ +# OpenTelemetry Spring Starter 4.0 + +Documentation of the OpenTelemetry Spring Starter 4.0 is [here](https://opentelemetry.io/docs/zero-code/java/spring-boot/). diff --git a/instrumentation/spring/starters/spring-boot-starter-4.0/build.gradle.kts b/instrumentation/spring/starters/spring-boot-starter-4.0/build.gradle.kts new file mode 100644 index 000000000000..01939022cf1b --- /dev/null +++ b/instrumentation/spring/starters/spring-boot-starter-4.0/build.gradle.kts @@ -0,0 +1,29 @@ +plugins { + id("otel.java-conventions") + id("otel.publish-conventions") + id("otel.japicmp-conventions") +} + +group = "io.opentelemetry.instrumentation" + +val springBootVersion = "4.0.0-M1" + +dependencies { + compileOnly("org.springframework.boot:spring-boot-starter:$springBootVersion") + compileOnly("org.springframework.boot:spring-boot-starter-aop:$springBootVersion") + api(project(":instrumentation:spring:spring-boot-autoconfigure-4.0")) + api(project(":instrumentation-annotations")) + implementation(project(":instrumentation:resources:library")) + implementation("io.opentelemetry:opentelemetry-sdk-extension-incubator") + api("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi") + api("io.opentelemetry:opentelemetry-api") + api("io.opentelemetry:opentelemetry-exporter-logging") + api("io.opentelemetry:opentelemetry-exporter-otlp") + api("io.opentelemetry:opentelemetry-sdk") + + implementation("io.opentelemetry.contrib:opentelemetry-azure-resources") + implementation("io.opentelemetry.contrib:opentelemetry-aws-resources") + implementation("io.opentelemetry.contrib:opentelemetry-gcp-resources") + implementation("io.opentelemetry.contrib:opentelemetry-cloudfoundry-resources") + implementation("io.opentelemetry.contrib:opentelemetry-baggage-processor") +} diff --git a/instrumentation/spring/starters/spring-boot-starter-4.0/gradle.properties b/instrumentation/spring/starters/spring-boot-starter-4.0/gradle.properties new file mode 100644 index 000000000000..3cd93a8bc48f --- /dev/null +++ b/instrumentation/spring/starters/spring-boot-starter-4.0/gradle.properties @@ -0,0 +1 @@ +otel.stable=false diff --git a/settings.gradle.kts b/settings.gradle.kts index e117dc1566ba..8506f879105a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -544,6 +544,7 @@ include(":instrumentation:spark-2.3:javaagent") include(":instrumentation:spring:spring-batch-3.0:javaagent") include(":instrumentation:spring:spring-boot-actuator-autoconfigure-2.0:javaagent") include(":instrumentation:spring:spring-boot-autoconfigure") +include(":instrumentation:spring:spring-boot-autoconfigure-4.0") include(":instrumentation:spring:spring-boot-resources:javaagent") include(":instrumentation:spring:spring-boot-resources:javaagent-unit-tests") include(":instrumentation:spring:spring-cloud-aws-3.0:javaagent") @@ -589,6 +590,7 @@ include(":instrumentation:spring:spring-webmvc:spring-webmvc-common:javaagent") include(":instrumentation:spring:spring-webmvc:spring-webmvc-common:testing") include(":instrumentation:spring:spring-ws-2.0:javaagent") include(":instrumentation:spring:starters:spring-boot-starter") +include(":instrumentation:spring:starters:spring-boot-starter-4.0") include(":instrumentation:spring:starters:zipkin-spring-boot-starter") include(":instrumentation:spymemcached-2.12:javaagent") include(":instrumentation:struts:struts-2.3:javaagent") From e8272952b84aed617c94403b27e540c5d8bc79ed Mon Sep 17 00:00:00 2001 From: otelbot <197425009+otelbot@users.noreply.github.com> Date: Tue, 12 Aug 2025 12:01:16 +0000 Subject: [PATCH 2/2] ./gradlew spotlessApply --- .../micrometer/MicrometerBridgeAutoConfiguration.java | 4 ++-- .../r2dbc/R2dbcInstrumentingPostProcessor.java | 3 ++- .../runtimemetrics/RuntimeMetricsAutoConfiguration.java | 2 +- .../RuntimeMetricsBeanRegistrationExcludeFilter.java | 1 - .../web/RestClientInstrumentationAutoConfiguration.java | 2 +- .../instrumentation/web/RestTemplateBeanPostProcessor.java | 4 +--- .../internal/properties/SpringConfigProperties.java | 2 +- 7 files changed, 8 insertions(+), 10 deletions(-) diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfiguration.java index 44114889be6d..8276f14f66a2 100644 --- a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/micrometer/MicrometerBridgeAutoConfiguration.java @@ -10,12 +10,12 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.micrometer.v1_5.OpenTelemetryMeterRegistry; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; -import org.springframework.boot.metrics.autoconfigure.CompositeMeterRegistryAutoConfiguration; -import org.springframework.boot.metrics.autoconfigure.MetricsAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.metrics.autoconfigure.CompositeMeterRegistryAutoConfiguration; +import org.springframework.boot.metrics.autoconfigure.MetricsAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2dbcInstrumentingPostProcessor.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2dbcInstrumentingPostProcessor.java index 8ee45bc4faa1..b60d57b40e0d 100644 --- a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2dbcInstrumentingPostProcessor.java +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2dbcInstrumentingPostProcessor.java @@ -30,7 +30,8 @@ class R2dbcInstrumentingPostProcessor implements BeanPostProcessor { @Override public Object postProcessAfterInitialization(Object bean, String beanName) { - if (bean instanceof ConnectionFactory connectionFactory && !ScopedProxyUtils.isScopedTarget(beanName)) { + if (bean instanceof ConnectionFactory connectionFactory + && !ScopedProxyUtils.isScopedTarget(beanName)) { return R2dbcTelemetry.builder(openTelemetryProvider.getObject()) .setStatementSanitizationEnabled( InstrumentationConfigUtil.isStatementSanitizationEnabled( diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/RuntimeMetricsAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/RuntimeMetricsAutoConfiguration.java index de4c8cf1fb16..0704dff424d5 100644 --- a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/RuntimeMetricsAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/RuntimeMetricsAutoConfiguration.java @@ -9,9 +9,9 @@ import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import jakarta.annotation.PreDestroy; import java.util.Comparator; import java.util.Optional; -import jakarta.annotation.PreDestroy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.context.event.ApplicationReadyEvent; diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/RuntimeMetricsBeanRegistrationExcludeFilter.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/RuntimeMetricsBeanRegistrationExcludeFilter.java index 25b0cc5e378b..e6b98f3fa675 100644 --- a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/RuntimeMetricsBeanRegistrationExcludeFilter.java +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/RuntimeMetricsBeanRegistrationExcludeFilter.java @@ -5,7 +5,6 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics; -import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics.Java17RuntimeMetricsProvider; import org.springframework.beans.factory.aot.BeanRegistrationExcludeFilter; import org.springframework.beans.factory.support.RegisteredBean; diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestClientInstrumentationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestClientInstrumentationAutoConfiguration.java index a96ae353fc85..9d21d0f6619b 100644 --- a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestClientInstrumentationAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestClientInstrumentationAutoConfiguration.java @@ -11,8 +11,8 @@ import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; import org.springframework.boot.restclient.RestClientCustomizer; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestClient; diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestTemplateBeanPostProcessor.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestTemplateBeanPostProcessor.java index f294db9a27eb..c7bdb67265a7 100644 --- a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestTemplateBeanPostProcessor.java +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestTemplateBeanPostProcessor.java @@ -31,8 +31,6 @@ public Object postProcessAfterInitialization(Object bean, String beanName) { } return RestTemplateInstrumentation.addIfNotPresent( - restTemplate, - openTelemetryProvider.getObject(), - configPropertiesProvider.getObject()); + restTemplate, openTelemetryProvider.getObject(), configPropertiesProvider.getObject()); } } diff --git a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigProperties.java b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigProperties.java index 1cd49ddaac1b..cca6ff9284eb 100644 --- a/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigProperties.java +++ b/instrumentation/spring/spring-boot-autoconfigure-4.0/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigProperties.java @@ -224,7 +224,7 @@ public Duration getDuration(String name) { .getDuration(name); } - @SuppressWarnings({ "unchecked", "StatementSwitchToExpressionSwitch" }) + @SuppressWarnings({"unchecked", "StatementSwitchToExpressionSwitch"}) @Override public Map getMap(String name) { Map otelSdkMap = otelSdkProperties.getMap(name);