spring-bootwebclientspring-cloud-sleuthdistributed-tracing

sleuth does not show Trace Id / Span Id in logs while WebClient Rest call


On rest api call with Webclient, few default logs are printed like below but sleuth doesn't add tracid with it. see below:

2022-08-10 10:18:26.123 DEBUG [cib_bulk,,] 1 --- [or-http-epoll-1] r.netty.http.client.HttpClientConnect    : [7c54bef8-1, L:/1.1.1.:60568 - R:xyz.c11.1.1.:443] Handler is being applied: {uri=xyz.c/services/productInventory/v2/product/search/count?abc=2346&status=ACTIVE, method=GET}

only application name is attached here [cib_bulk,,]. But in entire application, when I log manually through logger, then sleuth attach traceid and span id.

@Bean
public WebClient webClientWithTimeout() {
    String baseUrl = environment.getProperty("cibase.productapi.service.url");  
    LOG.info("Base Url of Product Inventory Service: {}",baseUrl);
    
    String username = environment.getProperty("cibase.productapi.basicauth.username");
    String password = environment.getProperty("cibase.productapi.basicauth.password");
    String trackingid = environment.getProperty("cibase.productapi.basicauth.trackingid");
    String trackingIdValue = environment.getProperty("cibase.productapi.basicauth.trackingid.value");
    
    HttpClient httpClient = HttpClient.create();

    Builder builder =  
             WebClient.builder()
            .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(IN_MEMORY_SIZE))
            .filter(basicAuthentication(username, password));
    
    if(trackingid != null){
        builder.defaultHeader(trackingid, trackingIdValue);
    }
    return builder.baseUrl(baseUrl).clientConnector(new ReactorClientHttpConnector(httpClient)).build();
}

=============

 List<Product> productList = webClient
                  .get()
                  .uri(uriBuilder -> uriBuilder.path(MessageConstants.PRODUCT_INVENTORY_API_URL).replaceQuery(queryString).build())
                  .retrieve()
                  .bodyToFlux(Product.class)
                  .collectList()
                  .retryWhen(retryConfiguration())
                  .block();

=====

<dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-sleuth</artifactId>
          <version>3.1.1</version>
        </dependency>

Solution

  • I found the solution. Just use below code to print Trace id and spanId in logging. This code is also useful to print REST call's request and response body in pretty format.

         import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication;
            import org.slf4j.Logger;
            import org.slf4j.LoggerFactory;
            import org.springframework.beans.factory.annotation.Autowired;
            import org.springframework.context.annotation.Bean;
            import org.springframework.context.annotation.Configuration;
            import org.springframework.core.env.Environment;
            import org.springframework.http.client.reactive.ReactorClientHttpConnector;
            import org.springframework.web.reactive.function.client.WebClient;
            import org.springframework.web.reactive.function.client.WebClient.Builder;
            
            import brave.http.HttpTracing;
            import io.netty.handler.logging.LogLevel;
            import reactor.netty.http.brave.ReactorNettyHttpTracing;
            import reactor.netty.http.client.HttpClient;
            import reactor.netty.transport.logging.AdvancedByteBufFormat;
        
        
         @Configuration
         public class WebClientConfiguration {
            public static final Logger LOG = LoggerFactory.getLogger(WebClientConfiguration.class);
        
        @Autowired
        private Environment environment;
    
        private static final int IN_MEMORY_SIZE = -1; // unlimited in-memory 
    
    /* Step 1: This bean is responsible to add Sleuth generated TraceId and SpanId in logs*/
        @Bean
        ReactorNettyHttpTracing reactorNettyHttpTracing(final HttpTracing httpTracing) {
            return ReactorNettyHttpTracing.create(httpTracing);
        }
         
        @Bean
        public WebClient webClientWithTimeout(final ReactorNettyHttpTracing reactorNettyHttpTracing) {
            String baseUrl = environment.getProperty("cibase.productapi.service.url");  
            LOG.info("Base Url of Product Inventory Service: {}",baseUrl);
            
            String username = environment.getProperty("cibase.productapi.basicauth.username");
            String password = environment.getProperty("cibase.productapi.basicauth.password");
            String trackingid = environment.getProperty("cibase.productapi.basicauth.trackingid");
            String trackingIdValue = environment.getProperty("cibase.productapi.basicauth.trackingid.value");
            
    // wiretap used to log request and response body
            HttpClient httpClient = HttpClient.create().wiretap(this.getClass().getCanonicalName(), LogLevel.DEBUG, AdvancedByteBufFormat.TEXTUAL);
        
            Builder builder =  
                     WebClient.builder()
                    .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(IN_MEMORY_SIZE))
                    .filter(basicAuthentication(username, password));
                    
            if(trackingid != null){
                builder.defaultHeader(trackingid, trackingIdValue);
            }
       /* step 2. reactorNettyHttpTracing object used here */
            return builder
                    .baseUrl(baseUrl)
                    .clientConnector(new ReactorClientHttpConnector(reactorNettyHttpTracing.decorateHttpClient(httpClient)))
                    .build();   // here we have used the above bean
        }
        
    }