Skip to content

Commit 3815908

Browse files
adinauerclaude
andcommitted
feat(spring): [Cache Tracing 3] Add BeanPostProcessor and auto-configuration
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 5e081fc commit 3815908

File tree

5 files changed

+124
-0
lines changed

5 files changed

+124
-0
lines changed

sentry-spring-7/api/sentry-spring-7.api

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

107+
public final class io/sentry/spring7/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+
107113
public final class io/sentry/spring7/cache/SentryCacheManagerWrapper : org/springframework/cache/CacheManager {
108114
public fun <init> (Lorg/springframework/cache/CacheManager;Lio/sentry/IScopes;)V
109115
public fun getCache (Ljava/lang/String;)Lorg/springframework/cache/Cache;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package io.sentry.spring7.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: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package io.sentry.spring7.cache
2+
3+
import io.sentry.IScopes
4+
import kotlin.test.Test
5+
import kotlin.test.assertSame
6+
import kotlin.test.assertTrue
7+
import org.mockito.kotlin.mock
8+
import org.springframework.cache.CacheManager
9+
10+
class SentryCacheBeanPostProcessorTest {
11+
12+
private val scopes: IScopes = mock()
13+
14+
@Test
15+
fun `wraps CacheManager beans in SentryCacheManagerWrapper`() {
16+
val cacheManager = mock<CacheManager>()
17+
val processor = SentryCacheBeanPostProcessor()
18+
19+
val result = processor.postProcessAfterInitialization(cacheManager, "cacheManager")
20+
21+
assertTrue(result is SentryCacheManagerWrapper)
22+
}
23+
24+
@Test
25+
fun `does not double-wrap SentryCacheManagerWrapper`() {
26+
val delegate = mock<CacheManager>()
27+
val alreadyWrapped = SentryCacheManagerWrapper(delegate, scopes)
28+
val processor = SentryCacheBeanPostProcessor()
29+
30+
val result = processor.postProcessAfterInitialization(alreadyWrapped, "cacheManager")
31+
32+
assertSame(alreadyWrapped, result)
33+
}
34+
35+
@Test
36+
fun `does not wrap non-CacheManager beans`() {
37+
val someBean = "not a cache manager"
38+
val processor = SentryCacheBeanPostProcessor()
39+
40+
val result = processor.postProcessAfterInitialization(someBean, "someBean")
41+
42+
assertSame(someBean, result)
43+
}
44+
}

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import io.sentry.spring7.SentryWebConfiguration;
2626
import io.sentry.spring7.SpringProfilesEventProcessor;
2727
import io.sentry.spring7.SpringSecuritySentryUserProvider;
28+
import io.sentry.spring7.cache.SentryCacheBeanPostProcessor;
2829
import io.sentry.spring7.checkin.SentryCheckInAdviceConfiguration;
2930
import io.sentry.spring7.checkin.SentryCheckInPointcutConfiguration;
3031
import io.sentry.spring7.checkin.SentryQuartzConfiguration;
@@ -65,6 +66,7 @@
6566
import org.springframework.boot.restclient.autoconfigure.RestTemplateAutoConfiguration;
6667
import org.springframework.boot.web.servlet.FilterRegistrationBean;
6768
import org.springframework.boot.webclient.autoconfigure.WebClientAutoConfiguration;
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;
@@ -229,6 +231,19 @@ static class Graphql22Configuration {}
229231
})
230232
static class QuartzConfiguration {}
231233

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

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import io.sentry.spring7.SentryUserFilter
3838
import io.sentry.spring7.SentryUserProvider
3939
import io.sentry.spring7.SpringProfilesEventProcessor
4040
import io.sentry.spring7.SpringSecuritySentryUserProvider
41+
import io.sentry.spring7.cache.SentryCacheBeanPostProcessor
4142
import io.sentry.spring7.tracing.SentryTracingFilter
4243
import io.sentry.spring7.tracing.SpringServletTransactionNameProvider
4344
import io.sentry.spring7.tracing.TransactionNameProvider
@@ -231,6 +232,7 @@ class SentryAutoConfigurationTest {
231232
"sentry.ignored-transactions=transactionName1,transactionNameB",
232233
"sentry.enable-backpressure-handling=false",
233234
"sentry.enable-database-transaction-tracing=true",
235+
"sentry.enable-cache-tracing=true",
234236
"sentry.enable-spotlight=true",
235237
"sentry.spotlight-connection-url=http://local.sentry.io:1234",
236238
"sentry.force-init=true",
@@ -284,6 +286,7 @@ class SentryAutoConfigurationTest {
284286
.containsOnly(FilterString("transactionName1"), FilterString("transactionNameB"))
285287
assertThat(options.isEnableBackpressureHandling).isEqualTo(false)
286288
assertThat(options.isEnableDatabaseTransactionTracing).isEqualTo(true)
289+
assertThat(options.isEnableCacheTracing).isEqualTo(true)
287290
assertThat(options.isForceInit).isEqualTo(true)
288291
assertThat(options.isGlobalHubMode).isEqualTo(true)
289292
assertThat(options.isCaptureOpenTelemetryEvents).isEqualTo(true)
@@ -1179,6 +1182,33 @@ class SentryAutoConfigurationTest {
11791182
}
11801183
}
11811184

1185+
@Test
1186+
fun `SentryCacheBeanPostProcessor is registered when enable-cache-tracing is true`() {
1187+
contextRunner
1188+
.withPropertyValues(
1189+
"sentry.dsn=http://key@localhost/proj",
1190+
"sentry.enable-cache-tracing=true",
1191+
)
1192+
.run { assertThat(it).hasSingleBean(SentryCacheBeanPostProcessor::class.java) }
1193+
}
1194+
1195+
@Test
1196+
fun `SentryCacheBeanPostProcessor is not registered when enable-cache-tracing is missing`() {
1197+
contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj").run {
1198+
assertThat(it).doesNotHaveBean(SentryCacheBeanPostProcessor::class.java)
1199+
}
1200+
}
1201+
1202+
@Test
1203+
fun `SentryCacheBeanPostProcessor is not registered when enable-cache-tracing is false`() {
1204+
contextRunner
1205+
.withPropertyValues(
1206+
"sentry.dsn=http://key@localhost/proj",
1207+
"sentry.enable-cache-tracing=false",
1208+
)
1209+
.run { assertThat(it).doesNotHaveBean(SentryCacheBeanPostProcessor::class.java) }
1210+
}
1211+
11821212
@Configuration(proxyBeanMethods = false)
11831213
open class CustomSchedulerFactoryBeanCustomizerConfiguration {
11841214
class MyJobListener : JobListener {

0 commit comments

Comments
 (0)