Spring WebFlux
Logging
Request Body
Response Body
Reactive Programming

How to log request and response bodies in Spring WebFlux

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Logging request and response bodies is a crucial aspect of developing and maintaining a reactive web application using Spring WebFlux. Understanding the incoming and outgoing data can significantly aid in debugging, performance monitoring, and compliance with audit requirements. Unlike traditional blocking Spring MVC applications, WebFlux operates on a fully non-blocking, reactive programming model, which introduces unique challenges when dealing with request and response bodies.

Understanding Spring WebFlux

Spring WebFlux is part of the Spring 5 framework, designed to handle asynchronous request processing in a reactive manner. It runs on the Reactive Streams API and allows for managing backpressure. It supports RESTful services and web applications but functions fundamentally differently than the conventional Spring MVC due to its non-blocking architecture.

Challenges in Logging

In a reactive stream, reading and writing request and response data are handled asynchronously and possibly in chunks. Therefore, logging becomes more complex because:

  • Asynchronous Execution: Data isn’t processed in a step-by-step manner but rather flows through the system as it becomes available.
  • Data Streamed in Parts: Data may come in fragments and need to be assembled for logging.
  • Replayability: Once a stream is consumed, it cannot be re-read unless it is carefully handled.

Logging Request and Response Bodies in WebFlux

1. Using a WebFilter

java
1import org.apache.logging.log4j.LogManager;
2import org.apache.logging.log4j.Logger;
3import org.springframework.stereotype.Component;
4import org.springframework.web.server.ServerWebExchange;
5import org.springframework.web.server.WebFilter;
6import org.springframework.web.server.WebFilterChain;
7import reactor.core.publisher.Mono;
8import reactor.core.publisher.Flux;
9
10@Component
11public class LoggingWebFilter implements WebFilter {
12    private static final Logger logger = LogManager.getLogger(LoggingWebFilter.class);
13
14    @Override
15    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
16        return logRequest(exchange)
17            .then(chain.filter(exchange))
18            .flatMap(resp -> logResponse(exchange));
19    }
20
21    private Mono<Void> logRequest(ServerWebExchange exchange) {
22        return Mono.fromRunnable(() -> {
23            String method = exchange.getRequest().getMethodValue();
24            String path = exchange.getRequest().getURI().getPath();
25            exchange.getRequest().getBody()
26                .doOnNext(buffer -> {
27                    String body = buffer.toString();
28                    logger.info("Request: {} {} Body: {}", method, path, body);
29                }).subscribe();
30        });
31    }
32
33    private Mono<Void> logResponse(ServerWebExchange exchange) {
34        return exchange.getResponse().writeWith(
35            exchange.getResponse().getBody().map(dataBuffer -> {
36                byte[] content = new byte[dataBuffer.readableByteCount()];
37                dataBuffer.read(content);
38                logger.info("Response Body: {}", new String(content));
39                return dataBuffer;
40            })
41        );
42    }
43}

2. Using Default DataBuffer Factory for Data Replay

java
1import org.springframework.core.io.buffer.DataBuffer;
2import org.springframework.core.io.buffer.DataBufferUtils;
3import org.springframework.core.io.buffer.DefaultDataBufferFactory;
4import reactor.core.publisher.Flux;
5
6private Mono<Void> logRequest(ServerWebExchange exchange) {
7    return DataBufferUtils.join(exchange.getRequest().getBody())
8        .flatMap(dataBuffer -> {
9            String body = dataBufferToString(dataBuffer);
10            logger.info("Request Body: {}", body);
11            exchange.getAttributes().put("cachedRequestBody", dataBuffer);
12
13            // Ensure buffer is reusable if needed
14            return Mono.empty();
15        });
16}
17
18private String dataBufferToString(DataBuffer buffer) {
19    byte[] bytes = new byte[buffer.readableByteCount()];
20    buffer.read(bytes);
21    DataBufferUtils.release(buffer);
22    return new String(bytes, StandardCharsets.UTF_8);
23}

In the code above, DataBufferUtils.join and dataBufferToString functions are used to read the entire request body only once and store it for later use, ensuring replayability.

Best Practices

  • Use Appropriate Log Levels: Log information should be at the appropriate levels (INFO, DEBUG, ERROR) tailored to the environment (e.g., DEBUG in development, INFO in production).
  • Sensitive Data Caution: Exclude or redact sensitive information from logs in compliance with data protection regulations.
  • Handle Large Bodies Efficiently: Be mindful of the performance impact of logging large bodies, and consider using sampling or truncation.
Key PointDescription
Non-blocking ModelWebFlux operates on reactive principles using Reactive Streams API.
Asynchronous ExecutionUnlike MVC, WebFlux processes requests and responses asynchronously.
ReplayabilityDataBuffers need careful handling to ensure they can be replayed and logged.
SecurityBe cautious about including sensitive data in logs
PerformanceManage the log size, especially in production environments.

Conclusion

Spring WebFlux provides a robust asynchronous framework conducive to modern reactive applications. Logging request and response bodies require understanding WebFlux’s non-blocking I/O operations but are critical for effective monitoring and debugging. By implementing a WebFilter and using utilities like DataBufferUtils, developers can effectively manage logging in their reactive applications while considering best practices surrounding security and performance.


Course illustration
Course illustration

All Rights Reserved.