I've recently migrated from Spring Cloud Greenwich.SR2 to Hoxton.SR9, and am using Spring Sleuth to trace incoming REST calls to my Spring Boot app that trigger messages that ultimately arrive in RabbitMQ.
Obviously one of the key differences in Spring Sleuth between those two versions is that, instead of sending 3 separate message headers for trace, span and parentSpan, now only one b3
header is sent of the form {trace}-{span}-{sample}-{parentSpan}
with the last two components optional.
The thing that is puzzling me is that the b3 header on the message arriving in RabbitMQ never seems to have a parentSpan component. e.g. the sequence goes something like this:
- Within app REST controller: traceId=T, spanId=S1, parentSpanId=S0
- Prior to sending event from within app: traceId=T, spanId=S2, parentSpanId=S1
- Message header on RabbitMQ queue: b3=T-S2-1
In other words the message header has the right trace, but shares the span of the sending event context in the app and has no parentSpan.
Is this expected? I feel sure that with previous versions of Sleuth I would have seen a new spanId in the message header and a parentSpan that was equal to the span in the sending event context.
Have I misunderstood what is going on, or (if what I expect is sensible) is there a way to change this default behaviour.
I have the following explicit sleuth property settings:
spring.sleuth.enabled=true
spring.messaging.enabled=true
spring.supportsJoin=false
(NB: I tried the same with supportsJoin defaulted, but it didn't seem to make any difference to the lack of parentSpanId).
Debugging Information
This is what I have been able to determine from stepping through the debugger:
- We pass a
GenericMessage
intoAbstractMessageChannel.send()
. - Eventually the message gets passed into
TracingChannelInterceptor.preSend()
- The
TracingChannelInterceptor
is configured with aDeferredInjector
for whichsetter
is an instance ofMessageHeaderPropagation
andinjectorFactory
has aproducerInjectorFunction=SINGLE_NO_PARENT
. - The
TracingChannelInterceptor.preSend()
method callsDeferredInjector.inject(span.context(), headers)
, which results in the SINGLE_NO_PARENT injector adding ab3
header of the form{traceId}-{spanId}-1
to the outgoing message.
So the absence of a parentSpanId seems to originate in the fact that the TracingChannelInterceptor.injector is a DeferredInjector whose producer injector is SINGLE_NO_PARENT.
Custom Configuration
Registering the following bean to override the default propagation settings changes the behaviour to:
- Within app REST controller: traceId=T, spanId=S1, parentSpanId=S0
- Prior to sending event from within app: traceId=T, spanId=S2, parentSpanId=S1
- Message header on RabbitMQ queue: b3=T-S2-1-S1
But I wonder why it's necessary to do this?
@Configuration
public class B3Configuration {
@Bean
Propagation.Factory customPropagationFactory() {
return B3Propagation.newFactoryBuilder()
.injectFormat(B3Propagation.Format.SINGLE)
.injectFormat(Span.Kind.CLIENT, B3Propagation.Format.SINGLE)
.injectFormat(Span.Kind.CONSUMER, B3Propagation.Format.SINGLE)
.injectFormat(Span.Kind.PRODUCER, B3Propagation.Format.SINGLE)
.build();
}
}