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.