Comprehensive Guide to Logging with Spring WebClient: ExchangeFilterFunction and Beyond

Dec 04, 2025 · Programming · 10 views · 7.8

Keywords: Spring WebClient | Logging | ExchangeFilterFunction

Abstract: This technical article provides an in-depth exploration of various approaches to implement request and response logging in Spring 5 WebClient, with a primary focus on the ExchangeFilterFunction mechanism. Through detailed analysis of custom filters, Netty wiretap configuration, and Spring Boot logging settings, it offers complete code examples and best practice guidelines for effective HTTP communication monitoring in reactive programming environments.

Introduction

In modern microservices architecture, HTTP client logging is crucial for debugging, monitoring, and troubleshooting. Spring WebClient, the reactive HTTP client introduced in Spring 5, offers multiple flexible logging mechanisms. This article systematically examines these approaches, with particular emphasis on the ExchangeFilterFunction core component.

ExchangeFilterFunction Fundamentals

ExchangeFilterFunction serves as the central abstraction in WebClient for processing requests and responses, enabling developers to inject custom logic at various stages of HTTP exchange. By implementing request and response processors, communication data can be easily captured and logged.

The following demonstrates a basic request logging filter implementation:

private static ExchangeFilterFunction logRequest() {
    return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
        log.info("Request: {} {}", clientRequest.method(), clientRequest.url());
        clientRequest.headers().forEach((name, values) -> 
            values.forEach(value -> log.info("{}={}", name, value)));
        return Mono.just(clientRequest);
    });
}

This filter executes before request transmission, logging the HTTP method and URL while iterating through all request headers. It is important to note that this approach does not log request bodies, as they are processed as streams in reactive programming.

Response Logging Implementation

Similarly, a response logging filter can be created:

private static ExchangeFilterFunction logResponse() {
    return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
        log.info("Response status: {}", clientResponse.statusCode());
        clientResponse.headers().asHttpHeaders().forEach((name, values) -> 
            values.forEach(value -> log.info("{}={}", name, value)));
        return Mono.just(clientResponse);
    });
}

Critical consideration: Response bodies should not be attempted to be read within response filters, as they can only be consumed once. If response body logging is required within filters, buffering mechanisms or alternative approaches must be employed.

WebClient Configuration

Integrating filters into WebClient is straightforward:

@Component
public class MyClient {
    private final WebClient webClient;
    
    public MyClient(WebClient.Builder webClientBuilder) {
        webClient = webClientBuilder
            .baseUrl("https://api.example.com")
            .filter(logRequest())
            .filter(logResponse())
            .build();
    }
    
    // Business method
    public Mono<String> fetchData(String path) {
        return webClient.get()
            .uri(path)
            .retrieve()
            .bodyToMono(String.class);
    }
}

Netty Wiretap Approach

Beyond ExchangeFilterFunction, lower-level logging can be achieved by configuring the underlying Netty HttpClient. This method captures complete communication data including request and response bodies.

For Spring Boot 2.4.0 and later:

HttpClient httpClient = HttpClient.create()
    .wiretap(this.getClass().getCanonicalName(), 
             LogLevel.DEBUG, 
             AdvancedByteBufFormat.TEXTUAL);

ClientHttpConnector connector = new ReactorClientHttpConnector(httpClient);

WebClient client = WebClient.builder()
    .clientConnector(connector)
    .build();

This approach produces human-readable log output with complete HTTP protocol details. For earlier Spring Boot versions, simpler wiretap(true) configuration can be used:

WebClient webClient = WebClient.builder()
    .clientConnector(new ReactorClientHttpConnector(
        HttpClient.create().wiretap(true)
    ))
    .build();

Then configure the logging level in application.properties:

logging.level.reactor.netty.http.client.HttpClient=DEBUG

Spring Boot Auto-configuration

Spring Boot provides out-of-the-box logging support. For Spring Boot 2.1.0 and above:

# application.properties
logging.level.org.springframework.web.reactive.function.client.ExchangeFunctions=TRACE
spring.http.log-request-details=true

For Spring Boot versions before 2.1.0, an additional configuration class is required:

@Configuration
public class LoggingConfig {
    
    @Bean
    @Order(0)
    public CodecCustomizer loggingCodecCustomizer() {
        return configurer -> configurer.defaultCodecs()
            .enableLoggingRequestDetails(true);
    }
}

Starting from Spring Boot 2.2.4, a more concise configuration approach is available:

WebClient webClient = WebClient.builder()
    .codecs(configurer -> 
        configurer.defaultCodecs().enableLoggingRequestDetails(true))
    .build();

Advanced Customization

For scenarios requiring finer control, Netty's LoggingHandler can be extended to customize log formats:

public class HttpLoggingHandler extends LoggingHandler {
    
    @Override
    protected String format(ChannelHandlerContext ctx, 
                           String event, Object arg) {
        if (arg instanceof ByteBuf) {
            ByteBuf msg = (ByteBuf) arg;
            return msg.toString(StandardCharsets.UTF_8);
        }
        return super.format(ctx, event, arg);
    }
}

Configuring the custom handler:

HttpClient httpClient = HttpClient.create()
    .tcpConfiguration(tcpClient ->
        tcpClient.bootstrap(bootstrap ->
            BootstrapHandlers.updateLogSupport(bootstrap, 
                new HttpLoggingHandler())));

WebClient.builder()
    .clientConnector(new ReactorClientHttpConnector(httpClient))
    .build();

Performance and Best Practices

When implementing logging in production environments, consider the following best practices:

  1. Selective Logging: Avoid logging complete request and response bodies in production, particularly for large data or sensitive information.
  2. Log Level Control: Use appropriate log levels (DEBUG or TRACE) to control output and prevent performance impacts.
  3. Asynchronous Processing: Ensure logging operations do not block reactive streams, maintaining non-blocking characteristics.
  4. Sensitive Information Filtering: Filter authentication tokens, passwords, and other sensitive data before logging.

Conclusion

Spring WebClient offers multi-layered, flexible logging mechanisms. ExchangeFilterFunction provides a concise yet powerful solution for most application scenarios, while Netty wiretap and Spring Boot auto-configuration offer complementary approaches for specific requirements. Developers should select appropriate methods based on concrete needs, balancing debugging requirements with performance considerations. Through proper configuration and utilization of these tools, observability and maintainability of microservices systems can be significantly enhanced.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.