Efficient HTTP Request Logging in Spring with Zalando: A Developer's Guide
Logging internal HTTP requests using RestTemplate with Zalando library.
1. Overview
In some cases, it is critical that we log both internal and external HTTP requests. This is very useful for debugging communication between microservices. But this feature is already available with other Spring boot libraries. What I wanted to show in this exercise is the flexibility of logging or saving in a database of particular data.
2. Prerequisites
2.1 Dependencies
For this exercise, I will use the following dependencies:
- Spring Boot 3
- logbook-spring-boot-starter v3.x
2.2 The Spring Project
3. Hands On
3.1 Controller
@RestController
@Slf4j
@RequiredArgsConstructor
public class LoggingController {
private final RestTemplate restTemplate;
@GetMapping("/logs/internal-requests")
public void logBody() {
log.debug("Logging internal request");
restTemplate.getForEntity("http://localhost:8080/call-me", Computer.class);
}
@GetMapping("/call-me")
public Computer callMe() {
log.debug("calling internal url");
return Computer.builder()
.brand("Apple")
.model("Macbook Pro")
.cpu("M2")
.ram(32)
.build();
}
}
This class exposes a REST endpoint /logs/internal-requests that will trigger an HTTP request to another endpoint /call-me. This call will be intercepted by the logbook.
Let's see how it works.
3.2 Web Configuration
We need to create the following beans for the logbook to intercept the HTTP requests.
@Configuration
@RequiredArgsConstructor
public class WebBeansConfig {
private final Logbook logbook;
@Bean
public Logbook getLogbook() {
return Logbook.builder()
.strategy(new StatusAtLeastStrategy(200))
.sink(new LogSink(new LogFormatter(), new LogWriter()))
.build();
}
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.getInterceptors().add(getLogbookInterceptor());
return restTemplate;
}
private LogbookClientHttpRequestInterceptor getLogbookInterceptor() {
return new LogbookClientHttpRequestInterceptor(logbook);
}
}
3.3 Logbook and Logging Classes
After the logbook intercepts the request, it will cause this class to perform pre and post request data manipulation.
@RequiredArgsConstructor
public class LogSink implements Sink {
private final LogFormatter formatter;
private final LogWriter writer;
@Override
public void write(Precorrelation preCorrelation, HttpRequest request) throws IOException {
}
@Override
public void write(Correlation correlation, HttpRequest request, HttpResponse response) throws IOException {
}
@Override
public void writeBoth(Correlation correlation, HttpRequest request, HttpResponse response) throws IOException {
writer.write(formatter.format(correlation.getId(), request, response));
}
}
This class extracts the necessary values from the HTTP request and response object. We can also deduce whether the request is incoming or outgoing.
// Customizeable message model
public class LogFormatter {
public Message format(String correlationId, HttpRequest request, HttpResponse response) throws IOException {
return Message.builder()
.correlationId(correlationId)
.requestBody(request.getBodyAsString())
.responseBody(response.getBodyAsString())
.build();
}
}
Finally, this class logs the message object in the console. We can also save the Message object in the database by injecting a mapper and converting the Message to an entity, then inject a repository that we can use to insert the entity in the database.
// Log writer
@Slf4j
public class LogWriter {
// you can autowire a service here
public void write(Message message) {
log.debug("Http intercepted message={}", message);
}
}




Post a Comment