Skip to content

Commit f2461a0

Browse files
authored
Use routing key as DSM topic for RabbitMQ default-exchange publishes (#11805)
Use routing key as DSM topic for RabbitMQ default-exchange publishes When a producer publishes to the default exchange (exchange == ""), the routing key is the destination queue name. The DSM checkpoint previously recorded an empty exchange and no topic, so the producer had no destination and showed up disconnected in the Data Streams Monitoring map. Record the routing key as the topic in that case (matching the consumer checkpoint and the JS/.NET tracers). Named-exchange publishes are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Fix propagation-disabled check for default-exchange RabbitMQ publishes The disabled-destination check still looked up propagation state by exchange name, which is always empty for default-exchange publishes. Use the routing key (queue name) instead, matching the DSM topic tag and the consumer-side check, so disabling a queue by name also suppresses producer-side propagation for default-exchange publishes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Merge branch 'master' into eric.firth/dsm-rabbitmq-default-exchange-topic Merge branch 'master' into eric.firth/dsm-rabbitmq-default-exchange-topic Merge branch 'master' into eric.firth/dsm-rabbitmq-default-exchange-topic Merge branch 'master' into eric.firth/dsm-rabbitmq-default-exchange-topic Merge branch 'master' into eric.firth/dsm-rabbitmq-default-exchange-topic Co-authored-by: eric.firth <eric.firth@datadoghq.com>
1 parent 9a4067f commit f2461a0

2 files changed

Lines changed: 57 additions & 6 deletions

File tree

dd-java-agent/instrumentation/rabbitmq-amqp-2.7/src/main/java/datadog/trace/instrumentation/rabbitmq/amqp/RabbitChannelInstrumentation.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
99
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.namedOneOf;
1010
import static datadog.trace.api.datastreams.DataStreamsTags.Direction.OUTBOUND;
11+
import static datadog.trace.api.datastreams.DataStreamsTags.create;
1112
import static datadog.trace.api.datastreams.DataStreamsTags.createWithExchange;
1213
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan;
1314
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activeSpan;
@@ -207,8 +208,10 @@ public static void onEnter(
207208
AgentSpan span = activeSpan();
208209
if (span == null) return;
209210
Config config = Config.get();
211+
final boolean isDefaultExchange = exchange == null || exchange.isEmpty();
212+
final String destination = isDefaultExchange ? routingKey : exchange;
210213
if (!config.isRabbitPropagationEnabled()
211-
|| config.isRabbitPropagationDisabledForDestination(exchange)) return;
214+
|| config.isRabbitPropagationDisabledForDestination(destination)) return;
212215
// This is the internal behavior when props are null. We're just doing it earlier now.
213216
if (props == null) {
214217
props = MessageProperties.MINIMAL_BASIC;
@@ -219,9 +222,13 @@ public static void onEnter(
219222
if (TIME_IN_QUEUE_ENABLED) {
220223
RabbitDecorator.injectTimeInQueueStart(headers);
221224
}
222-
DataStreamsTags tags =
223-
createWithExchange(
224-
"rabbitmq", OUTBOUND, exchange, routingKey != null && !routingKey.isEmpty());
225+
final boolean hasRoutingKey = routingKey != null && !routingKey.isEmpty();
226+
DataStreamsTags tags;
227+
if (isDefaultExchange && hasRoutingKey) {
228+
tags = create("rabbitmq", OUTBOUND, routingKey);
229+
} else {
230+
tags = createWithExchange("rabbitmq", OUTBOUND, exchange, hasRoutingKey);
231+
}
225232
DataStreamsContext dsmContext = DataStreamsContext.fromTags(tags);
226233
defaultPropagator().inject(span.with(dsmContext), headers, SETTER);
227234
props =

dd-java-agent/instrumentation/rabbitmq-amqp-2.7/src/test/groovy/RabbitMQTest.groovy

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import datadog.trace.agent.test.utils.PortUtils
1414
import datadog.trace.api.Config
1515
import datadog.trace.api.DDSpanTypes
1616
import datadog.trace.api.DDTags
17+
import datadog.trace.api.datastreams.PathwayContext
1718
import datadog.trace.bootstrap.instrumentation.api.InstrumentationTags
1819
import datadog.trace.bootstrap.instrumentation.api.Tags
1920
import datadog.trace.core.DDSpan
@@ -225,7 +226,7 @@ abstract class RabbitMQTestBase extends VersionedNamingTestBase {
225226
if (isDataStreamsEnabled()) {
226227
StatsGroup first = TEST_DATA_STREAMS_WRITER.groups.find { it.parentHash == 0 }
227228
verifyAll(first) {
228-
tags.hasAllTags("direction:out", "exchange:", "has_routing_key:true", "type:rabbitmq")
229+
tags.hasAllTags("direction:out", "topic:" + queueName, "type:rabbitmq")
229230
}
230231

231232
StatsGroup second = TEST_DATA_STREAMS_WRITER.groups.find { it.parentHash == first.hash }
@@ -493,7 +494,7 @@ abstract class RabbitMQTestBase extends VersionedNamingTestBase {
493494
if (isDataStreamsEnabled()) {
494495
StatsGroup first = TEST_DATA_STREAMS_WRITER.groups.find { it.parentHash == 0 }
495496
verifyAll(first) {
496-
tags.hasAllTags("direction:out", "exchange:", "has_routing_key:true", "type:rabbitmq")
497+
tags.hasAllTags("direction:out", "topic:some-routing-queue", "type:rabbitmq")
497498
}
498499
499500
StatsGroup second = TEST_DATA_STREAMS_WRITER.groups.find { it.parentHash == first.hash }
@@ -689,6 +690,49 @@ abstract class RabbitMQTestBase extends VersionedNamingTestBase {
689690
"deliver" | "some-exchange" | "some-routing-key" | "queueNameTest" | "" | false
690691
}
691692
693+
def "test rabbit publish to default exchange with queue name in disabled queues (producer side)"() {
694+
setup:
695+
removeSysConfig(RABBIT_PROPAGATION_DISABLED_QUEUES)
696+
def queueName = channel.queueDeclare().getQueue()
697+
injectSysConfig(RABBIT_PROPAGATION_DISABLED_QUEUES, queueName)
698+
699+
when:
700+
runUnderTrace("parent") {
701+
channel.basicPublish("", queueName, null, "Hello, world!".bytes)
702+
}
703+
GetResponse response = channel.basicGet(queueName, true)
704+
String body = new String(response.body)
705+
706+
then:
707+
body == "Hello, world!"
708+
709+
and:
710+
// Publishing to the default exchange uses the routing key (i.e. the queue name) as the
711+
// DSM destination, so a queue name in RABBIT_PROPAGATION_DISABLED_QUEUES must suppress
712+
// the pathway header, the same way it does for named-exchange publishes.
713+
if (isDataStreamsEnabled()) {
714+
def headers = response.getProps().getHeaders()
715+
assert headers == null || !headers.containsKey(PathwayContext.PROPAGATION_KEY_BASE64)
716+
}
717+
718+
and:
719+
assertTraces(3, SORT_TRACES_BY_ID) {
720+
trace(1) {
721+
rabbitSpan(it, "queue.declare")
722+
}
723+
trace(2) {
724+
basicSpan(it, "parent")
725+
rabbitSpan(it, "basic.publish <default> -> <generated>", false, span(0), operationForProducer())
726+
}
727+
trace(1) {
728+
rabbitSpan(it, "basic.get <generated>", false, null, operationForConsumer())
729+
}
730+
}
731+
732+
cleanup:
733+
removeSysConfig(RABBIT_PROPAGATION_DISABLED_QUEUES)
734+
}
735+
692736
def rabbitSpan(
693737
TraceAssert trace,
694738
String resource,

0 commit comments

Comments
 (0)