Skip to content

Commit 4d9f683

Browse files
adinauerclaude
andcommitted
feat(spring-jakarta): Add cache tracing for Spring Boot 3 / Spring 6
Port cache tracing classes from sentry-spring-7 to sentry-spring-jakarta, covering Spring Boot 3 (Spring Framework 6.x) users. Includes SentryCacheWrapper, SentryCacheManagerWrapper, SentryCacheBeanPostProcessor, and auto-configuration in sentry-spring-boot-jakarta. The retrieve() overrides for CompletableFuture/reactive cache operations are included and safe on Spring 6.0 (where retrieve() doesn't exist on the Cache interface) — they're simply dead code, never called by the framework until Spring 6.1+. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 41872ad commit 4d9f683

File tree

8 files changed

+981
-0
lines changed

8 files changed

+981
-0
lines changed

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import io.sentry.spring.jakarta.SentryWebConfiguration;
2626
import io.sentry.spring.jakarta.SpringProfilesEventProcessor;
2727
import io.sentry.spring.jakarta.SpringSecuritySentryUserProvider;
28+
import io.sentry.spring.jakarta.cache.SentryCacheBeanPostProcessor;
2829
import io.sentry.spring.jakarta.checkin.SentryCheckInAdviceConfiguration;
2930
import io.sentry.spring.jakarta.checkin.SentryCheckInPointcutConfiguration;
3031
import io.sentry.spring.jakarta.checkin.SentryQuartzConfiguration;
@@ -65,6 +66,7 @@
6566
import org.springframework.boot.context.properties.EnableConfigurationProperties;
6667
import org.springframework.boot.info.GitProperties;
6768
import org.springframework.boot.web.servlet.FilterRegistrationBean;
69+
import org.springframework.cache.CacheManager;
6870
import org.springframework.context.annotation.Bean;
6971
import org.springframework.context.annotation.Conditional;
7072
import org.springframework.context.annotation.Configuration;
@@ -231,6 +233,19 @@ static class Graphql22Configuration {}
231233
})
232234
static class QuartzConfiguration {}
233235

236+
@Configuration(proxyBeanMethods = false)
237+
@ConditionalOnClass(CacheManager.class)
238+
@ConditionalOnProperty(name = "sentry.enable-cache-tracing", havingValue = "true")
239+
@Open
240+
static class SentryCacheConfiguration {
241+
242+
@Bean
243+
public static @NotNull SentryCacheBeanPostProcessor sentryCacheBeanPostProcessor() {
244+
SentryIntegrationPackageStorage.getInstance().addIntegration("SpringCache");
245+
return new SentryCacheBeanPostProcessor();
246+
}
247+
}
248+
234249
@Configuration(proxyBeanMethods = false)
235250
@ConditionalOnClass(ProceedingJoinPoint.class)
236251
@ConditionalOnProperty(

sentry-spring-jakarta/api/sentry-spring-jakarta.api

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,35 @@ public final class io/sentry/spring/jakarta/SpringSecuritySentryUserProvider : i
104104
public fun provideUser ()Lio/sentry/protocol/User;
105105
}
106106

107+
public final class io/sentry/spring/jakarta/cache/SentryCacheBeanPostProcessor : org/springframework/beans/factory/config/BeanPostProcessor, org/springframework/core/PriorityOrdered {
108+
public fun <init> ()V
109+
public fun getOrder ()I
110+
public fun postProcessAfterInitialization (Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;
111+
}
112+
113+
public final class io/sentry/spring/jakarta/cache/SentryCacheManagerWrapper : org/springframework/cache/CacheManager {
114+
public fun <init> (Lorg/springframework/cache/CacheManager;Lio/sentry/IScopes;)V
115+
public fun getCache (Ljava/lang/String;)Lorg/springframework/cache/Cache;
116+
public fun getCacheNames ()Ljava/util/Collection;
117+
}
118+
119+
public final class io/sentry/spring/jakarta/cache/SentryCacheWrapper : org/springframework/cache/Cache {
120+
public fun <init> (Lorg/springframework/cache/Cache;Lio/sentry/IScopes;)V
121+
public fun clear ()V
122+
public fun evict (Ljava/lang/Object;)V
123+
public fun evictIfPresent (Ljava/lang/Object;)Z
124+
public fun get (Ljava/lang/Object;)Lorg/springframework/cache/Cache$ValueWrapper;
125+
public fun get (Ljava/lang/Object;Ljava/lang/Class;)Ljava/lang/Object;
126+
public fun get (Ljava/lang/Object;Ljava/util/concurrent/Callable;)Ljava/lang/Object;
127+
public fun getName ()Ljava/lang/String;
128+
public fun getNativeCache ()Ljava/lang/Object;
129+
public fun invalidate ()Z
130+
public fun put (Ljava/lang/Object;Ljava/lang/Object;)V
131+
public fun putIfAbsent (Ljava/lang/Object;Ljava/lang/Object;)Lorg/springframework/cache/Cache$ValueWrapper;
132+
public fun retrieve (Ljava/lang/Object;)Ljava/util/concurrent/CompletableFuture;
133+
public fun retrieve (Ljava/lang/Object;Ljava/util/function/Supplier;)Ljava/util/concurrent/CompletableFuture;
134+
}
135+
107136
public abstract interface annotation class io/sentry/spring/jakarta/checkin/SentryCheckIn : java/lang/annotation/Annotation {
108137
public abstract fun heartbeat ()Z
109138
public abstract fun monitorSlug ()Ljava/lang/String;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package io.sentry.spring.jakarta.cache;
2+
3+
import io.sentry.ScopesAdapter;
4+
import org.jetbrains.annotations.ApiStatus;
5+
import org.jetbrains.annotations.NotNull;
6+
import org.springframework.beans.BeansException;
7+
import org.springframework.beans.factory.config.BeanPostProcessor;
8+
import org.springframework.cache.CacheManager;
9+
import org.springframework.core.Ordered;
10+
import org.springframework.core.PriorityOrdered;
11+
12+
/** Wraps {@link CacheManager} beans in {@link SentryCacheManagerWrapper} for instrumentation. */
13+
@ApiStatus.Internal
14+
public final class SentryCacheBeanPostProcessor implements BeanPostProcessor, PriorityOrdered {
15+
16+
@Override
17+
public @NotNull Object postProcessAfterInitialization(
18+
final @NotNull Object bean, final @NotNull String beanName) throws BeansException {
19+
if (bean instanceof CacheManager && !(bean instanceof SentryCacheManagerWrapper)) {
20+
return new SentryCacheManagerWrapper((CacheManager) bean, ScopesAdapter.getInstance());
21+
}
22+
return bean;
23+
}
24+
25+
@Override
26+
public int getOrder() {
27+
return Ordered.LOWEST_PRECEDENCE;
28+
}
29+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package io.sentry.spring.jakarta.cache;
2+
3+
import io.sentry.IScopes;
4+
import java.util.Collection;
5+
import org.jetbrains.annotations.ApiStatus;
6+
import org.jetbrains.annotations.NotNull;
7+
import org.jetbrains.annotations.Nullable;
8+
import org.springframework.cache.Cache;
9+
import org.springframework.cache.CacheManager;
10+
11+
/** Wraps a Spring {@link CacheManager} to return Sentry-instrumented caches. */
12+
@ApiStatus.Internal
13+
public final class SentryCacheManagerWrapper implements CacheManager {
14+
15+
private final @NotNull CacheManager delegate;
16+
private final @NotNull IScopes scopes;
17+
18+
public SentryCacheManagerWrapper(
19+
final @NotNull CacheManager delegate, final @NotNull IScopes scopes) {
20+
this.delegate = delegate;
21+
this.scopes = scopes;
22+
}
23+
24+
@Override
25+
public @Nullable Cache getCache(final @NotNull String name) {
26+
final Cache cache = delegate.getCache(name);
27+
if (cache == null || cache instanceof SentryCacheWrapper) {
28+
return cache;
29+
}
30+
return new SentryCacheWrapper(cache, scopes);
31+
}
32+
33+
@Override
34+
public @NotNull Collection<String> getCacheNames() {
35+
return delegate.getCacheNames();
36+
}
37+
}

0 commit comments

Comments
 (0)