You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为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,消费者解析并设置

这是完全可行的方案,能同时满足基础需求和最优目标,步骤如下:

  1. 生产者端:将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);
        }
    }
    
  2. 消费者端:解析消息头并设置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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 23:40:36