Message Queue
The trace analysis draws a message as two calls that meet at a broker node of the
OTLP_VIRTUAL_MQ layer:
producer ──▶ broker ──▶ consumer
- A PRODUCER or CLIENT span that carries
messaging.systemdrawsproducer → brokerwith theotlp_service_relation_client_*metrics and measures the broker withotlp_mq_access_*. The topic or queue it names inmessaging.destination.name, or the oldermessaging.destination, is an endpoint of the broker, withotlp_mq_destination_*. - A CONSUMER span that carries
messaging.systemdrawsbroker → consumerwithotlp_service_relation_server_cpm, and counts as a request of the consumer in its service and endpoint metrics, and in its instance metrics when the span reports an instance.
Both applications need service.layer=OTLP_GENERAL on their resource for their spans to be analyzed. Nothing else is
built for a message: no edge from the producer straight to the consumer, no instance under the broker, and no broker
metric from the consumer’s side.
How the two sides meet
The producer and the consumer are both clients of the broker, so their spans are not paired as a caller’s CLIENT span
and a callee’s SERVER span are, and no network alias is learned for a broker, see Address-named
peers. The two edges meet only where both sides name the
broker with the same string. W3C trace context carries no address, unlike the SkyWalking agents’ sw8 header, so each
span names the broker from its own attributes, by the first one present:
| Span | Broker named by |
|---|---|
| producer | service.peer.name or peer.service; server.address and server.port (net.peer.name and net.peer.port); peer.address; the host and port of url.full or http.url; messaging.system |
| consumer | server.address and server.port (net.peer.name and net.peer.port); messaging.system |
A span that names the broker by none of the others lands on the node named after its messaging system, kafka or
rabbitmq, so every cluster of one system reached that way is one node.
What the agents report
As read from the source of the OpenTelemetry Java agent 2.32.0 with its default settings, and of the main branches, on 2026-10-30, of the OpenTelemetry .NET automatic instrumentation, the RabbitMQ .NET client, the OpenTelemetry Python and JavaScript contrib instrumentations and the OpenTelemetry Go automatic instrumentation.
Kafka
The Java agent, the .NET automatic instrumentation, kafkajs, the Go automatic instrumentation for segmentio/kafka-go
and kafka-python put no broker address on their Kafka spans, so both sides land on kafka. kafkajs puts the broker’s
address on its duration metric only, and kafka-python reports the bootstrap servers as messaging.url only.
confluent-kafka (Python) names the broker by the first bootstrap.servers entry on both sides, so its edges meet
when every client lists the same entry first. The topology therefore shows one kafka node shared by all the Kafka
clusters these agents reach, or, with confluent-kafka, one node per first bootstrap entry.
RabbitMQ
| Agent | Producer’s broker | Consumer’s broker |
|---|---|---|
| Java agent | rabbitmq; the connection’s address is reported as network.peer.address only, which the rule does not read |
rabbitmq |
| pika, aio-pika | the connection’s host:port |
the same; a pika consumer reading channel.consume() reports no address and lands on rabbitmq |
| amqplib | the connection’s host:port |
the same; messaging.system is the broker’s product name, and without one the spans draw no MQ edge |
| RabbitMQ .NET client | its endpoint’s host:port |
rabbitmq, since its deliver span carries no address, so the two edges do not meet |
Where both sides report an address, connect producers and consumers through the same address of the broker, such as one Kubernetes Service, so that they name the same node.
The Java agent also reports the channel calls other than a publish, queue.declare and basic.ack among them, as
CLIENT spans that carry messaging.system, and the RabbitMQ .NET client reports its connection attempts the same way.
The rule draws them as calls to the broker and counts them in its otlp_mq_access_*.
Which consumer span is counted
The rule counts a CONSUMER span as the consumer’s request, except one whose operation (messaging.operation.type, or
the older messaging.operation) is receive and that is the root of its trace, and never counts a CLIENT span whose
operation is receive, see Metrics. What the consumer’s response time shows depends
on the span its agent reports:
| Consumer span | Counted | Response time | Reported by |
|---|---|---|---|
process, its parent the producer’s span |
yes | the handling | Java agent; kafkajs eachMessage; RabbitMQ .NET client deliver |
process, its parent the producer’s span, ended by the acknowledgement |
yes | until the message is acknowledged or rejected, the channel closes or errors, or the instrumentation times out (consumeTimeoutMs, 1 minute by default); with noAck, until the callback returns, without what it left running asynchronously |
amqplib |
receive around the callback, its parent the producer’s span |
yes | the handling | pika and aio-pika callbacks |
receive from one fetch’s return to the next fetch, its parent the producer’s span |
yes | the handling between the two fetches | Go automatic instrumentation, segmentio/kafka-go |
receive around a blocking receive(), its parent the producer’s span |
yes | the wait for the message, included | Java agent, JMS MessageConsumer.receive() |
receive under a span the application opened |
yes | the Consume call that returned the message, its wait included |
.NET automatic instrumentation |
receive that ends before the message is handled, its parent the producer’s span |
yes | close to 0 | pika channel.consume() |
named receive but with no operation attribute, ended before the handling |
yes, even as a root | close to 0 | kafka-python |
receive that is the root of its trace |
no | .NET automatic instrumentation in a consume loop with no span around it; the Go automatic instrumentation for a message that carries no trace context; pika and aio-pika when neither the message nor the running code carries one | |
CLIENT poll |
no | kafkajs eachBatch, whose per-message process spans are counted |
confluent-kafka (Python) reports what one poll() or consume() call returned as a recv span with no attributes
and a process span under it, which stays open until the next call. Both are counted as requests, and only the
process span draws the edge from the broker.