Skip to content

Commit ce746d1

Browse files
authored
Merge pull request #5348 from getsentry/feat/queue-instrumentation-spring-boot-4
feat(spring): [Queue Instrumentation 40] Add Spring Boot 4 Kafka tracing
2 parents 22bca33 + 4c3f5cd commit ce746d1

32 files changed

Lines changed: 1769 additions & 17 deletions

File tree

CHANGELOG.md

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

55
### Features
66

7+
- Add Kafka queue tracing for Spring Boot 4 ([#5348](https://github.com/getsentry/sentry-java/pull/5348))
78
- Add `sentry-kafka` module for Kafka queue instrumentation without Spring ([#5288](https://github.com/getsentry/sentry-java/pull/5288))
89
- Add Kafka queue tracing for Spring Boot 3 ([#5254](https://github.com/getsentry/sentry-java/pull/5254)), ([#5255](https://github.com/getsentry/sentry-java/pull/5255)), ([#5256](https://github.com/getsentry/sentry-java/pull/5256))
910
- Add `enableQueueTracing` option and messaging span data conventions ([#5250](https://github.com/getsentry/sentry-java/pull/5250))

gradle/libs.versions.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ springboot3-starter-jdbc = { module = "org.springframework.boot:spring-boot-star
184184
springboot3-starter-actuator = { module = "org.springframework.boot:spring-boot-starter-actuator", version.ref = "springboot3" }
185185
springboot3-starter-cache = { module = "org.springframework.boot:spring-boot-starter-cache", version.ref = "springboot3" }
186186
spring-kafka3 = { module = "org.springframework.kafka:spring-kafka", version = "3.3.5" }
187+
spring-kafka4 = { module = "org.springframework.kafka:spring-kafka" }
187188
kafka-clients = { module = "org.apache.kafka:kafka-clients", version = "3.8.1" }
188189
springboot4-otel = { module = "io.opentelemetry.instrumentation:opentelemetry-spring-boot-starter", version.ref = "otelInstrumentation" }
189190
springboot4-resttestclient = { module = "org.springframework.boot:spring-boot-resttestclient", version.ref = "springboot4" }
@@ -201,6 +202,7 @@ springboot4-starter-webclient = { module = "org.springframework.boot:spring-boot
201202
springboot4-starter-jdbc = { module = "org.springframework.boot:spring-boot-starter-jdbc", version.ref = "springboot4" }
202203
springboot4-starter-actuator = { module = "org.springframework.boot:spring-boot-starter-actuator", version.ref = "springboot4" }
203204
springboot4-starter-cache = { module = "org.springframework.boot:spring-boot-starter-cache", version.ref = "springboot4" }
205+
springboot4-starter-kafka = { module = "org.springframework.boot:spring-boot-starter-kafka", version.ref = "springboot4" }
204206
timber = { module = "com.jakewharton.timber:timber", version = "4.7.1" }
205207

206208
# Animalsniffer signature

sentry-samples/sentry-samples-spring-boot-4-opentelemetry-noagent/build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ dependencies {
5858
implementation(projects.sentryOpentelemetry.sentryOpentelemetryAgentlessSpring)
5959
implementation(projects.sentryAsyncProfiler)
6060

61+
// kafka
62+
implementation(libs.springboot4.starter.kafka)
63+
implementation(projects.sentryKafka)
64+
6165
// database query tracing
6266
implementation(projects.sentryJdbc)
6367
runtimeOnly(libs.hsqldb)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package io.sentry.samples.spring.boot4.queues.kafka;
2+
3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
import org.springframework.context.annotation.Profile;
6+
import org.springframework.kafka.annotation.KafkaListener;
7+
import org.springframework.stereotype.Component;
8+
9+
@Component
10+
@Profile("kafka")
11+
public class KafkaConsumer {
12+
13+
private static final Logger logger = LoggerFactory.getLogger(KafkaConsumer.class);
14+
15+
@KafkaListener(topics = "sentry-topic", groupId = "sentry-sample-group")
16+
public void listen(String message) {
17+
logger.info("Received message: {}", message);
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package io.sentry.samples.spring.boot4.queues.kafka;
2+
3+
import org.springframework.context.annotation.Profile;
4+
import org.springframework.kafka.core.KafkaTemplate;
5+
import org.springframework.web.bind.annotation.GetMapping;
6+
import org.springframework.web.bind.annotation.RequestMapping;
7+
import org.springframework.web.bind.annotation.RequestParam;
8+
import org.springframework.web.bind.annotation.RestController;
9+
10+
@RestController
11+
@Profile("kafka")
12+
@RequestMapping("/kafka")
13+
public class KafkaController {
14+
15+
private final KafkaTemplate<String, String> kafkaTemplate;
16+
17+
public KafkaController(KafkaTemplate<String, String> kafkaTemplate) {
18+
this.kafkaTemplate = kafkaTemplate;
19+
}
20+
21+
@GetMapping("/produce")
22+
String produce(@RequestParam(defaultValue = "hello from sentry!") String message) {
23+
kafkaTemplate.send("sentry-topic", message);
24+
return "Message sent: " + message;
25+
}
26+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Kafka — activate with: --spring.profiles.active=kafka
2+
sentry.enable-queue-tracing=true
3+
4+
spring.kafka.bootstrap-servers=localhost:9092
5+
spring.kafka.consumer.group-id=sentry-sample-group
6+
spring.kafka.consumer.auto-offset-reset=earliest
7+
spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer
8+
spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer
9+
spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
10+
spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer
11+
12+
logging.level.org.apache.kafka=warn
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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 KafkaOtelCoexistenceSystemTest {
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 `Sentry Kafka integration is suppressed when OTel is active`() {
19+
val restClient = testHelper.restClient
20+
21+
restClient.produceKafkaMessage("otel-coexistence-test")
22+
assertEquals(200, restClient.lastKnownStatusCode)
23+
24+
testHelper.ensureTransactionReceived { transaction, _ ->
25+
transaction.transaction == "GET /kafka/produce" &&
26+
transaction.sdk?.integrationSet?.contains("SpringKafka") != true &&
27+
transaction.spans.any { span ->
28+
span.op == "queue.publish" &&
29+
span.origin == "auto.opentelemetry" &&
30+
span.data?.get("messaging.system") == "kafka"
31+
}
32+
}
33+
34+
testHelper.ensureTransactionReceived { transaction, _ ->
35+
transaction.contexts.trace?.operation == "queue.process" &&
36+
transaction.contexts.trace?.origin == "auto.opentelemetry" &&
37+
transaction.contexts.trace?.data?.get("messaging.system") == "kafka" &&
38+
transaction.sdk?.integrationSet?.contains("SpringKafka") != true
39+
}
40+
}
41+
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ dependencies {
5959
implementation(projects.sentryAsyncProfiler)
6060
implementation(libs.otel)
6161

62+
// kafka
63+
implementation(libs.springboot4.starter.kafka)
64+
implementation(projects.sentryKafka)
65+
6266
// cache tracing
6367
implementation(libs.springboot4.starter.cache)
6468
implementation(libs.caffeine)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package io.sentry.samples.spring.boot4.queues.kafka;
2+
3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
import org.springframework.context.annotation.Profile;
6+
import org.springframework.kafka.annotation.KafkaListener;
7+
import org.springframework.stereotype.Component;
8+
9+
@Component
10+
@Profile("kafka")
11+
public class KafkaConsumer {
12+
13+
private static final Logger logger = LoggerFactory.getLogger(KafkaConsumer.class);
14+
15+
@KafkaListener(topics = "sentry-topic", groupId = "sentry-sample-group")
16+
public void listen(String message) {
17+
logger.info("Received message: {}", message);
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package io.sentry.samples.spring.boot4.queues.kafka;
2+
3+
import org.springframework.context.annotation.Profile;
4+
import org.springframework.kafka.core.KafkaTemplate;
5+
import org.springframework.web.bind.annotation.GetMapping;
6+
import org.springframework.web.bind.annotation.RequestMapping;
7+
import org.springframework.web.bind.annotation.RequestParam;
8+
import org.springframework.web.bind.annotation.RestController;
9+
10+
@RestController
11+
@Profile("kafka")
12+
@RequestMapping("/kafka")
13+
public class KafkaController {
14+
15+
private final KafkaTemplate<String, String> kafkaTemplate;
16+
17+
public KafkaController(KafkaTemplate<String, String> kafkaTemplate) {
18+
this.kafkaTemplate = kafkaTemplate;
19+
}
20+
21+
@GetMapping("/produce")
22+
String produce(@RequestParam(defaultValue = "hello from sentry!") String message) {
23+
kafkaTemplate.send("sentry-topic", message);
24+
return "Message sent: " + message;
25+
}
26+
}

0 commit comments

Comments
 (0)