Learn how to use RestTemplate logging in Spring to print requests and responses, giving you better insights into your API calls.
1. Introduction
Logging HTTP requests and responses can be incredibly helpful for debugging and monitoring your Spring Boot applications. By enabling logging, you can track the data being sent and received, making it easier to troubleshoot issues, ensure data consistency, and gain visibility into the inner workings of your API calls.
In this guide, we’ll walk through a detailed approach to enable request and response logging with RestTemplate in Spring Boot. This includes simple setup steps, a clear example, and explanations to help you get started.
2. Why Log Requests and Responses?
RestTemplate logging gives you insight into your application’s interactions with external services. With this information, you can:
- Debug errors in API requests or responses
- Validate data integrity
- Track HTTP request latency and response status
- Improve observability in production environments
3. Setting Up RestTemplate Logging in Spring Boot
Spring Boot provides multiple ways to enable RestTemplate logging, from configuring properties to creating a custom interceptor. Here, we’ll explore both a configuration-based and a programmatic approach, with examples to make it easier for you to choose based on your needs.
3.1. Enabling Basic HTTP Logging with Spring Boot Properties
For basic HTTP logging, Spring Boot’s logging configuration properties make it easy. You can log basic request and response details without needing extra code by enabling specific logging levels for the org.springframework.web.client.RestTemplate
package.
Step-by-Step Setup:
a. Add the following configuration in application.properties
file to enable logging at the DEBUG level:
logging.level.org.springframework.web.client.RestTemplate=DEBUG
application.propertiesb. Restart your application. Now, you should see basic logs for requests and responses in your console.
Output:
DEBUG 6562 --- [spring-boot-rest-template-logging] [nio-8080-exec-1] o.s.web.client.RestTemplate : HTTP POST https://jsonplaceholder.typicode.com/posts
DEBUG 6562 --- [spring-boot-rest-template-logging] [nio-8080-exec-1] o.s.web.client.RestTemplate : Accept=[text/plain, application/json, application/*+json, */*]
DEBUG 6562 --- [spring-boot-rest-template-logging] [nio-8080-exec-1] o.s.web.client.RestTemplate : Writing [{title=foo, body=bar, userId=1}] as "application/json"
DEBUG 6562 --- [spring-boot-rest-template-logging] [nio-8080-exec-1] o.s.web.client.RestTemplate : Response 201 CREATED
DEBUG 6562 --- [spring-boot-rest-template-logging] [nio-8080-exec-1] o.s.web.client.RestTemplate : Reading to [java.lang.String] as "application/json;charset=utf-8"
application.ymlThe output shows that RestTemplate executed an HTTP POST request to https://jsonplaceholder.typicode.com/posts
, logging the Accept header values, the JSON body being sent {title=foo, body=bar, userId=1}
, and the subsequent response status of 201 CREATED, indicating that the resource was successfully created.
Limitations of Basic HTTP Logging
This setup provides limited information by primarily logging the HTTP method, URL, and some request details. While it captures the request’s content type and body, it may not include crucial information such as other request headers (like Authorization), the complete response body, or response headers. Consequently, for a more comprehensive view of API interactions, including headers and full body content, it’s recommended to implement a custom interceptor method, as explained below.
3.2. Detailed Logging with a Custom RestTemplate Interceptor
To capture request and response headers, bodies, and other details, we’ll create a custom ClientHttpRequestInterceptor
. This custom interceptor logs both requests and responses, providing full control over what’s printed to the logs.
High-Level Steps:
- Create Logging Interceptor Class: Implement the
ClientHttpRequestInterceptor
interface to log requests and responses. - Override the
intercept
Method: Log request data, execute the request, and log response data using a response wrapper. - Create Response Wrapper Class: Implement
ClientHttpResponse
to buffer the response body for logging. - Register the Interceptor: Configure a
RestTemplate
bean and add the logging interceptor.
Detailed Implementation Steps:
Step 1: Create the Logging Interceptor Class
Create a class named LoggingInterceptor
that implements the ClientHttpRequestInterceptor
interface. This class will be responsible for intercepting the HTTP requests and responses to log relevant information.
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.stream.Collectors;
@Component
public class LoggingInterceptor implements ClientHttpRequestInterceptor {
private static final Logger logger = LoggerFactory.getLogger(LoggingInterceptor.class);
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
logRequest(request, body); // Log request details
ClientHttpResponse response = execution.execute(request, body); // Execute the request
ClientHttpResponse bufferedResponse = new BufferingClientHttpResponseWrapper(response); // Wrap the response
logResponse(bufferedResponse); // Log response details
return bufferedResponse; // Return the wrapped response
}
private void logRequest(HttpRequest request, byte[] body) throws IOException {
// Log details of the HTTP request
logger.info("Request URI: {}", request.getURI());
logger.info("Request Method: {}", request.getMethod());
logger.info("Request Headers: {}", request.getHeaders());
logger.info("Request Body: {}", new String(body, StandardCharsets.UTF_8));
}
private void logResponse(ClientHttpResponse response) throws IOException {
// Read and log the response body
String responseBody = new BufferedReader(new InputStreamReader(response.getBody(), StandardCharsets.UTF_8))
.lines()
.collect(Collectors.joining("\n"));
logger.info("Response Status Code: {}", response.getStatusCode());
logger.info("Response Headers: {}", response.getHeaders());
logger.info("Response Body: {}", responseBody);
}
}
LoggingInterceptor.javaExplanation:
- Intercept Method: This method is overridden to implement the logging functionality:
- It first logs the request details using
logRequest
. - Then it executes the HTTP request using
execution.execute(request, body)
. - It wraps the response in a
BufferingClientHttpResponseWrapper
to allow reading the response body multiple times. - Finally, it logs the response details with
logResponse
.
- It first logs the request details using
Step 2: Create a Response Wrapper Class
Next, create a class named BufferingClientHttpResponseWrapper
that implements ClientHttpResponse
. This wrapper allows us to read the response body multiple times.
import org.springframework.http.HttpStatusCode;
import org.springframework.http.client.ClientHttpResponse;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
public class BufferingClientHttpResponseWrapper implements ClientHttpResponse {
private final ClientHttpResponse response; // Original response
private byte[] responseBody; // Buffered response body
public BufferingClientHttpResponseWrapper(ClientHttpResponse response) throws IOException {
this.response = response;
this.responseBody = streamToByteArray(response.getBody()); // Buffer the response body
}
private byte[] streamToByteArray(InputStream inputStream) throws IOException {
// Read the input stream into a byte array
ByteArrayOutputStream baos = new ByteArrayOutputStream();
inputStream.transferTo(baos);
return baos.toByteArray(); // Return the byte array
}
@Override
public InputStream getBody() {
// Return an InputStream from the buffered response body
return new ByteArrayInputStream(responseBody);
}
@Override
public HttpStatusCode getStatusCode() throws IOException {
return response.getStatusCode(); // Delegate to the original response
}
@Override
public int getRawStatusCode() throws IOException {
return response.getRawStatusCode(); // Delegate to the original response
}
@Override
public String getStatusText() throws IOException {
return response.getStatusText(); // Delegate to the original response
}
@Override
public void close() {
response.close(); // Close the original response
}
@Override
public org.springframework.http.HttpHeaders getHeaders() {
return response.getHeaders(); // Delegate to the original response
}
}
BufferingClientHttpResponseWrapper.javaExplanation:
- Response Body Buffering: The constructor reads the response body into a byte array, allowing us to access it multiple times.
- Delegation: All other methods delegate calls to the original
ClientHttpResponse
, ensuring we maintain the original response behavior while adding the buffering functionality.
Why Custom Response Wrapper Class is Necessary?
When the response body is read once, it can’t be read again. This means that if we read the response body to log it, we can’t use it afterward, as the input stream will be exhausted. To overcome this, we copy the response body into a ByteArrayOutputStream
with the help of BufferingClientHttpResponseWrapper
class we created above, allowing us to log it and still send it to the client.
Step 3: Register the Interceptor with the RestTemplate Bean
Now, create a configuration class to register the LoggingInterceptor
with your RestTemplate
. This step ensures that the interceptor is applied to all requests made by the RestTemplate
.
import com.bootcamptoprod.interceptor.LoggingInterceptor;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
import java.util.Collections;
@Configuration
public class RestTemplateConfig {
private final LoggingInterceptor loggingInterceptor; // Dependency injection of the LoggingInterceptor
public RestTemplateConfig(LoggingInterceptor loggingInterceptor) {
this.loggingInterceptor = loggingInterceptor; // Assigning the injected interceptor
}
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
// Build a RestTemplate with the logging interceptor
return builder
.additionalInterceptors(Collections.singletonList(loggingInterceptor)) // Add the interceptor
.build(); // Build and return the RestTemplate instance
}
}
RestTemplateConfig.javaExplanation:
- Configuration Class: This class is marked with
@Configuration
, indicating it provides Spring configuration. RestTemplate
Bean: TherestTemplate
method creates aRestTemplate
instance:- The logging interceptor is added to the builder via
additionalInterceptors
. - Finally, the
RestTemplate
is built and returned.
- The logging interceptor is added to the builder via
Step 4: Create a Controller to Test the Logging
Finally, create a simple controller to test the logging functionality by making an HTTP request.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.HashMap;
import java.util.Map;
@RestController
public class RestTemplateLoggingController {
@Autowired
private RestTemplate restTemplate; // Autowire the RestTemplate
@GetMapping("/test-api")
public ResponseEntity<String> callApi() {
String url = "https://jsonplaceholder.typicode.com/posts"; // External API URL
HttpHeaders headers = new HttpHeaders();
headers.set("Content-Type", "application/json"); // Set content type
// Create a request body
Map<String, String> requestBody = new HashMap<>();
requestBody.put("title", "foo");
requestBody.put("body", "bar");
requestBody.put("userId", "1");
HttpEntity<Map<String, String>> entity = new HttpEntity<>(requestBody, headers); // Wrap headers and body
return restTemplate.exchange(url, HttpMethod.POST, entity, String.class); // Execute the request
}
}
RestTemplateLoggingController.javaExplanation:
- Autowiring
RestTemplate
: TheRestTemplate
instance is injected using@Autowired
. - API Call: The
/test-api
endpoint sends a POST request to an external API (jsonplaceholder.typicode.com
) with a sample request body. - Response Handling: The response from the external API is returned to the caller.
Understanding Output:
When /test-api
controller endpoint is called, the logs capture the request made to the jsonplaceholder
API using RestTemplate, displaying the request URI, method, headers, and body. The response includes a status code of 201 CREATED
, indicating successful resource creation, along with the response headers and body confirming the created resource’s details.
4. Source Code
The complete source code of the above example can be found here.
5. Things to Consider
Here are some important considerations to keep in mind while logging RestTemplate requests and responses:
- Log Level Configuration: Choose appropriate log levels (DEBUG, INFO, WARN, ERROR) for different types of messages to ensure meaningful logs without overwhelming the log output.
- Sensitive Data Protection: Ensure that sensitive information, such as passwords, credit card numbers, and personal identifiable information (PII), are not logged. Implement filters to mask or omit such data.
- Performance Monitoring: Keep an eye on the performance impact of logging, especially if logging large payloads or logging every request/response in high-throughput applications.
- Configuration Management: Consider managing logging configurations through properties files to enable or disable logging easily without code changes.
- Error Handling: Implement proper error handling in your interceptor to avoid breaking the application flow due to logging errors.
6. FAQs
Can I log sensitive information using the interceptor?
It’s crucial to avoid logging sensitive information such as passwords, tokens, or personal data. Ensure to filter or mask sensitive information before logging.
How do I enable logging for specific HTTP methods only?
You can modify the interceptor’s logic to log requests and responses based on specific HTTP methods (e.g., GET, POST) by checking the request method in the intercept method.
Can I configure logging for only certain API endpoints?
Yes, you can enable logging for specific URLs by modifying the logic in your interceptor. You can check the request URI in the intercept method and log the details only if it matches the desired URL patterns.
7. Conclusion
Implementing logging for requests and responses in a Spring RestTemplate can significantly enhance your application’s observability and debugging capabilities. You can gain insights into the data being sent and received from external APIs, making it easier to track down issues and monitor performance. However, it’s essential to consider logging best practices, such as managing sensitive information and monitoring performance impact, to ensure that your logging strategy remains effective and secure.
8. Learn More
Interested in learning more?
Checkout our blog on How to Use Brotli Compression in Spring Boot with Brotli4j
Add a Comment