Skip to content

Commit fe2c403

Browse files
adinauerlbloder
andauthored
POTEL 56 - Port Spring Boot 3 OpenTelemetry changes to Spring Boot 2 (#3876)
* port Spring Boot 3 otel changes to Spring Boot 2 * Apply suggestions from code review Co-authored-by: Lukas Bloder <[email protected]> --------- Co-authored-by: Lukas Bloder <[email protected]>
1 parent f3c4ab7 commit fe2c403

File tree

8 files changed

+178
-35
lines changed

8 files changed

+178
-35
lines changed

sentry-spring-boot/build.gradle.kts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,13 @@ dependencies {
6868
testImplementation(Config.Libs.springBootStarterSecurity)
6969
testImplementation(Config.Libs.springBootStarterAop)
7070
testImplementation(Config.Libs.springBootStarterQuartz)
71+
testImplementation(Config.Libs.OpenTelemetry.otelSdk)
72+
testImplementation(Config.Libs.OpenTelemetry.otelExtensionAutoconfigureSpi)
73+
testImplementation(Config.Libs.springBoot3StarterOpenTelemetry)
7174
testImplementation(projects.sentryOpentelemetry.sentryOpentelemetryCore)
75+
testImplementation(projects.sentryOpentelemetry.sentryOpentelemetryAgent)
76+
testImplementation(projects.sentryOpentelemetry.sentryOpentelemetryAgentcustomization)
77+
testImplementation(projects.sentryOpentelemetry.sentryOpentelemetryBootstrap)
7278
}
7379

7480
configure<SourceSetContainer> {

sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import graphql.GraphQLError;
55
import io.sentry.EventProcessor;
66
import io.sentry.IScopes;
7+
import io.sentry.ISpanFactory;
78
import io.sentry.ITransportFactory;
89
import io.sentry.InitPriority;
910
import io.sentry.Integration;
@@ -28,6 +29,8 @@
2829
import io.sentry.spring.checkin.SentryQuartzConfiguration;
2930
import io.sentry.spring.exception.SentryCaptureExceptionParameterPointcutConfiguration;
3031
import io.sentry.spring.exception.SentryExceptionParameterAdviceConfiguration;
32+
import io.sentry.spring.opentelemetry.SentryOpenTelemetryAgentWithoutAutoInitConfiguration;
33+
import io.sentry.spring.opentelemetry.SentryOpenTelemetryNoAgentConfiguration;
3134
import io.sentry.spring.tracing.SentryAdviceConfiguration;
3235
import io.sentry.spring.tracing.SentrySpanPointcutConfiguration;
3336
import io.sentry.spring.tracing.SentryTracingFilter;
@@ -115,10 +118,29 @@ static class HubConfiguration {
115118
return new InAppIncludesResolver();
116119
}
117120

121+
@Configuration(proxyBeanMethods = false)
122+
@Import(SentryOpenTelemetryAgentWithoutAutoInitConfiguration.class)
123+
@Open
124+
@ConditionalOnProperty(name = "sentry.auto-init", havingValue = "false")
125+
@ConditionalOnClass(name = {"io.sentry.opentelemetry.agent.AgentMarker"})
126+
static class OpenTelemetryAgentWithoutAutoInitConfiguration {}
127+
128+
@Configuration(proxyBeanMethods = false)
129+
@Import(SentryOpenTelemetryNoAgentConfiguration.class)
130+
@Open
131+
@ConditionalOnClass(
132+
name = {
133+
"io.opentelemetry.api.OpenTelemetry",
134+
"io.sentry.opentelemetry.SentryAutoConfigurationCustomizerProvider"
135+
})
136+
@ConditionalOnMissingClass("io.sentry.opentelemetry.agent.AgentMarker")
137+
static class OpenTelemetryNoAgentConfiguration {}
138+
118139
@Bean
119140
public @NotNull IScopes sentryHub(
120141
final @NotNull List<Sentry.OptionsConfiguration<SentryOptions>> optionsConfigurations,
121142
final @NotNull SentryProperties options,
143+
final @NotNull ObjectProvider<ISpanFactory> spanFactory,
122144
final @NotNull ObjectProvider<GitProperties> gitProperties) {
123145
optionsConfigurations.forEach(
124146
optionsConfiguration -> optionsConfiguration.configure(options));
@@ -128,6 +150,7 @@ static class HubConfiguration {
128150
options.setRelease(git.getCommitId());
129151
}
130152
});
153+
spanFactory.ifAvailable(options::setSpanFactory);
131154

132155
options.setSentryClientName(
133156
BuildConfig.SENTRY_SPRING_BOOT_SDK_NAME + "/" + BuildConfig.VERSION_NAME);
@@ -155,21 +178,6 @@ static class ContextTagsEventProcessorConfiguration {
155178
}
156179
}
157180

158-
@Configuration(proxyBeanMethods = false)
159-
@ConditionalOnProperty(name = "sentry.auto-init", havingValue = "false")
160-
@ConditionalOnClass(io.sentry.opentelemetry.OpenTelemetryLinkErrorEventProcessor.class)
161-
@SuppressWarnings("deprecation")
162-
@Open
163-
static class OpenTelemetryLinkErrorEventProcessorConfiguration {
164-
165-
@Bean
166-
@ConditionalOnMissingBean
167-
public @NotNull io.sentry.opentelemetry.OpenTelemetryLinkErrorEventProcessor
168-
openTelemetryLinkErrorEventProcessor() {
169-
return new io.sentry.opentelemetry.OpenTelemetryLinkErrorEventProcessor();
170-
}
171-
}
172-
173181
@Configuration(proxyBeanMethods = false)
174182
@Import(SentryGraphqlAutoConfiguration.class)
175183
@Open

sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentryAutoConfigurationTest.kt

Lines changed: 62 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.sentry.spring.boot
22

33
import com.acme.MainBootClass
4+
import io.opentelemetry.api.OpenTelemetry
45
import io.sentry.AsyncHttpTransportFactory
56
import io.sentry.Breadcrumb
67
import io.sentry.EventProcessor
@@ -12,10 +13,12 @@ import io.sentry.NoOpTransportFactory
1213
import io.sentry.SamplingContext
1314
import io.sentry.Sentry
1415
import io.sentry.SentryEvent
16+
import io.sentry.SentryIntegrationPackageStorage
1517
import io.sentry.SentryLevel
1618
import io.sentry.SentryOptions
1719
import io.sentry.checkEvent
18-
import io.sentry.opentelemetry.OpenTelemetryLinkErrorEventProcessor
20+
import io.sentry.opentelemetry.SentryAutoConfigurationCustomizerProvider
21+
import io.sentry.opentelemetry.agent.AgentMarker
1922
import io.sentry.protocol.SentryTransaction
2023
import io.sentry.protocol.User
2124
import io.sentry.quartz.SentryJobListener
@@ -727,43 +730,72 @@ class SentryAutoConfigurationTest {
727730
}
728731

729732
@Test
730-
fun `when OpenTelemetryLinkErrorEventProcessor is on the classpath and auto init off, creates OpenTelemetryLinkErrorEventProcessor`() {
733+
fun `when AgentMarker is on the classpath and auto init off, runs SentryOpenTelemetryAgentWithoutAutoInitConfiguration`() {
734+
SentryIntegrationPackageStorage.getInstance().clearStorage()
731735
contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj", "sentry.auto-init=false")
732736
.run {
733-
assertThat(it).hasSingleBean(OpenTelemetryLinkErrorEventProcessor::class.java)
734-
val options = it.getBean(SentryOptions::class.java)
735-
assertThat(options.eventProcessors).anyMatch { processor -> processor.javaClass == OpenTelemetryLinkErrorEventProcessor::class.java }
737+
assertTrue(SentryIntegrationPackageStorage.getInstance().integrations.contains("SpringBoot3OpenTelemetryAgentWithoutAutoInit"))
736738
}
737739
}
738740

739741
@Test
740-
fun `when OpenTelemetryLinkErrorEventProcessor is on the classpath but auto init on, does not create OpenTelemetryLinkErrorEventProcessor`() {
741-
contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj", "sentry.auto-init=true")
742+
fun `when AgentMarker is on the classpath and auto init on, does not run SentryOpenTelemetryAgentWithoutAutoInitConfiguration`() {
743+
SentryIntegrationPackageStorage.getInstance().clearStorage()
744+
contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj")
742745
.run {
743-
assertThat(it).doesNotHaveBean(OpenTelemetryLinkErrorEventProcessor::class.java)
744-
val options = it.getBean(SentryOptions::class.java)
745-
assertThat(options.eventProcessors).noneMatch { processor -> processor.javaClass == OpenTelemetryLinkErrorEventProcessor::class.java }
746+
assertFalse(SentryIntegrationPackageStorage.getInstance().integrations.contains("SpringBoot3OpenTelemetryAgentWithoutAutoInit"))
746747
}
747748
}
748749

749750
@Test
750-
fun `when OpenTelemetryLinkErrorEventProcessor is on the classpath but auto init default, does not create OpenTelemetryLinkErrorEventProcessor`() {
751+
fun `when AgentMarker is not on the classpath and auto init off, does not run SentryOpenTelemetryAgentWithoutAutoInitConfiguration`() {
752+
SentryIntegrationPackageStorage.getInstance().clearStorage()
753+
contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj", "sentry.auto-init=false")
754+
.withClassLoader(FilteredClassLoader(AgentMarker::class.java))
755+
.run {
756+
assertFalse(SentryIntegrationPackageStorage.getInstance().integrations.contains("SpringBoot3OpenTelemetryAgentWithoutAutoInit"))
757+
}
758+
}
759+
760+
@Test
761+
fun `when AgentMarker is not on the classpath but OpenTelemetry is, runs SpringBoot3OpenTelemetryNoAgent`() {
762+
SentryIntegrationPackageStorage.getInstance().clearStorage()
751763
contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj")
764+
.withClassLoader(FilteredClassLoader(AgentMarker::class.java))
765+
.withUserConfiguration(OtelBeanConfig::class.java)
752766
.run {
753-
assertThat(it).doesNotHaveBean(OpenTelemetryLinkErrorEventProcessor::class.java)
754-
val options = it.getBean(SentryOptions::class.java)
755-
assertThat(options.eventProcessors).noneMatch { processor -> processor.javaClass == OpenTelemetryLinkErrorEventProcessor::class.java }
767+
assertTrue(SentryIntegrationPackageStorage.getInstance().integrations.contains("SpringBoot3OpenTelemetryNoAgent"))
756768
}
757769
}
758770

759771
@Test
760-
fun `when OpenTelemetryLinkErrorEventProcessor is not on the classpath, does not create OpenTelemetryLinkErrorEventProcessor`() {
761-
contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj", "sentry.auto-init=false")
762-
.withClassLoader(FilteredClassLoader(OpenTelemetryLinkErrorEventProcessor::class.java))
772+
fun `when AgentMarker and OpenTelemetry are not on the classpath, does not run SpringBoot3OpenTelemetryNoAgent`() {
773+
SentryIntegrationPackageStorage.getInstance().clearStorage()
774+
contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj")
775+
.withClassLoader(FilteredClassLoader(AgentMarker::class.java, OpenTelemetry::class.java))
763776
.run {
764-
assertThat(it).doesNotHaveBean(OpenTelemetryLinkErrorEventProcessor::class.java)
765-
val options = it.getBean(SentryOptions::class.java)
766-
assertThat(options.eventProcessors).noneMatch { processor -> processor.javaClass == OpenTelemetryLinkErrorEventProcessor::class.java }
777+
assertFalse(SentryIntegrationPackageStorage.getInstance().integrations.contains("SpringBoot3OpenTelemetryNoAgent"))
778+
}
779+
}
780+
781+
@Test
782+
fun `when AgentMarker and SentryAutoConfigurationCustomizerProvider are not on the classpath, does not run SpringBoot3OpenTelemetryNoAgent`() {
783+
SentryIntegrationPackageStorage.getInstance().clearStorage()
784+
contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj")
785+
.withClassLoader(FilteredClassLoader(AgentMarker::class.java, SentryAutoConfigurationCustomizerProvider::class.java))
786+
.withUserConfiguration(OtelBeanConfig::class.java)
787+
.run {
788+
assertFalse(SentryIntegrationPackageStorage.getInstance().integrations.contains("SpringBoot3OpenTelemetryNoAgent"))
789+
}
790+
}
791+
792+
@Test
793+
fun `when AgentMarker is not on the classpath and auto init on, does not run SentryOpenTelemetryAgentWithoutAutoInitConfiguration`() {
794+
SentryIntegrationPackageStorage.getInstance().clearStorage()
795+
contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj")
796+
.withClassLoader(FilteredClassLoader(AgentMarker::class.java))
797+
.run {
798+
assertFalse(SentryIntegrationPackageStorage.getInstance().integrations.contains("SpringBoot3OpenTelemetryAgentWithoutAutoInit"))
767799
}
768800
}
769801

@@ -1013,6 +1045,16 @@ class SentryAutoConfigurationTest {
10131045
override fun sample(samplingContext: SamplingContext) = 1.0
10141046
}
10151047

1048+
/**
1049+
* this should be taken care of by the otel spring starter in a real application
1050+
*/
1051+
@Configuration
1052+
open class OtelBeanConfig {
1053+
1054+
@Bean
1055+
open fun openTelemetry() = OpenTelemetry.noop()
1056+
}
1057+
10161058
open class CustomSentryUserProvider : SentryUserProvider {
10171059
override fun provideUser(): User? {
10181060
val user = User()

sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/it/SentrySpringIntegrationTest.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package io.sentry.spring.boot.it
22

3+
import io.sentry.DefaultSpanFactory
34
import io.sentry.IScopes
45
import io.sentry.ITransportFactory
56
import io.sentry.Sentry
7+
import io.sentry.SentryOptions
68
import io.sentry.checkEvent
79
import io.sentry.checkTransaction
810
import io.sentry.spring.tracing.SentrySpan
@@ -223,6 +225,12 @@ open class App {
223225

224226
@Bean
225227
open fun mockTransport() = transport
228+
229+
@Bean
230+
open fun optionsCallback() = Sentry.OptionsConfiguration<SentryOptions> { options ->
231+
// due to OTel being on the classpath we need to set the default again
232+
options.spanFactory = DefaultSpanFactory()
233+
}
226234
}
227235

228236
@RestController

sentry-spring/api/sentry-spring.api

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,17 @@ public final class io/sentry/spring/graphql/SentrySpringSubscriptionHandler : io
194194
public fun onSubscriptionResult (Ljava/lang/Object;Lio/sentry/IScopes;Lio/sentry/graphql/ExceptionReporter;Lgraphql/execution/instrumentation/parameters/InstrumentationFieldFetchParameters;)Ljava/lang/Object;
195195
}
196196

197+
public class io/sentry/spring/opentelemetry/SentryOpenTelemetryAgentWithoutAutoInitConfiguration {
198+
public fun <init> ()V
199+
public fun sentryOpenTelemetryOptionsConfiguration ()Lio/sentry/Sentry$OptionsConfiguration;
200+
}
201+
202+
public class io/sentry/spring/opentelemetry/SentryOpenTelemetryNoAgentConfiguration {
203+
public fun <init> ()V
204+
public static fun openTelemetrySpanFactory (Lio/opentelemetry/api/OpenTelemetry;)Lio/sentry/ISpanFactory;
205+
public fun sentryOpenTelemetryOptionsConfiguration ()Lio/sentry/Sentry$OptionsConfiguration;
206+
}
207+
197208
public class io/sentry/spring/tracing/SentryAdviceConfiguration {
198209
public fun <init> ()V
199210
public fun sentrySpanAdvice ()Lorg/aopalliance/aop/Advice;

sentry-spring/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ dependencies {
3838
compileOnly(projects.sentryGraphql)
3939
compileOnly(Config.Libs.springBootStarterQuartz)
4040
compileOnly(projects.sentryQuartz)
41+
compileOnly(Config.Libs.OpenTelemetry.otelSdk)
42+
compileOnly(projects.sentryOpentelemetry.sentryOpentelemetryAgentcustomization)
43+
compileOnly(projects.sentryOpentelemetry.sentryOpentelemetryBootstrap)
4144

4245
compileOnly(Config.CompileOnly.nopen)
4346
errorprone(Config.CompileOnly.nopenChecker)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package io.sentry.spring.opentelemetry;
2+
3+
import com.jakewharton.nopen.annotation.Open;
4+
import io.sentry.Sentry;
5+
import io.sentry.SentryIntegrationPackageStorage;
6+
import io.sentry.SentryOptions;
7+
import io.sentry.opentelemetry.OpenTelemetryUtil;
8+
import org.jetbrains.annotations.NotNull;
9+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
10+
import org.springframework.context.annotation.Bean;
11+
import org.springframework.context.annotation.Configuration;
12+
13+
@Configuration(proxyBeanMethods = false)
14+
@Open
15+
public class SentryOpenTelemetryAgentWithoutAutoInitConfiguration {
16+
17+
@Bean
18+
@ConditionalOnMissingBean(name = "sentryOpenTelemetryOptionsConfiguration")
19+
public @NotNull Sentry.OptionsConfiguration<SentryOptions>
20+
sentryOpenTelemetryOptionsConfiguration() {
21+
return options -> {
22+
SentryIntegrationPackageStorage.getInstance()
23+
.addIntegration("SpringBootOpenTelemetryAgentWithoutAutoInit");
24+
OpenTelemetryUtil.applyOpenTelemetryOptions(options, true);
25+
};
26+
}
27+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package io.sentry.spring.opentelemetry;
2+
3+
import com.jakewharton.nopen.annotation.Open;
4+
import io.opentelemetry.api.OpenTelemetry;
5+
import io.sentry.ISpanFactory;
6+
import io.sentry.Sentry;
7+
import io.sentry.SentryIntegrationPackageStorage;
8+
import io.sentry.SentryOptions;
9+
import io.sentry.opentelemetry.OpenTelemetryUtil;
10+
import io.sentry.opentelemetry.OtelSpanFactory;
11+
import io.sentry.opentelemetry.SentryAutoConfigurationCustomizerProvider;
12+
import org.jetbrains.annotations.NotNull;
13+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
14+
import org.springframework.context.annotation.Bean;
15+
import org.springframework.context.annotation.Configuration;
16+
17+
@Configuration(proxyBeanMethods = false)
18+
@Open
19+
public class SentryOpenTelemetryNoAgentConfiguration {
20+
21+
@Bean
22+
@ConditionalOnMissingBean
23+
public static ISpanFactory openTelemetrySpanFactory(OpenTelemetry openTelemetry) {
24+
return new OtelSpanFactory(openTelemetry);
25+
}
26+
27+
@Bean
28+
@ConditionalOnMissingBean(name = "sentryOpenTelemetryOptionsConfiguration")
29+
public @NotNull Sentry.OptionsConfiguration<SentryOptions>
30+
sentryOpenTelemetryOptionsConfiguration() {
31+
return options -> {
32+
SentryIntegrationPackageStorage.getInstance()
33+
.addIntegration("SpringBootOpenTelemetryNoAgent");
34+
SentryAutoConfigurationCustomizerProvider.skipInit = true;
35+
OpenTelemetryUtil.applyOpenTelemetryOptions(options, false);
36+
};
37+
}
38+
}

0 commit comments

Comments
 (0)