Search code examples
javaspringspring-bootspring-cloud-gatewayspring-cloud-sleuth

Spring Cloud Sleuth: tracer.getCurrentSpan() is NULL


Using Spring Cloud Sleuth version 3.1.4 and I want to insert the trace ID in the HTTP response headers. I created a bean of type GlobalFilter and trying to retrieve the trace ID string as follows

@Configuration
public class ResponseFilter {

    @Autowired
    Tracer tracer;
    
    @Autowired
    FilterUtils filterUtils;
    
    final Logger logger =LoggerFactory.getLogger(ResponseFilter.class);
    
    @Bean
    public GlobalFilter postGlobalFilter() {
        return (exchange, chain) -> {
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                  
                  String traceId = tracer.currentSpan().context().traceIdString();
                  logger.debug("Adding the correlation id to the outbound headers. {}", traceId);
                  exchange.getResponse().getHeaders().add(FilterUtils.CORRELATION_ID, traceId);
                  logger.debug("Completing outgoing request for {}.", exchange.getRequest().getURI());
              }));
        };
    }
}

I am getting tracer.currentSpan() as NULL which is why it gives NPE for the above code.


Solution

  • The problem is that the currentSpan will expire in the then() method and when you want to get it, it simply doesn't exist. Hence, you should take it sooner.

    Here's a solution where you can get the current span and retrieve the traceId:

        @Bean
        public GlobalFilter postGlobalFilter() {
            return (exchange, chain) -> {
                final String traceId = Optional.ofNullable(tracer.currentSpan())
                        .map(Span::context)
                        .map(TraceContext::traceIdString)
                        .orElse("null");
    
                return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                    logger.debug("Adding the correlation id to the outbound headers. {}", traceId);
                    exchange.getResponse().getHeaders().add(FilterUtils.CORRELATION_ID, traceId);
                    logger.debug("Completing outgoing request for {}.",
                            exchange.getRequest().getURI());
                }));
            };
        }
    

    Note that I have used Java's Optional to deal with null values. You can use Mono.just(...).map(...).switchIfEmpty(...) as well.

    Here's a cleaner way you could configure Spring Cloud Gateway's filter:

    @Slf4j
    @Configuration
    @Profile({"dev","stage"})
    @RequiredArgsConstructor
    public class ResponseFilter implements GlobalFilter {
        private final Tracer tracer;
    
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            final String traceId = Optional.ofNullable(tracer.currentSpan())
                    .map(Span::context)
                    .map(TraceContext::traceIdString)
                    .orElse("null");
    
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                log.debug("Adding the correlation id to the outbound headers. {}", traceId);
                exchange.getResponse().getHeaders().add(FilterUtils.CORRELATION_ID, traceId);
                log.debug("Completing outgoing request for {}.",
                        exchange.getRequest().getURI());
            }));
        }
    
    }
    

    Keep in mind that exposing traceId in the http response of the gateway is usually not a good idea (according to Spring Cloud Sleuth Documentation), unless you are in dev or stage and you want to use it for debugging purposes.