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