Skip to content

Commit e520524

Browse files
authored
Merge 46225de into 52feca7
2 parents 52feca7 + 46225de commit e520524

26 files changed

Lines changed: 1211 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@
5555

5656
### Features
5757

58+
- 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))
59+
- Add `enableQueueTracing` option and messaging span data conventions ([#5250](https://github.com/getsentry/sentry-java/pull/5250))
5860
- Prevent cross-organization trace continuation ([#5136](https://github.com/getsentry/sentry-java/pull/5136))
5961
- By default, the SDK now extracts the organization ID from the DSN (e.g. `o123.ingest.sentry.io`) and compares it with the `sentry-org_id` value in incoming baggage headers. When the two differ, the SDK starts a fresh trace instead of continuing the foreign one. This guards against accidentally linking traces across organizations.
6062
- New option `enableStrictTraceContinuation` (default `false`): when enabled, both the SDK's org ID **and** the incoming baggage org ID must be present and match for a trace to be continued. Traces with a missing org ID on either side are rejected. Configurable via code (`setStrictTraceContinuation(true)`), `sentry.properties` (`enable-strict-trace-continuation=true`), Android manifest (`io.sentry.strict-trace-continuation.enabled`), or Spring Boot (`sentry.strict-trace-continuation=true`).

gradle/libs.versions.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ springboot3-starter-security = { module = "org.springframework.boot:spring-boot-
184184
springboot3-starter-jdbc = { module = "org.springframework.boot:spring-boot-starter-jdbc", version.ref = "springboot3" }
185185
springboot3-starter-actuator = { module = "org.springframework.boot:spring-boot-starter-actuator", version.ref = "springboot3" }
186186
springboot3-starter-cache = { module = "org.springframework.boot:spring-boot-starter-cache", version.ref = "springboot3" }
187+
spring-kafka3 = { module = "org.springframework.kafka:spring-kafka", version = "3.3.5" }
187188
springboot4-otel = { module = "io.opentelemetry.instrumentation:opentelemetry-spring-boot-starter", version.ref = "otelInstrumentation" }
188189
springboot4-resttestclient = { module = "org.springframework.boot:spring-boot-resttestclient", version.ref = "springboot4" }
189190
springboot4-starter = { module = "org.springframework.boot:spring-boot-starter", version.ref = "springboot4" }

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ dependencies {
6262
implementation(libs.springboot3.starter.cache)
6363
implementation(libs.caffeine)
6464

65+
// kafka
66+
implementation(libs.spring.kafka3)
67+
6568
// OpenFeature SDK
6669
implementation(libs.openfeature)
6770

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package io.sentry.samples.spring.boot.jakarta;
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.boot.jakarta;
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: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Kafka — activate with: --spring.profiles.active=kafka
2+
spring.autoconfigure.exclude=
3+
spring.kafka.bootstrap-servers=localhost:9092
4+
spring.kafka.consumer.group-id=sentry-sample-group
5+
spring.kafka.consumer.auto-offset-reset=earliest
6+
spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer
7+
spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer
8+
spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
9+
spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ spring.quartz.job-store-type=memory
3737

3838
# Cache tracing
3939
sentry.enable-cache-tracing=true
40+
41+
# Kafka is only active with the 'kafka' profile (--spring.profiles.active=kafka)
42+
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration
43+
4044
spring.cache.cache-names=todos
4145
spring.cache.caffeine.spec=maximumSize=500,expireAfterAccess=600s
4246

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ dependencies {
4040
compileOnly(projects.sentryGraphql)
4141
compileOnly(projects.sentryGraphql22)
4242
compileOnly(projects.sentryQuartz)
43+
compileOnly(libs.spring.kafka3)
4344
compileOnly(Config.Libs.springWeb)
4445
compileOnly(Config.Libs.springWebflux)
4546
compileOnly(libs.context.propagation)
@@ -90,6 +91,7 @@ dependencies {
9091
testImplementation(libs.springboot3.starter)
9192
testImplementation(libs.springboot3.starter.aop)
9293
testImplementation(libs.springboot3.starter.graphql)
94+
testImplementation(libs.spring.kafka3)
9395
testImplementation(libs.springboot3.starter.quartz)
9496
testImplementation(libs.springboot3.starter.security)
9597
testImplementation(libs.springboot3.starter.test)

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
import io.sentry.spring.jakarta.checkin.SentryQuartzConfiguration;
3232
import io.sentry.spring.jakarta.exception.SentryCaptureExceptionParameterPointcutConfiguration;
3333
import io.sentry.spring.jakarta.exception.SentryExceptionParameterAdviceConfiguration;
34+
import io.sentry.spring.jakarta.kafka.SentryKafkaConsumerBeanPostProcessor;
35+
import io.sentry.spring.jakarta.kafka.SentryKafkaProducerBeanPostProcessor;
3436
import io.sentry.spring.jakarta.opentelemetry.SentryOpenTelemetryAgentWithoutAutoInitConfiguration;
3537
import io.sentry.spring.jakarta.opentelemetry.SentryOpenTelemetryNoAgentConfiguration;
3638
import io.sentry.spring.jakarta.tracing.CombinedTransactionNameProvider;
@@ -75,6 +77,7 @@
7577
import org.springframework.core.annotation.Order;
7678
import org.springframework.core.env.Environment;
7779
import org.springframework.graphql.execution.DataFetcherExceptionResolverAdapter;
80+
import org.springframework.kafka.core.KafkaTemplate;
7881
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
7982
import org.springframework.security.core.context.SecurityContextHolder;
8083
import org.springframework.web.client.RestClient;
@@ -246,6 +249,26 @@ static class SentryCacheConfiguration {
246249
}
247250
}
248251

252+
@Configuration(proxyBeanMethods = false)
253+
@ConditionalOnClass(KafkaTemplate.class)
254+
@ConditionalOnProperty(name = "sentry.enable-queue-tracing", havingValue = "true")
255+
@Open
256+
static class SentryKafkaQueueConfiguration {
257+
258+
@Bean
259+
public static @NotNull SentryKafkaProducerBeanPostProcessor
260+
sentryKafkaProducerBeanPostProcessor() {
261+
SentryIntegrationPackageStorage.getInstance().addIntegration("SpringKafka");
262+
return new SentryKafkaProducerBeanPostProcessor();
263+
}
264+
265+
@Bean
266+
public static @NotNull SentryKafkaConsumerBeanPostProcessor
267+
sentryKafkaConsumerBeanPostProcessor() {
268+
return new SentryKafkaConsumerBeanPostProcessor();
269+
}
270+
}
271+
249272
@Configuration(proxyBeanMethods = false)
250273
@ConditionalOnClass(ProceedingJoinPoint.class)
251274
@ConditionalOnProperty(
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package io.sentry.spring.boot.jakarta
2+
3+
import io.sentry.spring.jakarta.kafka.SentryKafkaConsumerBeanPostProcessor
4+
import io.sentry.spring.jakarta.kafka.SentryKafkaProducerBeanPostProcessor
5+
import kotlin.test.Test
6+
import org.assertj.core.api.Assertions.assertThat
7+
import org.springframework.boot.autoconfigure.AutoConfigurations
8+
import org.springframework.boot.test.context.runner.ApplicationContextRunner
9+
10+
class SentryKafkaAutoConfigurationTest {
11+
12+
private val contextRunner =
13+
ApplicationContextRunner()
14+
.withConfiguration(AutoConfigurations.of(SentryAutoConfiguration::class.java))
15+
.withPropertyValues(
16+
"sentry.dsn=http://key@localhost/proj",
17+
"sentry.traces-sample-rate=1.0",
18+
"sentry.shutdownTimeoutMillis=0",
19+
"sentry.sessionFlushTimeoutMillis=0",
20+
"sentry.flushTimeoutMillis=0",
21+
"sentry.readTimeoutMillis=50",
22+
"sentry.connectionTimeoutMillis=50",
23+
"sentry.send-modules=false",
24+
"sentry.debug=false",
25+
)
26+
27+
@Test
28+
fun `registers Kafka BPPs when queue tracing is enabled`() {
29+
contextRunner.withPropertyValues("sentry.enable-queue-tracing=true").run { context ->
30+
assertThat(context).hasSingleBean(SentryKafkaProducerBeanPostProcessor::class.java)
31+
assertThat(context).hasSingleBean(SentryKafkaConsumerBeanPostProcessor::class.java)
32+
}
33+
}
34+
35+
@Test
36+
fun `does not register Kafka BPPs when queue tracing is disabled`() {
37+
contextRunner.run { context ->
38+
assertThat(context).doesNotHaveBean(SentryKafkaProducerBeanPostProcessor::class.java)
39+
assertThat(context).doesNotHaveBean(SentryKafkaConsumerBeanPostProcessor::class.java)
40+
}
41+
}
42+
43+
@Test
44+
fun `does not register Kafka BPPs when queue tracing is explicitly false`() {
45+
contextRunner.withPropertyValues("sentry.enable-queue-tracing=false").run { context ->
46+
assertThat(context).doesNotHaveBean(SentryKafkaProducerBeanPostProcessor::class.java)
47+
assertThat(context).doesNotHaveBean(SentryKafkaConsumerBeanPostProcessor::class.java)
48+
}
49+
}
50+
}

0 commit comments

Comments
 (0)