Spring Boot - How to log all requests and responses with exceptions in single place?

4 min read 07-10-2024
Spring Boot - How to log all requests and responses with exceptions in single place?


Logging Requests, Responses, and Exceptions in Spring Boot: A Unified Approach

Logging is crucial for debugging, monitoring, and analyzing application behavior. In Spring Boot applications, it's common to log requests, responses, and exceptions separately. However, this can lead to scattered logs making it difficult to understand the complete flow of an interaction. This article demonstrates how to unify request, response, and exception logging in a single location for enhanced troubleshooting.

The Problem: Scattered Logs

Let's imagine a scenario where we're building a REST API using Spring Boot. We have separate logging configurations for request headers, response bodies, and error messages. This can result in log entries like:

Request:

2023-10-27 12:00:00.000 INFO  [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet - GET /api/users - 200 OK

Response:

2023-10-27 12:00:00.001 INFO  [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet - Request [GET /api/users] completed in 1ms, 0 bytes written

Exception:

2023-10-27 12:00:00.002 ERROR  [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `java.util.List` from String "invalid json":
  at [Source: (byte[])"invalid json"; line: 1, column: 1]
 (through reference chain: "users")

Finding the relevant information requires searching across multiple log files, making debugging cumbersome.

Unified Logging: A Single Source of Truth

To streamline logging, we can implement a custom filter that intercepts all requests and responses, capturing the necessary information. This filter can be configured to log all relevant details in a single log entry.

1. Create a Custom Filter:

import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;

@Component
public class RequestResponseLoggingFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        long startTime = System.currentTimeMillis();
        // Store request details
        String requestMethod = request.getMethod();
        String requestURI = request.getRequestURI();
        String queryString = request.getQueryString();
        String clientIP = request.getRemoteAddr();

        // Log Request details
        logger.info("Request: {} {} {} from {}", requestMethod, requestURI, queryString, clientIP);
        // Log request headers
        logger.info("Request Headers: {}", request.getHeaderNames());

        try {
            // Pass the request to the next filter in the chain
            filterChain.doFilter(request, response);
            // Log Response details
            logger.info("Response status: {}", response.getStatus());
            // Log response headers
            logger.info("Response Headers: {}", response.getHeaderNames());
        } catch (Exception ex) {
            // Log the exception
            logger.error("Exception occurred: ", ex);
        } finally {
            // Calculate response time
            long endTime = System.currentTimeMillis();
            long responseTime = endTime - startTime;
            logger.info("Request completed in {} ms", responseTime);
        }
    }
}

2. Configure the Filter:

In your application's WebSecurityConfigurerAdapter or WebConfig, register the filter:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private RequestResponseLoggingFilter requestResponseLoggingFilter;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // ... Your security configuration ...

        http.addFilterBefore(requestResponseLoggingFilter, WebAsyncManagerIntegrationFilter.class);
    }
}

3. Utilize Spring's @Slf4j Annotation:

Instead of using logger.info() and logger.error(), you can use log.info() and log.error(), which will automatically inject a logger based on your chosen logging framework (e.g., SLF4J).

4. Analyze Unified Logs:

Now, your log entries will contain all the information about a request:

2023-10-27 12:00:00.000 INFO  [http-nio-8080-exec-1] com.example.demo.filter.RequestResponseLoggingFilter - Request: GET /api/users null from 127.0.0.1
2023-10-27 12:00:00.000 INFO  [http-nio-8080-exec-1] com.example.demo.filter.RequestResponseLoggingFilter - Request Headers: [Accept, Content-Type, User-Agent]
2023-10-27 12:00:00.001 INFO  [http-nio-8080-exec-1] com.example.demo.filter.RequestResponseLoggingFilter - Response status: 200
2023-10-27 12:00:00.001 INFO  [http-nio-8080-exec-1] com.example.demo.filter.RequestResponseLoggingFilter - Response Headers: [Content-Type, Date]
2023-10-27 12:00:00.001 INFO  [http-nio-8080-exec-1] com.example.demo.filter.RequestResponseLoggingFilter - Request completed in 1 ms

In case of an exception:

2023-10-27 12:00:00.000 INFO  [http-nio-8080-exec-1] com.example.demo.filter.RequestResponseLoggingFilter - Request: GET /api/users null from 127.0.0.1
2023-10-27 12:00:00.000 INFO  [http-nio-8080-exec-1] com.example.demo.filter.RequestResponseLoggingFilter - Request Headers: [Accept, Content-Type, User-Agent]
2023-10-27 12:00:00.002 ERROR  [http-nio-8080-exec-1] com.example.demo.filter.RequestResponseLoggingFilter - Exception occurred: org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `java.util.List` from String "invalid json":
  at [Source: (byte[])"invalid json"; line: 1, column: 1]
 (through reference chain: "users")
2023-10-27 12:00:00.002 INFO  [http-nio-8080-exec-1] com.example.demo.filter.RequestResponseLoggingFilter - Request completed in 2 ms

Advantages of Unified Logging:

  • Improved Debugging: Easily trace request/response flow and identify the root cause of issues.
  • Simplified Monitoring: Analyze application performance by examining request times and response codes.
  • Enhanced Security Auditing: Capture critical request details for security analysis and compliance.

Conclusion

By centralizing request, response, and exception logging, you can dramatically simplify debugging and troubleshooting in your Spring Boot applications. The provided filter serves as a starting point, which can be further customized to include additional information or adapt to your specific logging requirements. Remember, effective logging is crucial for building robust and maintainable applications.