Skip to content

Commit 5e0629d

Browse files
adinauerclaude
andcommitted
fix(spring-jakarta): Warn when Kafka producer tracing silently fails
When ProducerFactory.addPostProcessor() is a no-op (the interface default), the Sentry post-processor is silently dropped and the customer gets zero producer tracing with no signal. Verify registration succeeded via getPostProcessors() after each addPostProcessor() call, and log a WARNING naming the factory bean and pointing toward SentryKafkaProducer.wrap() as the manual fallback. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 0ca42c8 commit 5e0629d

2 files changed

Lines changed: 28 additions & 20 deletions

File tree

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

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

33
import io.sentry.ScopesAdapter;
4+
import io.sentry.SentryLevel;
45
import io.sentry.kafka.SentryKafkaProducer;
56
import org.apache.kafka.clients.producer.Producer;
67
import org.jetbrains.annotations.ApiStatus;
@@ -22,11 +23,10 @@
2223
* KafkaTemplate} beans are left untouched, so all customer-configured listeners, interceptors and
2324
* observation settings are preserved.
2425
*
25-
* <p>Idempotent: re-running on the same factory does not register the post-processor twice.
26-
*
2726
* <p>Note: {@link ProducerFactory#addPostProcessor(ProducerPostProcessor)} is a default method on
28-
* the interface. Custom factories that do not extend {@code DefaultKafkaProducerFactory} and do not
29-
* implement {@code addPostProcessor} will silently no-op.
27+
* the interface that is a no-op unless overridden. Custom factories that do not extend {@code
28+
* DefaultKafkaProducerFactory} will not receive Sentry producer instrumentation; a warning is
29+
* logged at startup in that case.
3030
*/
3131
@ApiStatus.Internal
3232
public final class SentryKafkaProducerBeanPostProcessor
@@ -38,14 +38,21 @@ public final class SentryKafkaProducerBeanPostProcessor
3838
final @NotNull Object bean, final @NotNull String beanName) throws BeansException {
3939
if (bean instanceof ProducerFactory) {
4040
final @NotNull ProducerFactory factory = (ProducerFactory) bean;
41-
42-
for (final Object existing : factory.getPostProcessors()) {
43-
if (existing instanceof SentryProducerPostProcessor) {
44-
return bean;
45-
}
41+
final @NotNull SentryProducerPostProcessor pp = new SentryProducerPostProcessor<>();
42+
factory.addPostProcessor(pp);
43+
if (!factory.getPostProcessors().contains(pp)) {
44+
ScopesAdapter.getInstance()
45+
.getOptions()
46+
.getLogger()
47+
.log(
48+
SentryLevel.WARNING,
49+
"Sentry Kafka producer tracing not active for ProducerFactory '%s' (%s). "
50+
+ "addPostProcessor() was not honored — the factory may not extend "
51+
+ "DefaultKafkaProducerFactory. Wrap producers manually with "
52+
+ "SentryKafkaProducer.wrap(producer).",
53+
beanName,
54+
factory.getClass().getName());
4655
}
47-
48-
factory.addPostProcessor(new SentryProducerPostProcessor<>());
4956
}
5057
return bean;
5158
}

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

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import org.apache.kafka.clients.producer.Producer
88
import org.mockito.kotlin.any
99
import org.mockito.kotlin.argumentCaptor
1010
import org.mockito.kotlin.mock
11-
import org.mockito.kotlin.never
1211
import org.mockito.kotlin.verify
1312
import org.mockito.kotlin.whenever
1413
import org.springframework.kafka.core.DefaultKafkaProducerFactory
@@ -20,7 +19,8 @@ class SentryKafkaProducerBeanPostProcessorTest {
2019
@Test
2120
fun `registers Sentry post-processor on ProducerFactory`() {
2221
val factory = mock<ProducerFactory<String, String>>()
23-
whenever(factory.postProcessors).thenReturn(emptyList())
22+
val pp = SentryKafkaProducerBeanPostProcessor.SentryProducerPostProcessor<String, String>()
23+
whenever(factory.postProcessors).thenReturn(listOf(pp))
2424
val processor = SentryKafkaProducerBeanPostProcessor()
2525

2626
processor.postProcessAfterInitialization(factory, "kafkaProducerFactory")
@@ -33,16 +33,16 @@ class SentryKafkaProducerBeanPostProcessorTest {
3333
}
3434

3535
@Test
36-
fun `is idempotent when Sentry post-processor is already registered`() {
36+
fun `does not throw when addPostProcessor is a no-op (default interface method)`() {
37+
// Factory using the default no-op addPostProcessor / getPostProcessors
3738
val factory = mock<ProducerFactory<String, String>>()
38-
val existing =
39-
SentryKafkaProducerBeanPostProcessor.SentryProducerPostProcessor<String, String>()
40-
whenever(factory.postProcessors).thenReturn(listOf(existing))
39+
whenever(factory.postProcessors).thenReturn(emptyList())
4140
val processor = SentryKafkaProducerBeanPostProcessor()
4241

43-
processor.postProcessAfterInitialization(factory, "kafkaProducerFactory")
42+
// Should complete without throwing, and log a warning via ScopesAdapter
43+
processor.postProcessAfterInitialization(factory, "myFactory")
4444

45-
verify(factory, never()).addPostProcessor(any())
45+
verify(factory).addPostProcessor(any())
4646
}
4747

4848
@Test
@@ -58,7 +58,8 @@ class SentryKafkaProducerBeanPostProcessorTest {
5858
@Test
5959
fun `returns the same bean instance`() {
6060
val factory = mock<ProducerFactory<String, String>>()
61-
whenever(factory.postProcessors).thenReturn(emptyList())
61+
val pp = SentryKafkaProducerBeanPostProcessor.SentryProducerPostProcessor<String, String>()
62+
whenever(factory.postProcessors).thenReturn(listOf(pp))
6263
val processor = SentryKafkaProducerBeanPostProcessor()
6364

6465
val result = processor.postProcessAfterInitialization(factory, "kafkaProducerFactory")

0 commit comments

Comments
 (0)