Skip to content

Commit 35f42d7

Browse files
committed
Merge branch 'fix/kafka-consumer-interceptor-reflection' into fix/kafka-otel-agent-autoconfig
2 parents 22922ac + 934fed9 commit 35f42d7

2 files changed

Lines changed: 17 additions & 26 deletions

File tree

sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/kafka/SentryKafkaConsumerBeanPostProcessor.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,18 @@
2121
public final class SentryKafkaConsumerBeanPostProcessor
2222
implements BeanPostProcessor, PriorityOrdered {
2323

24+
private static final @NotNull String RECORD_INTERCEPTOR_FIELD_NAME = "recordInterceptor";
25+
26+
private final @NotNull String recordInterceptorFieldName;
27+
28+
public SentryKafkaConsumerBeanPostProcessor() {
29+
this(RECORD_INTERCEPTOR_FIELD_NAME);
30+
}
31+
32+
SentryKafkaConsumerBeanPostProcessor(final @NotNull String recordInterceptorFieldName) {
33+
this.recordInterceptorFieldName = recordInterceptorFieldName;
34+
}
35+
2436
private static final class InterceptorReadFailedException extends Exception {
2537
private static final long serialVersionUID = 1L;
2638

@@ -71,7 +83,7 @@ private static final class InterceptorReadFailedException extends Exception {
7183
throws InterceptorReadFailedException {
7284
try {
7385
final @NotNull Field field =
74-
AbstractKafkaListenerContainerFactory.class.getDeclaredField("recordInterceptor");
86+
AbstractKafkaListenerContainerFactory.class.getDeclaredField(recordInterceptorFieldName);
7587
field.setAccessible(true);
7688
return (RecordInterceptor<?, ?>) field.get(factory);
7789
} catch (NoSuchFieldException | IllegalAccessException | RuntimeException e) {

sentry-spring-jakarta/src/test/kotlin/io/sentry/spring/jakarta/kafka/SentryKafkaConsumerBeanPostProcessorTest.kt

Lines changed: 4 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package io.sentry.spring.jakarta.kafka
22

33
import kotlin.test.Test
4-
import kotlin.test.assertEquals
54
import kotlin.test.assertSame
65
import kotlin.test.assertTrue
76
import org.apache.kafka.clients.consumer.Consumer
@@ -97,14 +96,6 @@ class SentryKafkaConsumerBeanPostProcessorTest {
9796

9897
@Test
9998
fun `skips installation when reflection fails and preserves customer interceptor`() {
100-
// Subclass whose declared 'recordInterceptor' field does not exist on the
101-
// AbstractKafkaListenerContainerFactory class lookup path — this simulates the
102-
// future-spring-kafka case where the private field is renamed/removed.
103-
// We can't easily corrupt JDK reflection, so we instead verify the chosen
104-
// contract: when reflection succeeds and yields a non-Sentry interceptor,
105-
// it is preserved as a delegate (covered above). The reflection-failure
106-
// branch is logged at ERROR and returns the bean untouched; see
107-
// SentryKafkaConsumerBeanPostProcessor#postProcessAfterInitialization.
10899
val consumerFactory = mock<ConsumerFactory<String, String>>()
109100
val factory = ConcurrentKafkaListenerContainerFactory<String, String>()
110101
factory.consumerFactory = consumerFactory
@@ -117,29 +108,17 @@ class SentryKafkaConsumerBeanPostProcessorTest {
117108
}
118109
factory.setRecordInterceptor(customerInterceptor)
119110

120-
// Sanity check: customer interceptor is set before BPP runs.
121111
val field = factory.javaClass.superclass.getDeclaredField("recordInterceptor")
122112
field.isAccessible = true
123113
assertSame(customerInterceptor, field.get(factory))
124114

125-
// After BPP runs the customer interceptor must still be reachable
126-
// (either directly, or as the delegate of a SentryKafkaRecordInterceptor).
127-
val processor = SentryKafkaConsumerBeanPostProcessor()
115+
val processor = SentryKafkaConsumerBeanPostProcessor("missingRecordInterceptor")
128116
processor.postProcessAfterInitialization(factory, "kafkaListenerContainerFactory")
129117

130-
val installed = field.get(factory)
131-
val effective =
132-
if (installed is SentryKafkaRecordInterceptor<*, *>) {
133-
val delegateField = SentryKafkaRecordInterceptor::class.java.getDeclaredField("delegate")
134-
delegateField.isAccessible = true
135-
delegateField.get(installed)
136-
} else {
137-
installed
138-
}
139-
assertEquals(
118+
assertSame(
140119
customerInterceptor,
141-
effective,
142-
"customer interceptor must never be silently dropped",
120+
field.get(factory),
121+
"customer interceptor must remain installed when Sentry cannot read it",
143122
)
144123
}
145124
}

0 commit comments

Comments
 (0)