Skip to content
Draft
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
1 change: 1 addition & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ springboot3-starter-aop = { module = "org.springframework.boot:spring-boot-start
springboot3-starter-security = { module = "org.springframework.boot:spring-boot-starter-security", version.ref = "springboot3" }
springboot3-starter-jdbc = { module = "org.springframework.boot:spring-boot-starter-jdbc", version.ref = "springboot3" }
springboot3-starter-actuator = { module = "org.springframework.boot:spring-boot-starter-actuator", version.ref = "springboot3" }
springboot3-starter-cache = { module = "org.springframework.boot:spring-boot-starter-cache", version.ref = "springboot3" }
springboot4-otel = { module = "io.opentelemetry.instrumentation:opentelemetry-spring-boot-starter", version.ref = "otelInstrumentation" }
springboot4-resttestclient = { module = "org.springframework.boot:spring-boot-resttestclient", version.ref = "springboot4" }
springboot4-starter = { module = "org.springframework.boot:spring-boot-starter", version.ref = "springboot4" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ dependencies {
implementation(projects.sentryAsyncProfiler)
implementation(projects.sentryOpentelemetry.sentryOpentelemetryAgentlessSpring)

// cache tracing
implementation(libs.springboot3.starter.cache)
implementation(libs.caffeine)

// database query tracing
implementation(projects.sentryJdbc)
runtimeOnly(libs.hsqldb)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package io.sentry.samples.spring.boot.jakarta;

import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/cache/")
public class CacheController {
private final TodoService todoService;

public CacheController(TodoService todoService) {
this.todoService = todoService;
}

@GetMapping("{id}")
Todo get(@PathVariable Long id) {
return todoService.get(id);
}

@PostMapping
Todo save(@RequestBody Todo todo) {
return todoService.save(todo);
}

@DeleteMapping("{id}")
void delete(@PathVariable Long id) {
todoService.delete(id);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
Expand All @@ -21,6 +22,7 @@
import org.springframework.web.reactive.function.client.WebClient;

@SpringBootApplication
@EnableCaching
@EnableScheduling
public class SentryDemoApplication {
public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.sentry.samples.spring.boot.jakarta;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class TodoService {
private final Map<Long, Todo> store = new ConcurrentHashMap<>();

@Cacheable(value = "todos", key = "#id")
public Todo get(Long id) {
return store.get(id);
}

@CachePut(value = "todos", key = "#todo.id")
public Todo save(Todo todo) {
store.put(todo.getId(), todo);
return todo;
}

@CacheEvict(value = "todos", key = "#id")
public void delete(Long id) {
store.remove(id);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ spring.graphql.graphiql.enabled=true
spring.graphql.websocket.path=/graphql
spring.quartz.job-store-type=memory

# Cache tracing
sentry.enable-cache-tracing=true
spring.cache.cache-names=todos
spring.cache.caffeine.spec=maximumSize=500,expireAfterAccess=600s

# OTEL configuration
otel.propagators=tracecontext,baggage,sentry
otel.logs.exporter=none
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package io.sentry.systemtest

import io.sentry.systemtest.util.TestHelper
import kotlin.test.Test
import kotlin.test.assertEquals
import org.junit.Before

class CacheSystemTest {
lateinit var testHelper: TestHelper

@Before
fun setup() {
testHelper = TestHelper("http://localhost:8080")
testHelper.reset()
}

@Test
fun `cache put and get produce spans`() {
val restClient = testHelper.restClient

// Save a todo (triggers @CachePut -> cache.put span)
val todo = Todo(1L, "test-todo", false)
restClient.saveCachedTodo(todo)
assertEquals(200, restClient.lastKnownStatusCode)

testHelper.ensureTransactionReceived { transaction, _ ->
testHelper.doesTransactionContainSpanWithOp(transaction, "cache.put")
}

testHelper.reset()

// Get the todo (triggers @Cacheable -> cache.get span, should be a hit)
restClient.getCachedTodo(1L)
assertEquals(200, restClient.lastKnownStatusCode)

testHelper.ensureTransactionReceived { transaction, _ ->
testHelper.doesTransactionContainSpanWithOp(transaction, "cache.get")
}
}

@Test
fun `cache evict produces span`() {
val restClient = testHelper.restClient

restClient.deleteCachedTodo(1L)

testHelper.ensureTransactionReceived { transaction, _ ->
testHelper.doesTransactionContainSpanWithOp(transaction, "cache.remove")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ dependencies {
implementation(libs.otel)
implementation(projects.sentryAsyncProfiler)

// cache tracing
implementation(libs.springboot3.starter.cache)
implementation(libs.caffeine)

// database query tracing
implementation(projects.sentryJdbc)
runtimeOnly(libs.hsqldb)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package io.sentry.samples.spring.boot.jakarta;

import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/cache/")
public class CacheController {
private final TodoService todoService;

public CacheController(TodoService todoService) {
this.todoService = todoService;
}

@GetMapping("{id}")
Todo get(@PathVariable Long id) {
return todoService.get(id);
}

@PostMapping
Todo save(@RequestBody Todo todo) {
return todoService.save(todo);
}

@DeleteMapping("{id}")
void delete(@PathVariable Long id) {
todoService.delete(id);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
Expand All @@ -21,6 +22,7 @@
import org.springframework.web.reactive.function.client.WebClient;

@SpringBootApplication
@EnableCaching
@EnableScheduling
public class SentryDemoApplication {
public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.sentry.samples.spring.boot.jakarta;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class TodoService {
private final Map<Long, Todo> store = new ConcurrentHashMap<>();

@Cacheable(value = "todos", key = "#id")
public Todo get(Long id) {
return store.get(id);
}

@CachePut(value = "todos", key = "#todo.id")
public Todo save(Todo todo) {
store.put(todo.getId(), todo);
return todo;
}

@CacheEvict(value = "todos", key = "#id")
public void delete(Long id) {
store.remove(id);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,8 @@ spring.datasource.password=
spring.graphql.graphiql.enabled=true
spring.graphql.websocket.path=/graphql
spring.quartz.job-store-type=memory

# Cache tracing
sentry.enable-cache-tracing=true
spring.cache.cache-names=todos
spring.cache.caffeine.spec=maximumSize=500,expireAfterAccess=600s
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package io.sentry.systemtest

import io.sentry.systemtest.util.TestHelper
import kotlin.test.Test
import kotlin.test.assertEquals
import org.junit.Before

class CacheSystemTest {
lateinit var testHelper: TestHelper

@Before
fun setup() {
testHelper = TestHelper("http://localhost:8080")
testHelper.reset()
}

@Test
fun `cache put and get produce spans`() {
val restClient = testHelper.restClient

// Save a todo (triggers @CachePut -> cache.put span)
val todo = Todo(1L, "test-todo", false)
restClient.saveCachedTodo(todo)
assertEquals(200, restClient.lastKnownStatusCode)

testHelper.ensureTransactionReceived { transaction, _ ->
testHelper.doesTransactionContainSpanWithOp(transaction, "cache.put")
}

testHelper.reset()

// Get the todo (triggers @Cacheable -> cache.get span, should be a hit)
restClient.getCachedTodo(1L)
assertEquals(200, restClient.lastKnownStatusCode)

testHelper.ensureTransactionReceived { transaction, _ ->
testHelper.doesTransactionContainSpanWithOp(transaction, "cache.get")
}
}

@Test
fun `cache evict produces span`() {
val restClient = testHelper.restClient

restClient.deleteCachedTodo(1L)

testHelper.ensureTransactionReceived { transaction, _ ->
testHelper.doesTransactionContainSpanWithOp(transaction, "cache.remove")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ dependencies {
implementation(projects.sentryAsyncProfiler)
implementation(projects.sentryOpenfeature)

// cache tracing
implementation(libs.springboot3.starter.cache)
implementation(libs.caffeine)

// OpenFeature SDK
implementation(libs.openfeature)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package io.sentry.samples.spring.boot.jakarta;

import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/cache/")
public class CacheController {
private final TodoService todoService;

public CacheController(TodoService todoService) {
this.todoService = todoService;
}

@GetMapping("{id}")
Todo get(@PathVariable Long id) {
return todoService.get(id);
}

@PostMapping
Todo save(@RequestBody Todo todo) {
return todoService.save(todo);
}

@DeleteMapping("{id}")
void delete(@PathVariable Long id) {
todoService.delete(id);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
Expand All @@ -19,6 +20,7 @@
import org.springframework.web.reactive.function.client.WebClient;

@SpringBootApplication
@EnableCaching
@EnableScheduling
public class SentryDemoApplication {
public static void main(String[] args) {
Expand Down
Loading
Loading