Giới Thiệu
Chào các bạn, hôm nay mình xin giới thiệu một bài viết trong chuỗi series về Java và Spring Framework. Chúng ta sẽ cùng nhau tìm hiểu cách thức logging HTTP Request và Response bằng cách sử dụng Spring WebFlux. Rất mong những ý kiến đóng góp từ các bạn để bài viết thêm hoàn thiện.
Cấu Trúc Dự Án
Dưới đây là cấu trúc dự án mà chúng ta sẽ sử dụng trong bài hướng dẫn này:
|- spring-webflux
|- src
|- main
|- java
|- io.github.ntduycs.springwebflux
|- config
|- controller
|- UserController.java
|- filter
|- LoggingFilter.java
|- util
|- WebFluxApplication.java
|- resources
|- pom.xml
Cấu trúc này rất quen thuộc với những ai đã từng làm việc với Spring hoặc Java. Trong bài viết này, mình sẽ không giải thích sâu về cấu trúc này, tuy nhiên nếu có nhu cầu, hãy cho mình biết để mình có thể viết một bài giới thiệu chi tiết hơn.
Thêm Thư Viện Cần Thiết
Để bắt đầu với Spring WebFlux, chúng ta cần thêm một số dependencies trong file pom.xml
:
xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test-autoconfigure</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Với các dependencies trên, bạn chỉ cần thêm spring-boot-starter-webflux
để bắt đầu. Tuy nhiên, các dependency khác sẽ hữu ích cho các bài viết sau.
Phương Pháp Thực Hiện
Chúng ta sẽ tạo một custom filter để log thông tin về request và response khi server nhận được dữ liệu từ client. Mỗi request sẽ đi qua một chuỗi các filter trước khi được xử lý bởi controller. Đây là cách mà chúng ta thực hiện logging.
Tạo Custom Filter
Spring WebFlux cung cấp khả năng tạo filter tùy chỉnh thông qua việc implement interface WebFilter
:
java
@Slf4j
@Component
public class LoggingFilter implements WebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return chain.filter(new LoggingWebExchange(exchange));
}
}
Trong đoạn mã trên, chúng ta đã tạo một LoggingFilter
để thực hiện logging cho mỗi request. Chúng ta cần thông báo cho Spring container biết về filter này bằng annotation @Component
.
Tạo Logging WebExchange
Tiếp theo, để log thông tin từ request và response, chúng ta sẽ cần tạo một class LoggingWebExchange
kế thừa từ ServerWebExchangeDecorator
:
java
static class LoggingWebExchange extends ServerWebExchangeDecorator {
private final ServerHttpRequest request;
private final ServerHttpResponse response;
protected LoggingWebExchange(ServerWebExchange delegate) {
super(delegate);
this.request = new RequestLoggingDecorator(delegate.getRequest());
this.response = new ResponseLoggingDecorator(delegate.getResponse());
}
@Override
public ServerHttpRequest getRequest() {
return request;
}
@Override
public ServerHttpResponse getResponse() {
return response;
}
}
Class này cho phép ta truy cập thông tin về HTTP request và response trong quá trình xử lý.
Tạo Request Decorator
Chúng ta sẽ cần ghi lại một số thông tin quan trọng từ request:
- HTTP method (GET, POST, ...)
- HTTP headers
- Request body
- Request path
- Request query parameters
Mã cho Request Decorator như sau:
java
static class RequestLoggingDecorator extends ServerHttpRequestDecorator {
private final Flux<DataBuffer> body;
protected RequestLoggingDecorator(ServerHttpRequest request) {
super(request);
log.info("Request: {} {}", request.getMethod(), request.getPath());
log.info("Query: {}", JsonUtils.stringify(request.getQueryParams().toSingleValueMap()));
log.info("Headers: {}", JsonUtils.stringify(request.getHeaders().toSingleValueMap()));
this.body = super.getBody().doOnNext(this::logBody);
}
@Override
public Flux<DataBuffer> getBody() {
return body;
}
private void logBody(DataBuffer dataBuffer) {
log.debug("Request body: {}", dataBuffer.toString(StandardCharsets.UTF_8));
}
}
Tạo Response Decorator
Tương tự như Request Decorator, ta cũng cần ghi lại thông tin từ response:
java
static class ResponseLoggingDecorator extends ServerHttpResponseDecorator {
public ResponseLoggingDecorator(ServerHttpResponse delegate) {
super(delegate);
}
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
return super.writeWith(Flux.from(body).doOnNext(dataBuffer -> log.debug("Response: {}", dataBuffer.toString(StandardCharsets.UTF_8))));
}
}
Kiểm Tra Kết Quả
Để kiểm tra xem filter đã hoạt động hay chưa, chúng ta sẽ tạo một controller đơn giản:
java
@RestController
@RequestMapping("/users")
@RequiredArgsConstructor
public class UserController {
@GetMapping
public Mono<ResponseEntity<ListUserResponse>> listUsers(@Valid ListUserRequest request) {
return Mono.just(ResponseEntity.ok(new ListUserResponse()));
}
@PostMapping
public Mono<ResponseEntity<Void>> createUser(@RequestBody CreateUserRequest request) {
return Mono.just(ResponseEntity.noContent().build());
}
}
Bây giờ, hãy chạy ứng dụng và gọi tới API để xem kết quả.
Khởi Chạy Ứng Dụng
Chúng ta có thể khởi chạy ứng dụng Spring thông qua câu lệnh sau:
mvn spring-boot:run
(Lưu ý rằng bạn cần cài đặt Maven trước khi thực hiện lệnh này)
Gọi Đến API ListUsers
Sử dụng lệnh sau để gọi đến API ListUsers
:
curl 'localhost:9990/users?name=foo'
Quan sát console, bạn sẽ thấy các log đã được in ra:
INFO - Request: GET /users
INFO - Query: {"name":"foo"}
INFO - Headers: {"Host":"localhost:9990","User-Agent":"curl/8.7.1","Accept":"*/*"}
Gọi Đến API CreateUser
Sử dụng lệnh để gọi đến API CreateUser
:
curl -X POST localhost:9990/users --data '{"email": "foo@bar.com", "name": "Foo Bar"}' -H 'Content-Type:application/json'
Khi kiểm tra log, bạn sẽ thấy thông tin cần thiết đã được ghi lại đầy đủ.
Kết Luận
Chúng ta vừa khám phá cách thực hiện logging cho HTTP request và response bằng Spring WebFlux. Một số điểm có thể cải thiện bao gồm:
- Cơ chế enable/disable logging: Tùy chỉnh logging cho các môi trường khác nhau như DEV, SIT, và PROD.
- Log masking/redacting: Che giấu thông tin nhạy cảm như email trong log.
- Định dạng log: Sử dụng JSON thay vì plain text cho log.
Cảm ơn các bạn đã theo dõi bài viết! Nếu có bất kỳ câu hỏi nào, hãy để lại comment cho mình nhé. Chúc các bạn coding vui vẻ!
source: viblo