Skip to content

Commit 09e6e09

Browse files
authored
Merge pull request #5191 from getsentry/feat/cache-tracing-spring-boot-2
feat(spring): [Cache Tracing 10] Port cache tracing to Spring Boot 2 + samples
2 parents 7d0ec4c + 7fa53f7 commit 09e6e09

File tree

16 files changed

+864
-1
lines changed

16 files changed

+864
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
### Features
66

7-
- Add cache tracing instrumentation for Spring Boot 4 ([#5172](https://github.com/getsentry/sentry-java/pull/5172), [#5173](https://github.com/getsentry/sentry-java/pull/5173), [#5174](https://github.com/getsentry/sentry-java/pull/5174))
7+
- Add cache tracing instrumentation for Spring Boot 2, 3, and 4 ([#5172](https://github.com/getsentry/sentry-java/pull/5172), [#5173](https://github.com/getsentry/sentry-java/pull/5173), [#5174](https://github.com/getsentry/sentry-java/pull/5174), [#5190](https://github.com/getsentry/sentry-java/pull/5190), [#5191](https://github.com/getsentry/sentry-java/pull/5191))
88
- Wraps Spring `CacheManager` and `Cache` beans to produce `cache.get`, `cache.put`, `cache.remove`, and `cache.flush` spans
99
- Set `sentry.enable-cache-tracing` to `true` to enable this feature
1010
- Add JCache (JSR-107) cache tracing via new `sentry-jcache` module ([#5179](https://github.com/getsentry/sentry-java/pull/5179))

gradle/libs.versions.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ springboot-starter-aop = { module = "org.springframework.boot:spring-boot-starte
169169
springboot-starter-security = { module = "org.springframework.boot:spring-boot-starter-security", version.ref = "springboot2" }
170170
springboot-starter-jdbc = { module = "org.springframework.boot:spring-boot-starter-jdbc", version.ref = "springboot2" }
171171
springboot-starter-actuator = { module = "org.springframework.boot:spring-boot-starter-actuator", version.ref = "springboot2" }
172+
springboot-starter-cache = { module = "org.springframework.boot:spring-boot-starter-cache", version.ref = "springboot2" }
172173
springboot3-otel = { module = "io.opentelemetry.instrumentation:opentelemetry-spring-boot-starter", version.ref = "otelInstrumentation" }
173174
springboot3-starter = { module = "org.springframework.boot:spring-boot-starter", version.ref = "springboot3" }
174175
springboot3-starter-graphql = { module = "org.springframework.boot:spring-boot-starter-graphql", version.ref = "springboot3" }

sentry-samples/sentry-samples-spring-boot/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ dependencies {
4040
implementation(libs.springboot.starter.security)
4141
implementation(libs.springboot.starter.web)
4242
implementation(libs.springboot.starter.webflux)
43+
implementation(libs.springboot.starter.cache)
4344
implementation(libs.springboot.starter.websocket)
45+
implementation(libs.caffeine)
4446
implementation(Config.Libs.aspectj)
4547
implementation(Config.Libs.kotlinReflect)
4648
implementation(kotlin(Config.kotlinStdLib, KotlinCompilerVersion.VERSION))
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package io.sentry.samples.spring.boot;
2+
3+
import org.springframework.web.bind.annotation.DeleteMapping;
4+
import org.springframework.web.bind.annotation.GetMapping;
5+
import org.springframework.web.bind.annotation.PathVariable;
6+
import org.springframework.web.bind.annotation.PostMapping;
7+
import org.springframework.web.bind.annotation.RequestBody;
8+
import org.springframework.web.bind.annotation.RequestMapping;
9+
import org.springframework.web.bind.annotation.RestController;
10+
11+
@RestController
12+
@RequestMapping("/cache/")
13+
public class CacheController {
14+
private final TodoService todoService;
15+
16+
public CacheController(TodoService todoService) {
17+
this.todoService = todoService;
18+
}
19+
20+
@GetMapping("{id}")
21+
Todo get(@PathVariable Long id) {
22+
return todoService.get(id);
23+
}
24+
25+
@PostMapping
26+
Todo save(@RequestBody Todo todo) {
27+
return todoService.save(todo);
28+
}
29+
30+
@DeleteMapping("{id}")
31+
void delete(@PathVariable Long id) {
32+
todoService.delete(id);
33+
}
34+
}

sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/SentryDemoApplication.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.springframework.boot.SpringApplication;
1010
import org.springframework.boot.autoconfigure.SpringBootApplication;
1111
import org.springframework.boot.web.client.RestTemplateBuilder;
12+
import org.springframework.cache.annotation.EnableCaching;
1213
import org.springframework.context.annotation.Bean;
1314
import org.springframework.scheduling.annotation.EnableScheduling;
1415
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
@@ -18,6 +19,7 @@
1819
import org.springframework.web.reactive.function.client.WebClient;
1920

2021
@SpringBootApplication
22+
@EnableCaching
2123
@EnableScheduling
2224
public class SentryDemoApplication {
2325
public static void main(String[] args) {
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package io.sentry.samples.spring.boot;
2+
3+
import java.util.Map;
4+
import java.util.concurrent.ConcurrentHashMap;
5+
import org.springframework.cache.annotation.CacheEvict;
6+
import org.springframework.cache.annotation.CachePut;
7+
import org.springframework.cache.annotation.Cacheable;
8+
import org.springframework.stereotype.Service;
9+
10+
@Service
11+
public class TodoService {
12+
private final Map<Long, Todo> store = new ConcurrentHashMap<>();
13+
14+
@Cacheable(value = "todos", key = "#id")
15+
public Todo get(Long id) {
16+
return store.get(id);
17+
}
18+
19+
@CachePut(value = "todos", key = "#todo.id")
20+
public Todo save(Todo todo) {
21+
store.put(todo.getId(), todo);
22+
return todo;
23+
}
24+
25+
@CacheEvict(value = "todos", key = "#id")
26+
public void delete(Long id) {
27+
store.remove(id);
28+
}
29+
}

sentry-samples/sentry-samples-spring-boot/src/main/resources/application.properties

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ sentry.profile-session-sample-rate=1.0
2020
sentry.profiling-traces-dir-path=tmp/sentry/profiling-traces
2121
sentry.profile-lifecycle=TRACE
2222

23+
# Cache tracing
24+
sentry.enable-cache-tracing=true
25+
spring.cache.cache-names=todos
26+
spring.cache.caffeine.spec=maximumSize=500,expireAfterAccess=600s
27+
2328
# Database configuration
2429
spring.datasource.url=jdbc:p6spy:hsqldb:mem:testdb
2530
spring.datasource.driver-class-name=com.p6spy.engine.spy.P6SpyDriver
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package io.sentry.systemtest
2+
3+
import io.sentry.systemtest.util.TestHelper
4+
import kotlin.test.Test
5+
import kotlin.test.assertEquals
6+
import org.junit.Before
7+
8+
class CacheSystemTest {
9+
lateinit var testHelper: TestHelper
10+
11+
@Before
12+
fun setup() {
13+
testHelper = TestHelper("http://localhost:8080")
14+
testHelper.reset()
15+
}
16+
17+
@Test
18+
fun `cache put and get produce spans`() {
19+
val restClient = testHelper.restClient
20+
21+
// Save a todo (triggers @CachePut -> cache.put span)
22+
val todo = Todo(1L, "test-todo", false)
23+
restClient.saveCachedTodo(todo)
24+
assertEquals(200, restClient.lastKnownStatusCode)
25+
26+
testHelper.ensureTransactionReceived { transaction, _ ->
27+
testHelper.doesTransactionContainSpanWithOp(transaction, "cache.put")
28+
}
29+
30+
testHelper.reset()
31+
32+
// Get the todo (triggers @Cacheable -> cache.get span, should be a hit)
33+
restClient.getCachedTodo(1L)
34+
assertEquals(200, restClient.lastKnownStatusCode)
35+
36+
testHelper.ensureTransactionReceived { transaction, _ ->
37+
testHelper.doesTransactionContainSpanWithOp(transaction, "cache.get")
38+
}
39+
}
40+
41+
@Test
42+
fun `cache evict produces span`() {
43+
val restClient = testHelper.restClient
44+
45+
restClient.deleteCachedTodo(1L)
46+
47+
testHelper.ensureTransactionReceived { transaction, _ ->
48+
testHelper.doesTransactionContainSpanWithOp(transaction, "cache.remove")
49+
}
50+
}
51+
}

sentry-spring-boot/src/main/java/io/sentry/spring/boot/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.SpringProfilesEventProcessor;
2626
import io.sentry.spring.SpringSecuritySentryUserProvider;
2727
import io.sentry.spring.boot.graphql.SentryGraphqlAutoConfiguration;
28+
import io.sentry.spring.cache.SentryCacheBeanPostProcessor;
2829
import io.sentry.spring.checkin.SentryCheckInAdviceConfiguration;
2930
import io.sentry.spring.checkin.SentryCheckInPointcutConfiguration;
3031
import io.sentry.spring.checkin.SentryQuartzConfiguration;
@@ -64,6 +65,7 @@
6465
import org.springframework.boot.context.properties.EnableConfigurationProperties;
6566
import org.springframework.boot.info.GitProperties;
6667
import org.springframework.boot.web.servlet.FilterRegistrationBean;
68+
import org.springframework.cache.CacheManager;
6769
import org.springframework.context.annotation.Bean;
6870
import org.springframework.context.annotation.Conditional;
6971
import org.springframework.context.annotation.Configuration;
@@ -216,6 +218,19 @@ static class GraphqlConfiguration {}
216218
})
217219
static class QuartzConfiguration {}
218220

221+
@Configuration(proxyBeanMethods = false)
222+
@ConditionalOnClass(CacheManager.class)
223+
@ConditionalOnProperty(name = "sentry.enable-cache-tracing", havingValue = "true")
224+
@Open
225+
static class SentryCacheConfiguration {
226+
227+
@Bean
228+
public static @NotNull SentryCacheBeanPostProcessor sentryCacheBeanPostProcessor() {
229+
SentryIntegrationPackageStorage.getInstance().addIntegration("SpringCache");
230+
return new SentryCacheBeanPostProcessor();
231+
}
232+
}
233+
219234
@Configuration(proxyBeanMethods = false)
220235
@ConditionalOnClass(ProceedingJoinPoint.class)
221236
@ConditionalOnProperty(

sentry-spring/api/sentry-spring.api

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

107+
public final class io/sentry/spring/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/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/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+
}
133+
107134
public abstract interface annotation class io/sentry/spring/checkin/SentryCheckIn : java/lang/annotation/Annotation {
108135
public abstract fun heartbeat ()Z
109136
public abstract fun monitorSlug ()Ljava/lang/String;

0 commit comments

Comments
 (0)