如何为ActiveMQ消费者应用配置Sleuth生成TraceId/SpanId日志
我有两个Java Spring Boot应用:消息生产者(Producer.app)和消息消费者(Consumer.app),需求是让两个应用的日志都包含TraceId/SpanId,最优目标是消费者线程的TraceId和对应消息的生产者线程TraceId一致(当前非必需),至少要实现消费者应用日志输出TraceId/SpanId。
生产者应用运行正常:通过Postman触发REST接口发送消息到ActiveMQ时,该线程所有日志都包含TraceId/SpanId。但消费者应用消费ActiveMQ消息时,日志里没有TraceId/SpanId,且tracer.currentSpan()返回null。
两者的application配置文件内容一致:
logging: level: root: info server: port: 7071 spring: application: name: consumer-app sleuth: sampler: probability: 1.0 messaging: jms: enabled: false activemq: broker-url: tcp://127.0.0.1:61616 user: **** password: ******* packages: trust-all: true pool: enabled: true max-connections: 1000
已为两个应用配置Sleuth,预期两者日志都会包含TraceId/SpanId,但只有生产者正常。
配置类如下:
@Configuration public class JmsConfig { @Value("${spring.activemq.broker-url}") private String brokerUrl; @Value("${spring.activemq.user}") private String brokerUser; @Value("${spring.activemq.password}") private String brokerPassword; @Bean public PooledConnectionFactory pooledConnectionFactory(ActiveMQConnectionFactory activeMQConnectionFactory) { PooledConnectionFactory factory = new PooledConnectionFactory(); factory.setConnectionFactory(activeMQConnectionFactory); factory.setMaxConnections(60); factory.setReconnectOnException(true); factory.setExpiryTimeout(0); factory.setCreateConnectionOnStartup(true); return factory; } @Bean @Primary public ActiveMQConnectionFactory activeMQConnectionFactory() { ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(); factory.setBrokerURL(brokerUrl); factory.setPassword(brokerPassword); factory.setUserName(brokerUser); return factory; } @Bean public JmsTemplate myJmsTemplate(PooledConnectionFactory pooledConnectionFactory) { JmsTemplate jmsTemplate = new JmsTemplate(); jmsTemplate.setConnectionFactory(pooledConnectionFactory); return jmsTemplate; } }
由于配置中使用ActiveMQConnectionFactory,无法转换为ConnectionFactory,因此必须保持sleuth.messaging.jms.enabled=false。请问是否可以通过生产者应用将TraceId/SpanId随消息传递解决该问题?
方案1:手动在生产者传递TraceId/SpanId,消费者解析并设置
这是完全可行的方案,能同时满足基础需求和最优目标,步骤如下:
生产者端:将TraceId/SpanId存入消息头
在发送消息时,从Sleuth的Tracer中获取当前的TraceId和SpanId,将其作为消息属性添加到JMS消息中:@Autowired private Tracer tracer; @Autowired private JmsTemplate jmsTemplate; public void sendMessage(String destination, String message) { Span currentSpan = tracer.currentSpan(); if (currentSpan != null) { String traceId = currentSpan.context().traceId(); String spanId = currentSpan.context().spanId(); jmsTemplate.send(destination, session -> { TextMessage textMessage = session.createTextMessage(message); textMessage.setStringProperty("X-B3-TraceId", traceId); textMessage.setStringProperty("X-B3-SpanId", spanId); return textMessage; }); } else { jmsTemplate.convertAndSend(destination, message); } }消费者端:解析消息头并设置TraceId/SpanId到当前线程
在消费消息时,从消息属性中取出TraceId和SpanId,通过Sleuth的Tracer手动创建并激活Span:@Autowired private Tracer tracer; @JmsListener(destination = "your-queue-name") public void consumeMessage(Message message) throws JMSException { String traceId = message.getStringProperty("X-B3-TraceId"); String spanId = message.getStringProperty("X-B3-SpanId"); if (traceId != null && spanId != null) { // 创建Span上下文,关联生产者的TraceId/SpanId SpanContext spanContext = SpanContext.create(traceId, spanId, null, null); // 创建子Span并激活 Span span = tracer.nextSpan(spanContext).name("jms-consumer").start(); try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) { // 处理消息的业务逻辑,此时日志会包含生产者传递的TraceId if (message instanceof TextMessage) { String content = ((TextMessage) message).getText(); log.info("Consumed message: {}", content); } } finally { span.end(); } } else { // 无传递TraceId时,创建新的Trace链路 Span span = tracer.nextSpan().name("jms-consumer-fallback").start(); try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) { if (message instanceof TextMessage) { String content = ((TextMessage) message).getText(); log.info("Consumed message without trace: {}", content); } } finally { span.end(); } } }
方案2:调整ConnectionFactory配置,兼容Sleuth自动配置
你提到的ActiveMQConnectionFactory无法转换为ConnectionFactory的问题不成立,因为ActiveMQConnectionFactory本身就实现了ConnectionFactory接口。可能是自定义Bean的优先级或配置顺序导致Sleuth无法自动包装连接工厂,可以尝试:
- 移除自定义的
ActiveMQConnectionFactory和PooledConnectionFactoryBean,让Spring Boot自动配置这些组件,通过配置文件调整连接参数。 - 如果必须自定义连接工厂,手动使用
TraceConnectionFactoryWrapper包装你的PooledConnectionFactory,让Sleuth能够自动处理JMS消息的追踪逻辑。
关键说明
- 手动传递TraceId的方案完全可控,不仅能让消费者日志包含TraceId,还能保证和生产者的TraceId一致,达成最优目标。
- 使用
X-B3-TraceId和X-B3-SpanId字段符合OpenTracing/OpenTelemetry规范,兼容性更强。
内容的提问来源于stack exchange,提问作者Oleks Oleks

