fix(spring-jakarta): [Queue Instrumentation 13] Align enqueue time with Python#5283
Merged
adinauer merged 1 commit intofeat/queue-instrumentationfrom Apr 29, 2026
Conversation
…th Python Store sentry-task-enqueued-time as epoch seconds and compute receive latency from seconds on the consumer side. This aligns Java Kafka queue instrumentation with sentry-python Celery behavior for cross-SDK interoperability. Co-Authored-By: Claude <noreply@anthropic.com>
This was referenced Apr 10, 2026
Merged
Contributor
Semver Impact of This PR🟢 Patch (bug fixes) 📋 Changelog PreviewThis is how your changes will appear in the changelog. This PR will not appear in the changelog. 🤖 This preview updates automatically when you update the PR. |
This was referenced Apr 10, 2026
Sentry Build Distribution
|
Contributor
Performance metrics 🚀
|
This was referenced Apr 13, 2026
This was referenced Apr 20, 2026
Merged
Merged
Merged
This was referenced Apr 22, 2026
Merged
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 24cff6d. Configure here.
This was referenced Apr 22, 2026
Merged
8 tasks
Base automatically changed from
fix/queue-instrumentation-retry-count
to
feat/queue-instrumentation
April 29, 2026 13:35
55c5586
into
feat/queue-instrumentation
100 of 107 checks passed
This was referenced Apr 30, 2026
adinauer
added a commit
that referenced
this pull request
May 6, 2026
* collection: Queue Instrumentation
* feat(core): Add enableQueueTracing option and messaging span data conventions
Add enableQueueTracing boolean to SentryOptions (default false) and
ExternalOptions (nullable Boolean) with merge support. Add messaging.*
keys to SpanDataConvention for queue instrumentation span data.
Co-Authored-By: Claude <noreply@anthropic.com>
* changelog
* feat(samples): Add Kafka producer and consumer to Spring Boot 3 sample app
Add spring-kafka dependency and a simple Kafka producer/consumer setup
behind a 'kafka' Spring profile. Includes a REST endpoint to produce
messages and a KafkaListener that consumes them.
Kafka auto-configuration is excluded by default and only activated
when the 'kafka' profile is enabled.
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(spring-jakarta): Add Kafka producer instrumentation
Add SentryKafkaProducerWrapper that overrides doSend to create
queue.publish spans for all KafkaTemplate send operations. Injects
sentry-trace, baggage, and sentry-task-enqueued-time headers for
distributed tracing and receive latency calculation.
Add SentryKafkaProducerBeanPostProcessor to automatically wrap
KafkaTemplate beans.
Co-Authored-By: Claude <noreply@anthropic.com>
* changelog
* feat(spring-jakarta): Add Kafka consumer instrumentation
Add SentryKafkaRecordInterceptor that creates queue.process transactions
for incoming Kafka records. Forks scopes per record, extracts sentry-trace
and baggage headers for distributed tracing via continueTrace, and
calculates messaging.message.receive.latency from the enqueued-time header.
Composes with existing RecordInterceptor via delegation. Span lifecycle
is managed through success/failure callbacks.
Add SentryKafkaConsumerBeanPostProcessor to register the interceptor on
ConcurrentKafkaListenerContainerFactory beans.
Co-Authored-By: Claude <noreply@anthropic.com>
* changelog
* feat(spring-boot-jakarta): Add Kafka queue auto-configuration
Register SentryKafkaProducerBeanPostProcessor and
SentryKafkaConsumerBeanPostProcessor when spring-kafka is on the
classpath and sentry.enable-queue-tracing=true. Follows the same
pattern as SentryCacheConfiguration.
Co-Authored-By: Claude <noreply@anthropic.com>
* changelog
* test(samples): Add Kafka queue system tests for Spring Boot 3
Add KafkaQueueSystemTest with e2e tests for:
- Producer endpoint creates queue.publish span
- Consumer creates queue.process transaction
- Distributed tracing (producer and consumer share same trace)
- Messaging attributes on publish span and process transaction
Also add produceKafkaMessage to RestTestClient and enable
sentry.enable-queue-tracing in the kafka profile properties.
Requires a running Kafka broker at localhost:9092 and the sample app
started with --spring.profiles.active=kafka.
Co-Authored-By: Claude <noreply@anthropic.com>
* docs: Add rule against force-pushing stack branches
Force-pushing a stack branch can cause GitHub to auto-merge or
auto-close other PRs in the stack. Add explicit guidance to never
use --force, --force-with-lease, or amend+push on stack branches.
* docs: Also prohibit --amend on stack branches
* feat(samples): Add Kafka producer and consumer to Spring Boot 3 OTel sample apps
Add Kafka queue tracing support to both the OTel agent and agentless
Spring Boot 3 sample applications. Each sample gets a KafkaController
for producing messages and a KafkaConsumer listener, activated via the
'kafka' Spring profile. Kafka auto-configuration is excluded by default
and only enabled when the kafka profile is active.
* fix(spring-boot-jakarta): Disable Sentry Kafka instrumentation when OTel is active
Skip registration of SentryKafkaProducerBeanPostProcessor and
SentryKafkaConsumerBeanPostProcessor when a Sentry OpenTelemetry
integration (agent or agentless) is on the classpath. OpenTelemetry
provides its own Kafka instrumentation, so Sentry's would create
duplicate spans.
* fix(core): Add Kafka span origins to ignored list for OpenTelemetry
Add auto.queue.spring_jakarta.kafka.producer and
auto.queue.spring_jakarta.kafka.consumer to the ignored span origins
when running with OTel agent or agentless-spring. Prevents duplicate
spans when both Sentry and OTel Kafka instrumentation are active.
* ref(spring-jakarta): Replace SentryKafkaProducerWrapper with SentryProducerInterceptor
Replace the KafkaTemplate subclass approach with a Kafka-native
ProducerInterceptor. The BeanPostProcessor now sets the interceptor
on the existing KafkaTemplate instead of replacing the bean, which
preserves any custom configuration on the template.
Existing customer interceptors are composed using Spring's
CompositeProducerInterceptor. If reflection fails to read the
existing interceptor, a warning is logged.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(spring-jakarta): Update consumer references and add reflection warning log
Update SentryKafkaRecordInterceptor and its test to reference
SentryProducerInterceptor instead of the removed
SentryKafkaProducerWrapper.
Add a warning log in SentryKafkaConsumerBeanPostProcessor when
reflection fails to read the existing RecordInterceptor, so users
know their custom interceptor may not be chained.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(spring-jakarta): Initialize Sentry in SentryProducerInterceptorTest
TransactionContext constructor requires ScopesAdapter.getOptions() to
be non-null for thread checker access. Add initForTest/close to
ensure Sentry is properly initialized during tests.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(spring-jakarta): Initialize Sentry in consumer test, fix API file ordering
Add initForTest/close to SentryKafkaRecordInterceptorTest to fix NPE
from TransactionContext constructor requiring initialized Sentry.
Regenerate API file to fix alphabetical ordering of
SentryProducerInterceptor entry.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(spring-jakarta): Clean up stale ThreadLocal context in Kafka consumer interceptor
Implement clearThreadState() and defensive cleanup in intercept() to
prevent ThreadLocal leaks of SentryRecordContext. Spring Kafka calls
clearThreadState() in the poll loop's finally block, making it the
most reliable cleanup hook for edge cases where success()/failure()
callbacks are skipped (e.g. Error thrown by listener).
Also add defensive cleanup at the start of intercept() to handle any
stale context from a previous record that was not properly cleaned up.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(spring-jakarta): Fork root scopes and skip when OTel is active in Kafka consumer interceptor
Use Sentry.forkedRootScopes() instead of scopes.forkedScopes() so each
Kafka message starts with a clean scope from root, matching the pattern
used by SentryWebFilter for reactive request boundaries.
Add isIgnored() check using SpanUtils.isIgnored() on the trace origin
so the interceptor no-ops when OTel is active and the origin is in the
ignored span origins list, consistent with SentryTracingFilter.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(spring-jakarta): Guard entire span lifecycle in Kafka producer interceptor
Wrap all span operations (startChild, setData, injectHeaders, finish)
in a single try-catch so instrumentation can never break the customer's
Kafka send. The record is always returned regardless of any exception
in Sentry code.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(spring-jakarta): [Queue Instrumentation 12] Add Kafka retry count attribute
Set messaging.message.retry.count on queue.process transactions when the Spring Kafka delivery attempt header is present. This keeps retry context on consumer traces without changing transaction lifecycle behavior.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(spring-jakarta): [Queue Instrumentation 13] Align enqueue time with Python
Store sentry-task-enqueued-time as epoch seconds and compute receive latency from seconds on the consumer side. This aligns Java Kafka queue instrumentation with sentry-python Celery behavior for cross-SDK interoperability.
Co-Authored-By: Claude <noreply@anthropic.com>
* ref(kafka): Extract sentry-kafka module from spring-jakarta
Move Kafka producer interceptor to a new sentry-kafka module and rename
to SentryKafkaProducerInterceptor. Add SentryKafkaConsumerInterceptor
for vanilla kafka-clients users. Spring integration now depends on
sentry-kafka and passes a Spring-specific trace origin.
This allows non-Spring applications to use Kafka queue instrumentation
directly via kafka-clients interceptor config.
Co-Authored-By: Claude <noreply@anthropic.com>
* changelog
* feat(kafka): Add no-arg producer interceptor for Kafka config
Allow kafka-clients to instantiate SentryKafkaProducerInterceptor via interceptor.classes by adding a no-arg constructor that uses ScopesAdapter. This makes native Kafka interceptor wiring work out of the box in applications and samples.\n\nAlso add a Kafka tracing example to the console sample with a transaction-scoped producer send, and cover no-arg constructor behavior in sentry-kafka tests.
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(kafka): Add consumer demo to console sample
Show end-to-end Kafka queue tracing in the console sample by running a background consumer thread, producing a message, and waiting for consume before exit.\n\nAdd a no-arg constructor to SentryKafkaConsumerInterceptor so kafka-clients can instantiate it from interceptor.classes, and add test coverage for that constructor.
Co-Authored-By: Claude <noreply@anthropic.com>
* ref(samples): Extract Kafka console showcase into dedicated class
Move Kafka producer/consumer showcase logic out of Main into KafkaShowcase to make the sample easier to read and follow. Keep runtime behavior unchanged by preserving the same demo entry point and flow.
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(samples): Add opt-in Kafka console e2e coverage
Gate the console Kafka showcase behind SENTRY_SAMPLE_KAFKA_BOOTSTRAP_SERVERS so Kafka behavior is enabled only when configured. Keep the showcase isolated in KafkaShowcase and use fail-fast Kafka client timeouts for local runs.\n\nExtend console system tests to assert producer and consumer queue tracing when Kafka is enabled. Update system-test-runner to provision or reuse a local Kafka broker for the console module and clean up runner-managed resources.
Co-Authored-By: Claude <noreply@anthropic.com>
* ref(samples): Move KafkaShowcase to kafka subpackage
Move KafkaShowcase under io.sentry.samples.console.kafka and update Main to
import the relocated class.
This keeps Kafka-specific sample code grouped in a dedicated package
without changing runtime behavior.
Co-Authored-By: Claude <noreply@anthropic.com>
* Update KafkaShowcase.java
extract constant
* Update KafkaShowcase.java
extract methods
* Update KafkaShowcase.java
refactor
* Format code
* fix
* ref(samples): Clarify Kafka setup in console showcase
Restructure KafkaShowcase to highlight the required Sentry interceptor
configuration for producer and consumer setups.
Split property construction into explicit helper methods and rename the
entrypoint to make customer integration requirements easier to follow
without changing behavior.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(test): Enable Kafka profile for Spring Kafka system tests
Make the system test runner configure Kafka requirements by module.
Start Kafka and set SPRING_PROFILES_ACTIVE=kafka for modules that need
Kafka-backed Spring endpoints so queue system tests run with the expected
routing and broker configuration.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(spring): Guard Kafka auto-config on sentry-kafka
Require the sentry-kafka producer interceptor class before activating
Spring Boot Jakarta queue auto-configuration. This keeps sentry-kafka
optional for customers who only use the starter without Kafka queue
tracing support on the classpath.
Add a regression test that hides sentry-kafka from the classloader and
verifies the Kafka bean post-processors are skipped instead of being
registered.
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(kafka): [Queue Instrumentation 17] Add manual consumer tracing helper
Add an experimental helper for wrapping raw Kafka consumer record
processing in queue.process transactions. This exposes Kafka consumer
tracing outside interceptor-based integrations.
Capture messaging metadata and distributed tracing context in the
helper so future queue instrumentation can reuse the same behavior.
Co-Authored-By: Claude <noreply@anthropic.com>
* ref(kafka): Remove raw consumer interceptor
Remove the raw Kafka consumer interceptor from sentry-kafka and update the console sample to use the manual consumer tracing helper instead. Keep producer tracing on the interceptor path and move consumer tracing to explicit record processing.
Co-Authored-By: Claude <noreply@anthropic.com>
* ref(samples): Clarify Kafka consumer tracing sample
Print the consumed Kafka record inside the manual consumer tracing callback so the sample shows where application processing happens. Update the console system test to assert the manual queue.process transaction and its manual consumer origin.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(kafka): Honor ignored producer span origins
Short-circuit the raw Kafka producer interceptor when its trace origin is
configured in ignoredSpanOrigins.
This lets customers disable the integration quickly without relying on the
later no-op span path, and keeps the interceptor from injecting tracing
headers when the origin is ignored.
Co-Authored-By: Claude <noreply@anthropic.com>
* ref(spring): Use injected scopes in Kafka interceptor
Stop the Spring Kafka record interceptor from reaching through the
static Sentry API when forking root scopes. This keeps the raw Kafka
and Spring Kafka paths aligned and makes the interceptor easier to test.
Co-Authored-By: Claude <noreply@anthropic.com>
* ref(samples): [Queue Instrumentation 18] Move Kafka sources into queues.kafka package
Move KafkaConsumer and KafkaController in the three Spring Boot Jakarta
samples (jakarta, jakarta-opentelemetry, jakarta-opentelemetry-noagent)
into a queues.kafka sub-package.
No behavior change. Groups the Kafka-specific sample sources so future
queue integrations can sit next to them under queues.
Co-Authored-By: Claude <noreply@anthropic.com>
* ref(samples): [Queue Instrumentation 19] Drop Kafka auto-config exclude from Spring Boot samples
Remove `spring.autoconfigure.exclude=KafkaAutoConfiguration` from the
default `application.properties` and the matching empty override from
`application-kafka.properties` in the three Spring Boot Jakarta samples.
`spring.autoconfigure.exclude` is a single list property, so overriding
it in a profile replaces the whole list rather than merging. Adding a
sibling `rabbitmq` profile with the same pattern would not compose —
activating one profile would unsilence the other's auto-config.
The `@Profile("kafka")` annotations already on `KafkaConsumer` and
`KafkaController` gate the actual listener container and endpoint, so
no broker connection is attempted when the profile is inactive.
`KafkaAutoConfiguration` still runs and creates an unused
`KafkaTemplate` bean in that case, which is harmless.
Sentry's own Kafka auto-config remains gated on
`sentry.enable-queue-tracing=true`, which is only set in
`application-kafka.properties`, so Sentry instrumentation behavior is
unchanged.
* ref(kafka): [Queue Instrumentation 20] Log Kafka instrumentation failures
Previously `SentryKafkaProducerInterceptor.onSend(...)` and
`SentryKafkaConsumerTracing` silently swallowed any `Throwable` thrown
while instrumenting a Kafka record. That protects customer Kafka I/O
from breakage, but makes instrumentation bugs invisible.
Log each caught `Throwable` to the SDK logger at `SentryLevel.ERROR`
(matching the existing pattern in `RequestPayloadExtractor`) before
continuing the fail-open path:
- `SentryKafkaProducerInterceptor`: producer span creation / header injection
- `SentryKafkaConsumerTracing`: scope fork + `makeCurrent`, transaction
start, transaction finish
No behavior change for customer callbacks or Kafka send/receive: the
catches still swallow the throwable, they now just surface it via the
SDK's own logger.
`SentryKafkaRecordInterceptor` (Spring) was reviewed and intentionally
left as-is — it does not wrap its instrumentation in `catch (Throwable)`
blocks, so there is nothing silent to log. The `NumberFormatException`
branches on malformed `sentry-task-enqueued-time` headers are expected
input, not instrumentation faults, and remain silent.
* fix(kafka): [Queue Instrumentation 21] Preserve third-party baggage on Kafka producer records
`SentryKafkaProducerInterceptor.injectHeaders(...)` previously removed and
overwrote the outgoing `baggage` header on every record, discarding any
third-party baggage entries already present (e.g. set by another vendor's
instrumentation or the application itself).
Read the existing `baggage` header values off the `ProducerRecord` and
pass them to `TracingUtils.trace(...)`. The downstream
`BaggageHeader.fromBaggageAndOutgoingHeader` preserves non-`sentry-*`
entries in the outgoing header while Sentry continues to own its own
keys.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(spring-boot-jakarta): [Queue Instrumentation 22] Cover spring-kafka class-absence gate
`SentryKafkaQueueConfiguration` in `SentryAutoConfiguration` gates the
Kafka BPPs on both `org.springframework.kafka.core.KafkaTemplate` and
`io.sentry.kafka.SentryKafkaProducerInterceptor` being present on the
classpath. Only the latter was covered by a test.
Add a `FilteredClassLoader(KafkaTemplate::class.java)` test that asserts
neither `SentryKafkaProducerBeanPostProcessor` nor
`SentryKafkaConsumerBeanPostProcessor` is registered when spring-kafka
is missing, even with `sentry.enable-queue-tracing=true`.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(spring-jakarta): [Queue Instrumentation 23] Install Kafka context before trace setup
Store the lifecycle token in the thread-local context immediately after
makeCurrent() so Spring's failure and clearThreadState callbacks can
always clean it up.
Previously, exceptions from trace continuation or transaction setup could
happen before the context was published, leaving cleanup dependent on
later stale-context handling instead of the normal interceptor callback
path.
* fix(kafka): [Queue Instrumentation 24] Read all baggage headers on consumers
Pass every Kafka baggage header through trace continuation in both the
raw Kafka helper and the Spring Kafka record interceptor.
Previously both consumer paths used lastHeader("baggage"), which dropped
all earlier baggage values and could break interop with upstream OTel or
other W3C baggage producers. Reading the full header list preserves the
existing baggage context during queue trace continuation.
* fix(kafka): [Queue Instrumentation 25] Finish producer spans on failures
Keep a local producer child span reference and always finish it when
instrumentation fails after span creation. This preserves fail-open send
behavior without leaking unfinished queue.publish spans.
Add a regression test covering header injection failures.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(kafka): [Queue Instrumentation 26] Mark producer interceptor experimental
The raw kafka producer path requires customers to reference
SentryKafkaProducerInterceptor directly by class name, so it should not be
marked internal. Align it with the customer-facing queue tracing surface by
marking it experimental instead.
Audit the remaining Kafka classes still marked internal and keep them as-is:
the Spring bean post processors and Spring record interceptor remain
framework wiring internals rather than direct customer entry points.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(spring-jakarta): [Queue Instrumentation 27] Delegate Kafka record thread-state hooks
SentryKafkaRecordInterceptor wraps an existing customer RecordInterceptor
when one is present on the listener container factory, but it previously
only delegated intercept, success, failure, and afterRecord.
setupThreadState was not overridden, so the default no-op from
ThreadStateProcessor shadowed any delegate implementation. clearThreadState
performed Sentry cleanup but never forwarded to the delegate either.
Customers relying on these hooks for MDC, security context, or other
thread-local state on Kafka listener threads would silently lose that
behavior once Sentry auto-wrapped their interceptor.
Delegate setupThreadState to the wrapped interceptor, and in
clearThreadState run Sentry cleanup inside try and delegate to the wrapped
interceptor in finally so delegate cleanup still executes if Sentry
cleanup throws.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(samples): Cover OTel Jakarta Kafka coexistence end-to-end
Enable the Kafka Spring profile (and Kafka broker) for the two OTel Spring
Boot 3 Jakarta sample modules in the system-test runner, and add a Kafka
system test in each that produces a message and asserts no Sentry-style
`queue.publish` / `queue.process` span/transaction is emitted.
SentryKafkaQueueConfiguration is guarded by
@ConditionalOnMissingClass("io.sentry.opentelemetry.SentryAutoConfigurationCustomizerProvider"),
so the Sentry Kafka bean post-processors must not be wired when the Sentry
OTel integration is present. The new assertions lock that suppression into
CI for both the agent and noagent OTel Jakarta samples.
Addresses review finding F-011.
* fix(spring-jakarta): [Queue Instrumentation 29] Set body_size on Spring Kafka consumer transaction
The Spring Kafka consumer path (`SentryKafkaRecordInterceptor`) never set
`messaging.message.body_size`, while the raw Kafka consumer helper
(`SentryKafkaConsumerTracing`) already sets it from
`ConsumerRecord#serializedValueSize()`.
Both are first-party Kafka consumer integrations shipped in the same
stack and should emit the same messaging schema so dashboards and
queries remain consistent across Spring vs. raw Kafka setups.
Mirror the raw helper: set `SpanDataConvention.MESSAGING_MESSAGE_BODY_SIZE`
on the `queue.process` transaction when `serializedValueSize() >= 0`.
Add regression tests for both the positive and the -1 (unknown) cases.
#skip-changelog
* test(spring-jakarta): [Queue Instrumentation 30] Cover Kafka record interceptor lifecycle edge cases
Add three regression tests for SentryKafkaRecordInterceptor that pin down
the lifecycle contract around clearThreadState cleanup:
- full lifecycle intercept -> success -> clearThreadState closes the
lifecycle token exactly once and does not double-finish the transaction
- when a delegating interceptor returns null from intercept (filtering
the record), the safety net in clearThreadState still finishes the
transaction and closes the token
- when a delegating interceptor throws from intercept, clearThreadState
still finishes the transaction and closes the token after the
exception has propagated
Addresses review finding R6-F001.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(kafka): [Queue Instrumentation 31] Write enqueued-time header as plain decimal
The sentry-task-enqueued-time Kafka header was serialized via
String.valueOf(double), which emits scientific notation (e.g.
1.776933649613E9) for epoch-seconds values. Cross-SDK consumers
(sentry-python, -ruby, -php, -dotnet) expect a plain decimal like
1776938295.692000 and could not parse the Java output, defeating the
cross-SDK alignment goal of #5283.
Route the value through DateUtils.doubleToBigDecimal(...).toString(),
the same helper already used to serialize epoch-seconds timestamps in
SentryTransaction, SentrySpan, SentryLogEvent, etc. At the pinned scale
of 6, BigDecimal.toString() produces plain decimal form for all
realistic epoch-seconds magnitudes.
Add regression assertions that reject scientific notation and pin the
plain-decimal format in SentryKafkaProducerInterceptorTest.
Co-Authored-By: Claude <noreply@anthropic.com>
* changelog
* test(spring-boot-jakarta): [Queue Instrumentation 32] Filter OTel in Kafka auto-config negative tests
The regression tests "does not register Kafka BPPs when sentry-kafka is
not present" and "...when spring-kafka is not present" previously
passed for the wrong reason: OTel's SentryAutoConfigurationCustomizerProvider
is on the test classpath as a testImplementation dependency, so the
@ConditionalOnMissingClass(OTel) gate on SentryKafkaQueueConfiguration
was already blocking the beans independent of the @ConditionalOnClass
check the tests were meant to validate.
Make noSentryKafkaClassLoader and noSpringKafkaClassLoader additionally
filter SentryAutoConfigurationCustomizerProvider so only the gate under
test can be the blocker. Verified by temporarily removing
SentryKafkaProducerInterceptor from the @ConditionalOnClass list: the
test now correctly fails, proving it actually guards against the
regression it is named for.
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(opentelemetry): [Queue Instrumentation 33] Map OTel messaging spans to Sentry queue ops
Wire OTel messaging spans into the Sentry Queues product when
`sentry.enable-queue-tracing=true` so OTel-only setups (e.g. the
agentless Spring Boot Jakarta sample) populate queue dashboards without
needing the Sentry-native Kafka interceptors.
`SpanDescriptionExtractor` now recognizes spans carrying
`messaging.system` and maps them to `queue.publish` / `queue.process` /
`queue.receive` ops, using the destination name as the description and
`TransactionNameSource.TASK`. Op selection prefers
`messaging.operation.type` (current OTel semconv), falls back to the
deprecated `messaging.operation`, and only as a last resort consults
`SpanKind` — `SpanKind.CONSUMER` is overloaded for both `receive` and
`process`, so attribute-driven mapping is required to disambiguate. The
extractor takes `SentryOptions` so the mapping stays gated; when the
flag is off, behavior is unchanged.
`SentrySpanExporter` additionally transfers the messaging attributes
(`system`, `destination.name`, `operation.type`, `message.id`,
`message.body.size`, `message.envelope.size`) onto root transactions.
Root transactions don't bulk-copy OTel attributes the way child spans
do, but the Queues product reads `trace.data.messaging.*`, so consumer
root transactions need them propagated explicitly. These are operational
metadata only (no payload contents), so the transfer is unconditional.
Add `MESSAGING_OPERATION_TYPE` and `MESSAGING_MESSAGE_ENVELOPE_SIZE` to
`SpanDataConvention` for use by the exporter and downstream
integrations. Document the OTel-mode behavior in the two Jakarta OTel
sample `application-kafka.properties` so users know the flag activates
the OTel remapping path here, not the Sentry-native Kafka auto-config
(which stays suppressed by its `@ConditionalOnMissingClass` OTel guard).
* fix(otel): Prefer messaging over http mapping when queue tracing enabled
Some OTel instrumentations (notably aws-sdk-2.2 SQS) attach both
`http.request.method` and `messaging.system` to the same span. With the
previous gate order, those spans resolved to http.client and the Sentry
Queues product never lit up for one of the most common OTel-coexistence
targets.
When `enableQueueTracing` is true and `messaging.system` is present, map
to a queue.* op before the http and db checks. When the flag is off, the
existing http-first ordering is preserved.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(otel): Map messaging "create" to queue.create instead of queue.publish
The OTel messaging semconv defines "create" and "publish" as distinct
operations: "create" represents message construction, "publish" the
network send. Folding both into queue.publish risks double-counting
producer transactions on instrumentations that emit a separate create
span (per OTel semconv guidance).
Per the Sentry Queues telemetry spec
(https://develop.sentry.dev/sdk/telemetry/traces/modules/queues/),
queue.create is a canonical op distinct from queue.publish, so map
"create" to its spec-correct destination rather than dropping it.
Empirically, current Kafka OTel instrumentation does not emit a
separate create span, so this is a no-op for Kafka users today; the
change future-proofs other systems and any future Kafka OTel version.
Co-Authored-By: Claude <noreply@anthropic.com>
* docs(options): Clarify enableQueueTracing covers native + OTel paths
The setEnableQueueTracing Javadoc said only "Whether queue operations
(publish, process) should be traced." — silent on the fact that the
flag also drives OTel messaging-span transformation when
sentry-opentelemetry is on the classpath.
Reword on both the getter and setter to make explicit that the flag
both emits Sentry-native queue spans and transforms OTel messaging
spans to match Sentry's queue conventions, so customers grepping
their IDE see what the flag does in either integration mode.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(otel): Map messaging "settle" to queue.settle
OTel messaging semconv defines messaging.operation.type=settle for
consumer ack/nack/reject spans (JMS, RabbitMQ, Pulsar acknowledge).
The switch had no case for "settle", so settle spans on
SpanKind.CONSUMER were falling through to the SpanKind fallback and
becoming queue.process — duplicating the real process span — while
on SpanKind.CLIENT they became the generic "queue" default.
queue.settle is one of the canonical Queues telemetry ops per
https://develop.sentry.dev/sdk/telemetry/traces/modules/queues/, so
add the explicit mapping.
Co-Authored-By: Claude <noreply@anthropic.com>
* chore(samples): Drop verbose comment above sentry.enable-queue-tracing
The OTel Kafka sample properties carried a 10-line comment explaining
the OTel->Sentry remapping mechanism and SentryKafkaQueueConfiguration
suppression behavior. That belongs in the SDK docs, not in a sample
config — drop it so the property line speaks for itself.
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(kafka): [Queue Instrumentation 34] Wrap Producer for send spans
Replace SentryKafkaProducerInterceptor with SentryKafkaProducer, a
Producer<K,V> wrapper that records a queue.publish span around each
send and finishes it when the broker ack callback fires. The span
now reflects the full async send lifecycle, not just the synchronous
onSend window.
For Spring Boot, the SentryKafkaProducerBeanPostProcessor switches
from patching KafkaTemplate.setProducerInterceptor(...) to installing
a ProducerPostProcessor on every ProducerFactory bean via
ProducerFactory.addPostProcessor(...). KafkaTemplate beans are no
longer touched, so all customer-configured listeners, interceptors
and observation settings are preserved.
The console sample now wraps the raw KafkaProducer instead of setting
INTERCEPTOR_CLASSES_CONFIG. Spring Boot samples need no change — the
auto-configured ProducerPostProcessor is transparent.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(kafka): Inject trace headers even without active span
Decouple header injection from span creation in SentryKafkaProducer
so that distributed tracing works for background workers, @scheduled
jobs, and startup publishers that have no active span.
Restructure send() to match the SentryFeignClient/OkHttp pattern:
- isIgnored: pure delegate, no headers, no span
- No active span: inject headers from PropagationContext, no span
- Active span: start child span, inject headers, wrap callback
Also simplify the implementation:
- Rename injectHeaders to maybeInjectHeaders with encapsulated
try/catch (matches Feign's maybeAddTracingHeaders pattern)
- Remove outer try/catch around span setup
- Remove redundant span.isNoOp() early-return branch
- Remove redundant isFinished() guards before finish() calls
Co-Authored-By: Claude <noreply@anthropic.com>
* changelog
* ref(kafka): Reimplement SentryKafkaProducer as a dynamic Proxy
Replace the concrete `implements Producer<K,V>` class with a
`Proxy.newProxyInstance`-based wrapper that intercepts only the two
`send()` overloads and forwards every other method reflectively to
the delegate.
The concrete class required explicitly delegating every method on the
`Producer` interface, coupling the wrapper to a specific Kafka version:
`clientInstanceId(Duration)` was added in Kafka 3.7, and the deprecated
`sendOffsetsToTransaction(Map, String)` was removed in Kafka 4.0. The
dynamic proxy has no such coupling — new or removed interface methods are
handled automatically, giving full compatibility across all Kafka
client versions.
Public API change: `SentryKafkaProducer` is now a utility class with
static `wrap()` overloads instead of constructors. Callers wrap a
producer with `SentryKafkaProducer.wrap(producer)`. The Spring BPP and
console sample are updated accordingly.
Co-Authored-By: Claude <noreply@anthropic.com>
* 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>
* fix(kafka): Preserve existing consumer interceptor on reflection failure
If reading recordInterceptor via reflection fails, leave the container\nfactory untouched instead of installing Sentry's interceptor with a\nnull delegate. This avoids silently dropping customer-configured\ninterceptors for DLQ routing, auditing, or other message handling\nconcerns.\n\nAdd tests that preserve customer interceptors both when chaining\nsucceeds and when reflection cannot safely determine the existing\ninterceptor.\n\nCo-Authored-By: Claude <noreply@anthropic.com>
* fix(spring-boot-jakarta): Skip Kafka autoconfig for OTel agent
* fix(spring-jakarta): Close leaked Kafka interceptor scope
Store the lifecycle token in the thread-local before trace continuation or transaction startup can throw. This keeps the cleanup path reachable and closes the forked scopes even when interceptor preparation fails.
Also log the preparation failure instead of letting the interceptor break customer processing.
* fix(test): Remove stale Kafka container before startup
Always remove the named Kafka system-test container before starting a new broker. This avoids docker name conflicts after crashed or interrupted runs while still keeping stop_kafka_broker ownership-aware for reused brokers.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(otel): Add send and deliver mapping coverage
* test(kafka): Add no-op producer span coverage
* fix(kafka): Pass consumer interceptor log throwable correctly
* test(kafka): Exercise consumer interceptor reflection failure
Force the reflection-failure path in the consumer bean post processor test so it proves customer interceptors remain untouched when Sentry skips installation.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(test): Set SENTRY_ENABLE_QUEUE_TRACING for Kafka system tests
When SENTRY_AUTO_INIT=true with the OTel agent, Sentry is initialized
early by SentryAutoConfigurationCustomizerProvider before Spring Boot
loads application-kafka.properties. Without the env var, queue tracing
stays disabled and OTel messaging spans are not mapped to
queue.publish/queue.process ops, causing
KafkaOtelCoexistenceSystemTest to fail.
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(spring): Add Kafka queue tracing for Spring Boot 4
Port the Spring Boot 3 Kafka queue tracing support to the Spring 7 and Spring Boot 4 modules.
Add Spring Kafka bean post-processors, Boot 4 auto-configuration, and matching sample system-test coverage.
Co-Authored-By: Claude <noreply@anthropic.com>
* changelog
* feat(spring): Add Kafka queue tracing for Spring Boot 2
Port Kafka queue tracing to the Spring and Spring Boot 2 modules.
Add Spring Kafka bean post-processors, Boot 2 auto-configuration, and matching sample system-test coverage.
Co-Authored-By: Claude <noreply@anthropic.com>
* docs(rules): Add queue tracing cursor rules
Document when to load queue-specific Cursor rules and summarize how Sentry Queues data is produced by the Java SDK Kafka instrumentation.
Co-Authored-By: Claude <noreply@anthropic.com>
* changelog
* build(samples): Use Spring Boot Kafka starter in Boot 4 samples
* fix(queue): Apply queue instrumentation review changes
* test(spring): Address Kafka tracing review comments
Simplify Kafka interceptor test delegates and rely on Kotlin type inference in Spring Kafka tests.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(spring): Initialize Sentry in Kafka BPP tests
Initialize Sentry before each Kafka bean post-processor test and close it afterwards so logging paths do not depend on test execution order. This prevents failures when earlier tests close the SDK before these tests run.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(spring): Address Kafka review comments
Simplify Spring Kafka test interceptors and cover intercepting records without a consumer.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(spring): Isolate capture exception advice scopes
Initialize Sentry before installing the mocked scopes used by the capture exception parameter advice test. Close Sentry after the test so the mocked scopes do not leak into later tests.
Co-Authored-By: Claude <noreply@anthropic.com>
* changelog entry
* fix README changes
* test(otel): Relax Kafka coexistence span assertion
Avoid requiring the async Kafka producer span to be embedded in the HTTP transaction. OTel can finish and export the producer span after the request transaction, so this assertion flakes while the test still verifies OTel instrumentation suppresses Spring Kafka integration.
Refs #5373
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(kafka): Make producer proxy equality reflexive
Return true when the Kafka producer proxy is compared with itself. This preserves existing delegate equality behavior for other comparisons while satisfying the equals contract.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Sentry Github Bot <bot+github-bot@sentry.io>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

PR Stack (Queue Instrumentation)
📜 Description
Align
sentry-task-enqueued-timeformat with sentry-python by storing epoch seconds in the producer interceptor and computingmessaging.message.receive.latencyfrom seconds in the consumer interceptor.This keeps the header semantics consistent across SDKs and avoids cross-SDK parsing mismatches.
💡 Motivation and Context
F-009 identified a cross-SDK format mismatch: Java wrote epoch millis while sentry-python writes epoch seconds. Mixed-language queue pipelines could produce wrong or missing receive latency.
💚 How did you test it?
./gradlew spotlessApply apiDump./gradlew :sentry-spring-jakarta:test --tests='*SentryProducerInterceptorTest*' --tests='*SentryKafkaRecordInterceptorTest*'📝 Checklist
sendDefaultPIIis enabled.🔮 Next steps
None.
#skip-changelog