All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.sentry.spring.boot.jakarta.SentryAutoConfiguration Maven / Gradle / Ivy

The newest version!
package io.sentry.spring.boot.jakarta;

import com.jakewharton.nopen.annotation.Open;
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;
import io.sentry.ScopesAdapter;
import io.sentry.Sentry;
import io.sentry.SentryIntegrationPackageStorage;
import io.sentry.SentryOptions;
import io.sentry.protocol.SdkVersion;
import io.sentry.quartz.SentryJobListener;
import io.sentry.spring.boot.jakarta.graphql.SentryGraphql22AutoConfiguration;
import io.sentry.spring.boot.jakarta.graphql.SentryGraphqlAutoConfiguration;
import io.sentry.spring.jakarta.ContextTagsEventProcessor;
import io.sentry.spring.jakarta.SentryExceptionResolver;
import io.sentry.spring.jakarta.SentryRequestResolver;
import io.sentry.spring.jakarta.SentrySpringFilter;
import io.sentry.spring.jakarta.SentryUserFilter;
import io.sentry.spring.jakarta.SentryUserProvider;
import io.sentry.spring.jakarta.SentryWebConfiguration;
import io.sentry.spring.jakarta.SpringSecuritySentryUserProvider;
import io.sentry.spring.jakarta.checkin.SentryCheckInAdviceConfiguration;
import io.sentry.spring.jakarta.checkin.SentryCheckInPointcutConfiguration;
import io.sentry.spring.jakarta.checkin.SentryQuartzConfiguration;
import io.sentry.spring.jakarta.exception.SentryCaptureExceptionParameterPointcutConfiguration;
import io.sentry.spring.jakarta.exception.SentryExceptionParameterAdviceConfiguration;
import io.sentry.spring.jakarta.opentelemetry.SentryOpenTelemetryAgentWithoutAutoInitConfiguration;
import io.sentry.spring.jakarta.opentelemetry.SentryOpenTelemetryNoAgentConfiguration;
import io.sentry.spring.jakarta.tracing.SentryAdviceConfiguration;
import io.sentry.spring.jakarta.tracing.SentrySpanPointcutConfiguration;
import io.sentry.spring.jakarta.tracing.SentryTracingFilter;
import io.sentry.spring.jakarta.tracing.SentryTransactionPointcutConfiguration;
import io.sentry.spring.jakarta.tracing.SpringMvcTransactionNameProvider;
import io.sentry.spring.jakarta.tracing.SpringServletTransactionNameProvider;
import io.sentry.spring.jakarta.tracing.TransactionNameProvider;
import io.sentry.transport.ITransportGate;
import io.sentry.transport.apache.ApacheHttpClientTransportFactory;
import jakarta.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Optional;
import org.aspectj.lang.ProceedingJoinPoint;
import org.jetbrains.annotations.NotNull;
import org.quartz.core.QuartzScheduler;
import org.slf4j.MDC;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration;
import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration;
import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.info.GitProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.graphql.execution.DataFetcherExceptionResolverAdapter;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.servlet.HandlerExceptionResolver;

@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "sentry.dsn")
@Open
public class SentryAutoConfiguration {

  /** Registers general purpose Sentry related beans. */
  @Configuration(proxyBeanMethods = false)
  @EnableConfigurationProperties(SentryProperties.class)
  @Open
  static class HubConfiguration {

    @Bean
    @ConditionalOnMissingBean(name = "sentryOptionsConfiguration")
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public @NotNull Sentry.OptionsConfiguration sentryOptionsConfiguration(
        final @NotNull ObjectProvider beforeSendCallback,
        final @NotNull ObjectProvider
                beforeSendTransactionCallback,
        final @NotNull ObjectProvider
                beforeBreadcrumbCallback,
        final @NotNull ObjectProvider tracesSamplerCallback,
        final @NotNull List eventProcessors,
        final @NotNull List integrations,
        final @NotNull ObjectProvider transportGate,
        final @NotNull ObjectProvider transportFactory,
        final @NotNull InAppIncludesResolver inAppPackagesResolver) {
      return options -> {
        beforeSendCallback.ifAvailable(options::setBeforeSend);
        beforeSendTransactionCallback.ifAvailable(options::setBeforeSendTransaction);
        beforeBreadcrumbCallback.ifAvailable(options::setBeforeBreadcrumb);
        tracesSamplerCallback.ifAvailable(options::setTracesSampler);
        eventProcessors.forEach(options::addEventProcessor);
        integrations.forEach(options::addIntegration);
        transportGate.ifAvailable(options::setTransportGate);
        transportFactory.ifAvailable(options::setTransportFactory);
        inAppPackagesResolver.resolveInAppIncludes().forEach(options::addInAppInclude);
      };
    }

    @Bean
    public @NotNull InAppIncludesResolver inAppPackagesResolver() {
      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> optionsConfigurations,
        final @NotNull SentryProperties options,
        final @NotNull ObjectProvider spanFactory,
        final @NotNull ObjectProvider gitProperties) {
      optionsConfigurations.forEach(
          optionsConfiguration -> optionsConfiguration.configure(options));
      gitProperties.ifAvailable(
          git -> {
            if (options.getRelease() == null && options.isUseGitCommitIdAsRelease()) {
              options.setRelease(git.getCommitId());
            }
          });
      spanFactory.ifAvailable(options::setSpanFactory);

      options.setSentryClientName(
          BuildConfig.SENTRY_SPRING_BOOT_JAKARTA_SDK_NAME + "/" + BuildConfig.VERSION_NAME);
      options.setSdkVersion(createSdkVersion(options));
      options.setInitPriority(InitPriority.LOW);
      addPackageAndIntegrationInfo();
      // Spring Boot sets ignored exceptions in runtime using reflection - where the generic
      // information is lost
      // its technically possible to set non-throwable class to `ignoredExceptionsForType` set
      // here we make sure that only classes that extend throwable are set on this field
      options.getIgnoredExceptionsForType().removeIf(it -> !Throwable.class.isAssignableFrom(it));
      Sentry.init(options);
      return ScopesAdapter.getInstance();
    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(MDC.class)
    @Open
    static class ContextTagsEventProcessorConfiguration {

      @Bean
      public @NotNull ContextTagsEventProcessor contextTagsEventProcessor(
          final @NotNull SentryOptions sentryOptions) {
        return new ContextTagsEventProcessor(sentryOptions);
      }
    }

    @Configuration(proxyBeanMethods = false)
    @Import(SentryGraphqlAutoConfiguration.class)
    @Open
    @ConditionalOnClass({
      io.sentry.graphql.SentryInstrumentation.class,
      DataFetcherExceptionResolverAdapter.class,
      GraphQLError.class
    })
    @ConditionalOnMissingClass({
      "io.sentry.graphql22.SentryInstrumentation" // avoid duplicate bean
    })
    static class GraphqlConfiguration {}

    @Configuration(proxyBeanMethods = false)
    @Import(SentryGraphql22AutoConfiguration.class)
    @Open
    @ConditionalOnClass({
      io.sentry.graphql22.SentryInstrumentation.class,
      DataFetcherExceptionResolverAdapter.class,
      GraphQLError.class
    })
    static class Graphql22Configuration {}

    @Configuration(proxyBeanMethods = false)
    @Import(SentryQuartzConfiguration.class)
    @Open
    @ConditionalOnClass({
      SentryJobListener.class,
      QuartzScheduler.class,
      SchedulerFactoryBean.class
    })
    static class QuartzConfiguration {}

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(ProceedingJoinPoint.class)
    @ConditionalOnProperty(
        value = "sentry.enable-aot-compatibility",
        havingValue = "false",
        matchIfMissing = true)
    @Import(SentryCheckInAdviceConfiguration.class)
    @Open
    static class SentryCheckInAspectsConfiguration {
      @Configuration(proxyBeanMethods = false)
      @ConditionalOnMissingBean(name = "sentryCheckInPointcut")
      @Import(SentryCheckInPointcutConfiguration.class)
      @Open
      static class SentryCheckInPointcutAutoConfiguration {}
    }

    /** Registers beans specific to Spring MVC. */
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
    @Import(SentryWebConfiguration.class)
    @Open
    static class SentryWebMvcConfiguration {

      private static final int SENTRY_SPRING_FILTER_PRECEDENCE = Ordered.HIGHEST_PRECEDENCE;

      @Configuration(proxyBeanMethods = false)
      @ConditionalOnClass(SecurityContextHolder.class)
      @Open
      static class SentrySecurityConfiguration {

        /**
         * Configures {@link SpringSecuritySentryUserProvider} only if Spring Security is on the
         * classpath. Its order is set to be higher than {@link
         * SentryWebConfiguration#httpServletRequestSentryUserProvider(SentryOptions)}
         *
         * @param sentryOptions the Sentry options
         * @return {@link SpringSecuritySentryUserProvider}
         */
        @Bean
        @Order(1)
        public @NotNull SpringSecuritySentryUserProvider springSecuritySentryUserProvider(
            final @NotNull SentryOptions sentryOptions) {
          return new SpringSecuritySentryUserProvider(sentryOptions);
        }
      }

      /**
       * Configures {@link SentryUserFilter}. By default it runs as the last filter in order to make
       * sure that all potential authentication information is propagated to {@link
       * HttpServletRequest#getUserPrincipal()}. If Spring Security is auto-configured, its order is
       * set to run after Spring Security.
       *
       * @param scopes the Sentry scopes
       * @param sentryProperties the Sentry properties
       * @param sentryUserProvider the user provider
       * @return {@link SentryUserFilter} registration bean
       */
      @Bean
      @ConditionalOnBean(SentryUserProvider.class)
      public @NotNull FilterRegistrationBean sentryUserFilter(
          final @NotNull IScopes scopes,
          final @NotNull SentryProperties sentryProperties,
          final @NotNull List sentryUserProvider) {
        final FilterRegistrationBean filter = new FilterRegistrationBean<>();
        filter.setFilter(new SentryUserFilter(scopes, sentryUserProvider));
        filter.setOrder(resolveUserFilterOrder(sentryProperties));
        return filter;
      }

      private @NotNull Integer resolveUserFilterOrder(
          final @NotNull SentryProperties sentryProperties) {
        return Optional.ofNullable(sentryProperties.getUserFilterOrder())
            .orElse(Ordered.LOWEST_PRECEDENCE);
      }

      @Bean
      public @NotNull SentryRequestResolver sentryRequestResolver(final @NotNull IScopes scopes) {
        return new SentryRequestResolver(scopes);
      }

      @Bean
      @ConditionalOnMissingBean(name = "sentrySpringFilter")
      public @NotNull FilterRegistrationBean sentrySpringFilter(
          final @NotNull IScopes scopes,
          final @NotNull SentryRequestResolver requestResolver,
          final @NotNull TransactionNameProvider transactionNameProvider) {
        FilterRegistrationBean filter =
            new FilterRegistrationBean<>(
                new SentrySpringFilter(scopes, requestResolver, transactionNameProvider));
        filter.setOrder(SENTRY_SPRING_FILTER_PRECEDENCE);
        return filter;
      }

      @Bean
      @ConditionalOnMissingBean(name = "sentryTracingFilter")
      public FilterRegistrationBean sentryTracingFilter(
          final @NotNull IScopes scopes,
          final @NotNull TransactionNameProvider transactionNameProvider) {
        FilterRegistrationBean filter =
            new FilterRegistrationBean<>(new SentryTracingFilter(scopes, transactionNameProvider));
        filter.setOrder(SENTRY_SPRING_FILTER_PRECEDENCE + 1); // must run after SentrySpringFilter
        return filter;
      }

      @Configuration(proxyBeanMethods = false)
      @ConditionalOnClass(HandlerExceptionResolver.class)
      @Open
      static class SentryMvcModeConfig {

        @Bean
        @ConditionalOnMissingBean
        public @NotNull SentryExceptionResolver sentryExceptionResolver(
            final @NotNull IScopes scopes,
            final @NotNull TransactionNameProvider transactionNameProvider,
            final @NotNull SentryProperties options) {
          return new SentryExceptionResolver(
              scopes, transactionNameProvider, options.getExceptionResolverOrder());
        }

        @Bean
        @ConditionalOnMissingBean(TransactionNameProvider.class)
        public @NotNull TransactionNameProvider transactionNameProvider() {
          return new SpringMvcTransactionNameProvider();
        }
      }

      @Configuration(proxyBeanMethods = false)
      @ConditionalOnMissingClass("org.springframework.web.servlet.HandlerExceptionResolver")
      @Open
      static class SentryServletModeConfig {

        @Bean
        @ConditionalOnMissingBean(TransactionNameProvider.class)
        public @NotNull TransactionNameProvider transactionNameProvider() {
          return new SpringServletTransactionNameProvider();
        }
      }
    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(ProceedingJoinPoint.class)
    @ConditionalOnProperty(
        value = "sentry.enable-aot-compatibility",
        havingValue = "false",
        matchIfMissing = true)
    @Import(SentryExceptionParameterAdviceConfiguration.class)
    @Open
    static class SentryErrorAspectsConfiguration {
      @Configuration(proxyBeanMethods = false)
      @ConditionalOnMissingBean(name = "sentryCaptureExceptionParameterPointcut")
      @Import(SentryCaptureExceptionParameterPointcutConfiguration.class)
      @Open
      static class SentryCaptureExceptionParameterPointcutAutoConfiguration {}
    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnProperty(
        value = "sentry.enable-aot-compatibility",
        havingValue = "false",
        matchIfMissing = true)
    @Conditional(SentryTracingCondition.class)
    @ConditionalOnClass(ProceedingJoinPoint.class)
    @Import(SentryAdviceConfiguration.class)
    @Open
    static class SentryPerformanceAspectsConfiguration {

      @Configuration(proxyBeanMethods = false)
      @ConditionalOnMissingBean(name = "sentryTransactionPointcut")
      @Import(SentryTransactionPointcutConfiguration.class)
      @Open
      static class SentryTransactionPointcutAutoConfiguration {}

      @Configuration(proxyBeanMethods = false)
      @ConditionalOnMissingBean(name = "sentrySpanPointcut")
      @Import(SentrySpanPointcutConfiguration.class)
      @Open
      static class SentrySpanPointcutAutoConfiguration {}
    }

    @Configuration(proxyBeanMethods = false)
    @AutoConfigureBefore(RestTemplateAutoConfiguration.class)
    @ConditionalOnClass(RestTemplate.class)
    @Open
    static class SentryPerformanceRestTemplateConfiguration {
      @Bean
      public SentrySpanRestTemplateCustomizer sentrySpanRestTemplateCustomizer(IScopes scopes) {
        return new SentrySpanRestTemplateCustomizer(scopes);
      }
    }

    @Configuration(proxyBeanMethods = false)
    @AutoConfigureBefore(RestClientAutoConfiguration.class)
    @ConditionalOnClass(RestClient.class)
    @Open
    static class SentrySpanRestClientConfiguration {
      @Bean
      public SentrySpanRestClientCustomizer sentrySpanRestClientCustomizer(IScopes scopes) {
        return new SentrySpanRestClientCustomizer(scopes);
      }
    }

    @Configuration(proxyBeanMethods = false)
    @AutoConfigureBefore(WebClientAutoConfiguration.class)
    @ConditionalOnClass(WebClient.class)
    @Open
    static class SentryPerformanceWebClientConfiguration {
      @Bean
      public SentrySpanWebClientCustomizer sentrySpanWebClientCustomizer(IScopes scopes) {
        return new SentrySpanWebClientCustomizer(scopes);
      }
    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnMissingBean(ITransportFactory.class)
    @ConditionalOnClass(ApacheHttpClientTransportFactory.class)
    @Open
    static class ApacheHttpClientTransportFactoryAutoconfiguration {

      @Bean
      public @NotNull ApacheHttpClientTransportFactory apacheHttpClientTransportFactory() {
        return new ApacheHttpClientTransportFactory();
      }
    }

    private static @NotNull SdkVersion createSdkVersion(
        final @NotNull SentryOptions sentryOptions) {
      SdkVersion sdkVersion = sentryOptions.getSdkVersion();

      final String name = BuildConfig.SENTRY_SPRING_BOOT_JAKARTA_SDK_NAME;
      final String version = BuildConfig.VERSION_NAME;
      sdkVersion = SdkVersion.updateSdkVersion(sdkVersion, name, version);

      return sdkVersion;
    }

    private static void addPackageAndIntegrationInfo() {
      SentryIntegrationPackageStorage.getInstance()
          .addPackage(
              "maven:io.sentry:sentry-spring-boot-starter-jakarta", BuildConfig.VERSION_NAME);
      SentryIntegrationPackageStorage.getInstance().addIntegration("SpringBoot3");
    }
  }

  static final class SentryTracingCondition extends AnyNestedCondition {

    public SentryTracingCondition() {
      super(ConfigurationPhase.REGISTER_BEAN);
    }

    @ConditionalOnProperty(name = "sentry.traces-sample-rate")
    @SuppressWarnings("UnusedNestedClass")
    private static class SentryTracesSampleRateCondition {}

    @ConditionalOnBean(SentryOptions.TracesSamplerCallback.class)
    @SuppressWarnings("UnusedNestedClass")
    private static class SentryTracesSamplerBeanCondition {}
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy