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
- Class Declaration and Logger Initialization:
- The class implements the
WebFilter
interface, which requires implementing thefilter
method. - A logger is instantiated using
LoggerFactory.getLogger
, which is used to log information.
- The class implements the
filter
Method:- This method is the core of the filter and is responsible for processing each HTTP request.
- It takes two parameters:
ServerWebExchange exchange
andWebFilterChain chain
. Theexchange
provides access to the HTTP request and response, while thechain
allows the filter to pass control to the next filter in the chain. - The request’s URL is obtained from
exchange.getRequest().getURI().toString()
.
- Logging Request Body:
- The request body is read and logged. The body is retrieved as a
DataBuffer
, andDataBufferUtils.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.
- The request body is read and logged. The body is retrieved as a
- Cancellation and Finalization:
- The
doOnCancel
method logs a debug message if the request body logging is canceled. - The
doFinally
method callslogSignalType
, which logs the signal type that triggered the end of the request processing.
- The
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.
Leave a Reply