Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions sentry-spring-boot/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,13 @@ dependencies {
testImplementation(Config.Libs.springBootStarterSecurity)
testImplementation(Config.Libs.springBootStarterAop)
testImplementation(Config.Libs.springBootStarterQuartz)
testImplementation(Config.Libs.OpenTelemetry.otelSdk)
testImplementation(Config.Libs.OpenTelemetry.otelExtensionAutoconfigureSpi)
testImplementation(Config.Libs.springBoot3StarterOpenTelemetry)
testImplementation(projects.sentryOpentelemetry.sentryOpentelemetryCore)
testImplementation(projects.sentryOpentelemetry.sentryOpentelemetryAgent)
testImplementation(projects.sentryOpentelemetry.sentryOpentelemetryAgentcustomization)
testImplementation(projects.sentryOpentelemetry.sentryOpentelemetryBootstrap)
}

configure<SourceSetContainer> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import graphql.GraphQLError;
import io.sentry.EventProcessor;
import io.sentry.IScopes;
import io.sentry.ISpanFactory;
import io.sentry.ITransportFactory;
import io.sentry.InitPriority;
import io.sentry.Integration;
Expand All @@ -28,6 +29,8 @@
import io.sentry.spring.checkin.SentryQuartzConfiguration;
import io.sentry.spring.exception.SentryCaptureExceptionParameterPointcutConfiguration;
import io.sentry.spring.exception.SentryExceptionParameterAdviceConfiguration;
import io.sentry.spring.opentelemetry.SentryOpenTelemetryAgentWithoutAutoInitConfiguration;
import io.sentry.spring.opentelemetry.SentryOpenTelemetryNoAgentConfiguration;
import io.sentry.spring.tracing.SentryAdviceConfiguration;
import io.sentry.spring.tracing.SentrySpanPointcutConfiguration;
import io.sentry.spring.tracing.SentryTracingFilter;
Expand Down Expand Up @@ -115,10 +118,29 @@ static class HubConfiguration {
return new InAppIncludesResolver();
}

@Configuration(proxyBeanMethods = false)
@Import(SentryOpenTelemetryAgentWithoutAutoInitConfiguration.class)
@Open
@ConditionalOnProperty(name = "sentry.auto-init", havingValue = "false")
@ConditionalOnClass(name = {"io.sentry.opentelemetry.agent.AgentMarker"})
static class OpenTelemetryAgentWithoutAutoInitConfiguration {}

@Configuration(proxyBeanMethods = false)
@Import(SentryOpenTelemetryNoAgentConfiguration.class)
@Open
@ConditionalOnClass(
name = {
"io.opentelemetry.api.OpenTelemetry",
"io.sentry.opentelemetry.SentryAutoConfigurationCustomizerProvider"
})
@ConditionalOnMissingClass("io.sentry.opentelemetry.agent.AgentMarker")
static class OpenTelemetryNoAgentConfiguration {}

@Bean
public @NotNull IScopes sentryHub(
final @NotNull List<Sentry.OptionsConfiguration<SentryOptions>> optionsConfigurations,
final @NotNull SentryProperties options,
final @NotNull ObjectProvider<ISpanFactory> spanFactory,
final @NotNull ObjectProvider<GitProperties> gitProperties) {
optionsConfigurations.forEach(
optionsConfiguration -> optionsConfiguration.configure(options));
Expand All @@ -128,6 +150,7 @@ static class HubConfiguration {
options.setRelease(git.getCommitId());
}
});
spanFactory.ifAvailable(options::setSpanFactory);

options.setSentryClientName(
BuildConfig.SENTRY_SPRING_BOOT_SDK_NAME + "/" + BuildConfig.VERSION_NAME);
Expand Down Expand Up @@ -155,21 +178,6 @@ static class ContextTagsEventProcessorConfiguration {
}
}

@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "sentry.auto-init", havingValue = "false")
@ConditionalOnClass(io.sentry.opentelemetry.OpenTelemetryLinkErrorEventProcessor.class)
@SuppressWarnings("deprecation")
@Open
static class OpenTelemetryLinkErrorEventProcessorConfiguration {

@Bean
@ConditionalOnMissingBean
public @NotNull io.sentry.opentelemetry.OpenTelemetryLinkErrorEventProcessor
openTelemetryLinkErrorEventProcessor() {
return new io.sentry.opentelemetry.OpenTelemetryLinkErrorEventProcessor();
}
}

@Configuration(proxyBeanMethods = false)
@Import(SentryGraphqlAutoConfiguration.class)
@Open
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.sentry.spring.boot

import com.acme.MainBootClass
import io.opentelemetry.api.OpenTelemetry
import io.sentry.AsyncHttpTransportFactory
import io.sentry.Breadcrumb
import io.sentry.EventProcessor
Expand All @@ -12,10 +13,12 @@ import io.sentry.NoOpTransportFactory
import io.sentry.SamplingContext
import io.sentry.Sentry
import io.sentry.SentryEvent
import io.sentry.SentryIntegrationPackageStorage
import io.sentry.SentryLevel
import io.sentry.SentryOptions
import io.sentry.checkEvent
import io.sentry.opentelemetry.OpenTelemetryLinkErrorEventProcessor
import io.sentry.opentelemetry.SentryAutoConfigurationCustomizerProvider
import io.sentry.opentelemetry.agent.AgentMarker
import io.sentry.protocol.SentryTransaction
import io.sentry.protocol.User
import io.sentry.quartz.SentryJobListener
Expand Down Expand Up @@ -727,43 +730,72 @@ class SentryAutoConfigurationTest {
}

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

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

@Test
fun `when OpenTelemetryLinkErrorEventProcessor is on the classpath but auto init default, does not create OpenTelemetryLinkErrorEventProcessor`() {
fun `when AgentMarker is not on the classpath and auto init off, does not run SentryOpenTelemetryAgentWithoutAutoInitConfiguration`() {
SentryIntegrationPackageStorage.getInstance().clearStorage()
contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj", "sentry.auto-init=false")
.withClassLoader(FilteredClassLoader(AgentMarker::class.java))
.run {
assertFalse(SentryIntegrationPackageStorage.getInstance().integrations.contains("SpringBoot3OpenTelemetryAgentWithoutAutoInit"))
}
}

@Test
fun `when AgentMarker is not on the classpath but OpenTelemetry is, runs SpringBoot3OpenTelemetryNoAgent`() {
SentryIntegrationPackageStorage.getInstance().clearStorage()
contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj")
.withClassLoader(FilteredClassLoader(AgentMarker::class.java))
.withUserConfiguration(OtelBeanConfig::class.java)
.run {
assertThat(it).doesNotHaveBean(OpenTelemetryLinkErrorEventProcessor::class.java)
val options = it.getBean(SentryOptions::class.java)
assertThat(options.eventProcessors).noneMatch { processor -> processor.javaClass == OpenTelemetryLinkErrorEventProcessor::class.java }
assertTrue(SentryIntegrationPackageStorage.getInstance().integrations.contains("SpringBoot3OpenTelemetryNoAgent"))
}
}

@Test
fun `when OpenTelemetryLinkErrorEventProcessor is not on the classpath, does not create OpenTelemetryLinkErrorEventProcessor`() {
contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj", "sentry.auto-init=false")
.withClassLoader(FilteredClassLoader(OpenTelemetryLinkErrorEventProcessor::class.java))
fun `when AgentMarker and OpenTelemetry are not on the classpath, does not run SpringBoot3OpenTelemetryNoAgent`() {
SentryIntegrationPackageStorage.getInstance().clearStorage()
contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj")
.withClassLoader(FilteredClassLoader(AgentMarker::class.java, OpenTelemetry::class.java))
.run {
assertThat(it).doesNotHaveBean(OpenTelemetryLinkErrorEventProcessor::class.java)
val options = it.getBean(SentryOptions::class.java)
assertThat(options.eventProcessors).noneMatch { processor -> processor.javaClass == OpenTelemetryLinkErrorEventProcessor::class.java }
assertFalse(SentryIntegrationPackageStorage.getInstance().integrations.contains("SpringBoot3OpenTelemetryNoAgent"))
}
}

@Test
fun `when AgentMarker and SentryAutoConfigurationCustomizerProvider are not on the classpath, does not run SpringBoot3OpenTelemetryNoAgent`() {
SentryIntegrationPackageStorage.getInstance().clearStorage()
contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj")
.withClassLoader(FilteredClassLoader(AgentMarker::class.java, SentryAutoConfigurationCustomizerProvider::class.java))
.withUserConfiguration(OtelBeanConfig::class.java)
.run {
assertFalse(SentryIntegrationPackageStorage.getInstance().integrations.contains("SpringBoot3OpenTelemetryNoAgent"))
}
}

@Test
fun `when AgentMarker is not on the classpath and auto init on, does not run SentryOpenTelemetryAgentWithoutAutoInitConfiguration`() {
SentryIntegrationPackageStorage.getInstance().clearStorage()
contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj")
.withClassLoader(FilteredClassLoader(AgentMarker::class.java))
.run {
assertFalse(SentryIntegrationPackageStorage.getInstance().integrations.contains("SpringBoot3OpenTelemetryAgentWithoutAutoInit"))
}
}

Expand Down Expand Up @@ -1013,6 +1045,16 @@ class SentryAutoConfigurationTest {
override fun sample(samplingContext: SamplingContext) = 1.0
}

/**
* this should be taken care of by the otel spring starter in a real application
*/
@Configuration
open class OtelBeanConfig {

@Bean
open fun openTelemetry() = OpenTelemetry.noop()
}

open class CustomSentryUserProvider : SentryUserProvider {
override fun provideUser(): User? {
val user = User()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package io.sentry.spring.boot.it

import io.sentry.DefaultSpanFactory
import io.sentry.IScopes
import io.sentry.ITransportFactory
import io.sentry.Sentry
import io.sentry.SentryOptions
import io.sentry.checkEvent
import io.sentry.checkTransaction
import io.sentry.spring.tracing.SentrySpan
Expand Down Expand Up @@ -223,6 +225,12 @@ open class App {

@Bean
open fun mockTransport() = transport

@Bean
open fun optionsCallback() = Sentry.OptionsConfiguration<SentryOptions> { options ->
// due to OTel being on the classpath we need to set the default again
options.spanFactory = DefaultSpanFactory()
}
}

@RestController
Expand Down
11 changes: 11 additions & 0 deletions sentry-spring/api/sentry-spring.api
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,17 @@ public final class io/sentry/spring/graphql/SentrySpringSubscriptionHandler : io
public fun onSubscriptionResult (Ljava/lang/Object;Lio/sentry/IScopes;Lio/sentry/graphql/ExceptionReporter;Lgraphql/execution/instrumentation/parameters/InstrumentationFieldFetchParameters;)Ljava/lang/Object;
}

public class io/sentry/spring/opentelemetry/SentryOpenTelemetryAgentWithoutAutoInitConfiguration {
public fun <init> ()V
public fun sentryOpenTelemetryOptionsConfiguration ()Lio/sentry/Sentry$OptionsConfiguration;
}

public class io/sentry/spring/opentelemetry/SentryOpenTelemetryNoAgentConfiguration {
public fun <init> ()V
public static fun openTelemetrySpanFactory (Lio/opentelemetry/api/OpenTelemetry;)Lio/sentry/ISpanFactory;
public fun sentryOpenTelemetryOptionsConfiguration ()Lio/sentry/Sentry$OptionsConfiguration;
}

public class io/sentry/spring/tracing/SentryAdviceConfiguration {
public fun <init> ()V
public fun sentrySpanAdvice ()Lorg/aopalliance/aop/Advice;
Expand Down
3 changes: 3 additions & 0 deletions sentry-spring/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ dependencies {
compileOnly(projects.sentryGraphql)
compileOnly(Config.Libs.springBootStarterQuartz)
compileOnly(projects.sentryQuartz)
compileOnly(Config.Libs.OpenTelemetry.otelSdk)
compileOnly(projects.sentryOpentelemetry.sentryOpentelemetryAgentcustomization)
compileOnly(projects.sentryOpentelemetry.sentryOpentelemetryBootstrap)

compileOnly(Config.CompileOnly.nopen)
errorprone(Config.CompileOnly.nopenChecker)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.sentry.spring.opentelemetry;

import com.jakewharton.nopen.annotation.Open;
import io.sentry.Sentry;
import io.sentry.SentryIntegrationPackageStorage;
import io.sentry.SentryOptions;
import io.sentry.opentelemetry.OpenTelemetryUtil;
import org.jetbrains.annotations.NotNull;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
@Open
public class SentryOpenTelemetryAgentWithoutAutoInitConfiguration {

@Bean
@ConditionalOnMissingBean(name = "sentryOpenTelemetryOptionsConfiguration")
public @NotNull Sentry.OptionsConfiguration<SentryOptions>
sentryOpenTelemetryOptionsConfiguration() {
return options -> {
SentryIntegrationPackageStorage.getInstance()
.addIntegration("SpringBootOpenTelemetryAgentWithoutAutoInit");
OpenTelemetryUtil.applyOpenTelemetryOptions(options, true);
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package io.sentry.spring.opentelemetry;

import com.jakewharton.nopen.annotation.Open;
import io.opentelemetry.api.OpenTelemetry;
import io.sentry.ISpanFactory;
import io.sentry.Sentry;
import io.sentry.SentryIntegrationPackageStorage;
import io.sentry.SentryOptions;
import io.sentry.opentelemetry.OpenTelemetryUtil;
import io.sentry.opentelemetry.OtelSpanFactory;
import io.sentry.opentelemetry.SentryAutoConfigurationCustomizerProvider;
import org.jetbrains.annotations.NotNull;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
@Open
public class SentryOpenTelemetryNoAgentConfiguration {

@Bean
@ConditionalOnMissingBean
public static ISpanFactory openTelemetrySpanFactory(OpenTelemetry openTelemetry) {
return new OtelSpanFactory(openTelemetry);
}

@Bean
@ConditionalOnMissingBean(name = "sentryOpenTelemetryOptionsConfiguration")
public @NotNull Sentry.OptionsConfiguration<SentryOptions>
sentryOpenTelemetryOptionsConfiguration() {
return options -> {
SentryIntegrationPackageStorage.getInstance()
.addIntegration("SpringBootOpenTelemetryNoAgent");
SentryAutoConfigurationCustomizerProvider.skipInit = true;
OpenTelemetryUtil.applyOpenTelemetryOptions(options, false);
};
}
}
Loading