Request Logging with Spring WebFlux Filters

Intercept incoming API requests before they reach your application to filter or modify data according to your applications needs.

In the world of reactive programming, Spring WebFlux offers a powerful framework for building non-blocking, asynchronous applications. One common requirement in web development is to log information about incoming requests, such as the URL and the request body. In this article, we’ll walk through the process of creating a Spring WebFlux Web Filter that captures and logs these details using the SLF4J logger.

Introduction to Spring WebFlux Web Filters

Web Filters in Spring WebFlux are components that intercept incoming HTTP requests and responses. They provide a way to perform pre-processing or post-processing tasks on requests before they reach the main handler or after the response has been generated. In this article, we’ll focus on creating a pre-processing filter to log information about incoming requests. This filter will be tasked with logging the body of an HTTP request.

Setup

Before we begin, ensure you have a Spring WebFlux project set up. You can create a new Spring Boot project using Spring Initializr and selecting the “Reactive Web” dependency. The example in this article will be using JDK 21 and Gradle as the build tool.

Creation

Let’s create a custom web filter that logs the URL and request body using SLF4J logger. Create a class named RequestLoggingFilter:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import reactor.core.publisher.Mono;
import reactor.core.publisher.SignalType;
import reactor.util.annotation.Nullable;

@Component
public class RequestLoggingFilter implements WebFilter {

    private static final Logger logger = LoggerFactory.getLogger(RequestLoggingFilter.class);

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String url = request.getURI().toString();

        // Get and log the request body
        return DataBufferUtils.join(exchange.getRequest().getBody())
            .doOnNext(buffer -> {
                byte[] bytes = new byte[buffer.readableByteCount()];
                buffer.read(bytes);
                String body = new String(bytes, StandardCharsets.UTF_8);
                logger.info("Request URL: {}", url);
                logger.info("Request Body: {}", body);
            })
            .doOnCancel(() -> logger.debug("Request body logging cancelled"))
            .doFinally(this::logSignalType)
            .then(chain.filter(exchange));
    }

    private void logSignalType(@Nullable SignalType signalType) {
        logger.debug("Request logging signal: {}", signalType);
    }
}

This class is placed inside of the filter package in the project. By adding the @Component annotation and extending the WebFilter class, webflux will automatically detect the filter and add it to the list of filter chains.

Code Explanation

  1. Class Declaration and Logger Initialization:
    • The class implements the WebFilter interface, which requires implementing the filter method.
    • A logger is instantiated using LoggerFactory.getLogger, which is used to log information.
  2. filter Method:
    • This method is the core of the filter and is responsible for processing each HTTP request.
    • It takes two parameters: ServerWebExchange exchange and WebFilterChain chain. The exchange provides access to the HTTP request and response, while the chain allows the filter to pass control to the next filter in the chain.
    • The request’s URL is obtained from exchange.getRequest().getURI().toString().
  3. Logging Request Body:
    • The request body is read and logged. The body is retrieved as a DataBuffer, and DataBufferUtils.join is used to aggregate the request body.
    • The contents of the body are converted to a String using UTF-8 encoding and logged alongside the request URL.
  4. Cancellation and Finalization:
    • The doOnCancel method logs a debug message if the request body logging is canceled.
    • The doFinally method calls logSignalType, which logs the signal type that triggered the end of the request processing.
  5. logSignalType Method:
    • This private method logs the signal type (e.g., onComplete, onError) that indicates the end of the reactive stream’s lifecycle.

The use of reactive streams (Mono and DataBufferUtils) aligns with the non-blocking, reactive nature of Spring WebFlux, ensuring that the logging process does not block the main thread.

Configuration

The @Component annotation indicates that RequestLoggingFilter is a Spring-managed bean. The WebFilter interface requires implementing the filter method, where we extract the URL and request body. We use SLF4J’s logger to log the information at different levels.

Create an Example Request Object

Inside our project a payload package is created to house an incoming DTO object that will match the request for our controller. It will contain three data types for reference: a string, integer, and boolean value. Our ExampleRequest class will contain Lombok annotations to avoid boilerplate code for constructors, setters, and getters. Here is the example object:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ExampleRequest {
    private String param1;
    private Integer param2;
    private Boolean param3;
}

Test Controller

In order to verify the functionality of our webfilter, we will create a test controller that will capture the payload of an incoming request and log the contents to the console. It will be annotated as a @RestController which combines @Controller and @ResponseBody, simplifying the creation of a RESTful web service. It indicates that the class is a Spring MVC controller where each method will return JSON data directly instead of resolving a view.

@RestController
public class TestController {
    @PostMapping("/log")
    public Mono<Void> logRequest(@RequestBody Mono<ExampleRequest> exampleRequestMono) {
        return Mono.empty();
    }
}

Testing

To test the filter, simply send a request to your application’s endpoints. You’ll see the URL and request body logged using the SLF4J logger.

Log Request

Endpoint: POST /log

Description: This endpoint accepts a POST request containing a JSON body representing an ExampleRequest object. The server processes the request body but does not return any content. It is typically used for logging purposes or other actions where no response body is needed.

Request:

  • Method: POST
  • URL: /log
  • Request Body: The request body must be a JSON object that adheres to the ExampleRequest structure.

Request Body Schema:

{
  "property1": "string",
  "property2": "integer",
  "property3": "boolean"
}

Sample Project

Here is a sample of project that utilizes the WebFlux Webfilter as a Spring Boot based application that uses gradle as the build tool.

To build the application just run:

./gradlew build

Then you can run the jar file by running the command:

java -jar build/libs/filter-0.0.1-SNAPSHOT.jar

You can clone the following repository to your local machine, which contains the gradle.build file that specifies the jar name.

Conclusion

Creating a Spring WebFlux Web Filter to log request details using the SLF4J logger is a valuable technique for debugging and monitoring incoming requests. Overall, this filter provides a comprehensive mechanism for capturing and logging essential request details, contributing to better observability and traceability in a WebFlux application. By understanding the core concepts of Web Filters and leveraging the reactive nature of Spring WebFlux, you can enhance your application’s logging capabilities and gain insights into the data flowing through your system.

Index