在本快速教程中,我们将演示使用 Spring 的日志记录过滤器记录传入请求的基础知识。如果我们刚刚开始使用日志记录,我们可以查看此日志记录介绍文章以及SLF4J 文章。
日志记录依赖项将与介绍文章中的依赖项相同;我们在这里简单地添加 Spring:
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-core</artifactId>
- <version>5.2.2.RELEASE</version>
- </dependency>
Copy
最新版本可以在这里找到弹簧芯。
首先,我们将定义一个要在示例中使用的控制器:
- @RestController
- public class TaxiFareController {
-
- @GetMapping("/taxifare/get/")
- public RateCard getTaxiFare() {
- return new RateCard();
- }
-
- @PostMapping("/taxifare/calculate/")
- public String calculateTaxiFare(
- @RequestBody @Valid TaxiRide taxiRide) {
-
- // return the calculated fare
- }
- }
Copy
Spring 提供了一种机制,用于配置用户定义的拦截器以在 Web 请求之前和之后执行操作。
在 Spring 请求拦截器中,其中一个值得注意的接口是HandlerInterceptor,我们可以通过实现以下方法来记录传入的请求:
此外,Spring 以 HandlerInterceptorAdaptor 类的形式提供了HandlerInterceptor接口的默认实现,用户可以扩展该类。
让我们通过将HandlerInterceptorAdaptor扩展为来创建我们自己的拦截器:
- @Component
- public class TaxiFareRequestInterceptor
- extends HandlerInterceptorAdapter {
-
- @Override
- public boolean preHandle(
- HttpServletRequest request,
- HttpServletResponse response,
- Object handler) {
- return true;
- }
-
- @Override
- public void afterCompletion(
- HttpServletRequest request,
- HttpServletResponse response,
- Object handler,
- Exception ex) {
- //
- }
- }
Copy
最后,我们将在 MVC 生命周期中配置TaxiRideRequestInterceptor,以捕获映射到TaxiFareController类中定义的路径/taxifare的控制器方法调用的预处理和后处理:
- @Configuration
- public class TaxiFareMVCConfig implements WebMvcConfigurer {
-
- @Autowired
- private TaxiFareRequestInterceptor taxiFareRequestInterceptor;
-
- @Override
- public void addInterceptors(InterceptorRegistry registry) {
- registry.addInterceptor(taxiFareRequestInterceptor)
- .addPathPatterns("/taxifare/*/");
- }
- }
Copy
总之,WebMvcConfigurer通过调用addInterceptors() 方法在 Spring MVC 生命周期中添加了TaxiFareRequestInterceptor。
最大的挑战是获取请求和响应有效负载的副本以进行日志记录,并且仍然将请求的有效负载留给 servlet 来处理它:
读取请求的主要问题是,一旦首次读取输入流,它就会被标记为已使用,无法再次读取。
应用程序将在读取请求流后引发异常:
- {
- "timestamp": 1500645243383,
- "status": 400,
- "error": "Bad Request",
- "exception": "org.springframework.http.converter
- .HttpMessageNotReadableException",
- "message": "Could not read document: Stream closed;
- nested exception is java.io.IOException: Stream closed",
- "path": "/rest-log/taxifare/calculate/"
- }
Copy
为了克服这个问题,我们可以利用缓存来存储请求流并将其用于日志记录。
Spring 提供了一些有用的类,例如 ContentCachingRequestWrapper 和ContentCachingResponseWrapper,它们可用于缓存请求数据以进行日志记录。
让我们调整TaxiRideRequestInterceptor类的preHandle(),以使用ContentCachingRequestWrapper类缓存请求对象:
- @Override
- public boolean preHandle(HttpServletRequest request,
- HttpServletResponse response, Object handler) {
-
- HttpServletRequest requestCacheWrapperObject
- = new ContentCachingRequestWrapper(request);
- requestCacheWrapperObject.getParameterMap();
- // Read inputStream from requestCacheWrapperObject and log it
- return true;
- }
Copy
如我们所见,我们使用ContentCachingRequestWrapper类缓存请求对象,我们可以使用它来读取有效负载数据以进行日志记录,而不会干扰实际的请求对象:
requestCacheWrapperObject.getContentAsByteArray();
Copy
限度
- Content-Type:application/x-www-form-urlencoded
- Method-Type:POST
Copy
requestCacheWrapperObject.getParameterMap();
Copy
Spring 提供了一个内置的解决方案来记录有效负载。我们可以通过使用配置插入 Spring 应用程序来使用现成的过滤器。
AbstractRequestLoggingFilter是一个提供日志记录基本功能的过滤器。子类应覆盖 beforeRequest() 和afterRequest() 方法,以围绕请求执行实际日志记录。
Spring 框架提供了三个具体的实现类,我们可以用来记录传入的请求。这三个类是:
现在让我们转到CommonsRequestLoggingFilter,并将其配置为捕获用于日志记录的传入请求。
我们可以通过添加 Bean 定义来配置 Spring 引导应用程序以启用请求日志记录:
- @Configuration
- public class RequestLoggingFilterConfig {
-
- @Bean
- public CommonsRequestLoggingFilter logFilter() {
- CommonsRequestLoggingFilter filter
- = new CommonsRequestLoggingFilter();
- filter.setIncludeQueryString(true);
- filter.setIncludePayload(true);
- filter.setMaxPayloadLength(10000);
- filter.setIncludeHeaders(false);
- filter.setAfterMessagePrefix("REQUEST DATA : ");
- return filter;
- }
- }
Copy
此日志记录筛选器还要求我们将日志级别设置为 DEBUG。我们可以通过在logback中添加以下元素来启用DEBUG模式.xml:
- <logger name="org.springframework.web.filter.CommonsRequestLoggingFilter">
- <level value="DEBUG" />
- </logger>
Copy
启用 DEBUG 级别日志的另一种方法是在application.properties 中添加以下内容:
- logging.level.org.springframework.web.filter.CommonsRequestLoggingFilter=
- DEBUG
Copy
在标准的Spring Web应用程序中,我们可以通过XML配置或Java配置来设置过滤器。因此,让我们使用传统的基于Java的配置来设置CommonsRequestLoggingFilter。
众所周知,默认情况下,CommonsRequestLoggingFilter的includePayload属性设置为 false。我们需要一个自定义类来覆盖属性的值,以便在使用 Java 配置注入容器之前启用includePayload:
- public class CustomeRequestLoggingFilter
- extends CommonsRequestLoggingFilter {
-
- public CustomeRequestLoggingFilter() {
- super.setIncludeQueryString(true);
- super.setIncludePayload(true);
- super.setMaxPayloadLength(10000);
- }
- }
Copy
然后我们需要使用基于 Java 的 Web 初始值设定项注入CustomeRequestLoggingFilter:
- public class CustomWebAppInitializer implements
- WebApplicationInitializer {
- public void onStartup(ServletContext container) {
-
- AnnotationConfigWebApplicationContext context
- = new AnnotationConfigWebApplicationContext();
- context.setConfigLocation("com.baeldung");
- container.addListener(new ContextLoaderListener(context));
-
- ServletRegistration.Dynamic dispatcher
- = container.addServlet("dispatcher",
- new DispatcherServlet(context));
- dispatcher.setLoadOnStartup(1);
- dispatcher.addMapping("/");
-
- container.addFilter("customRequestLoggingFilter",
- CustomeRequestLoggingFilter.class)
- .addMappingForServletNames(null, false, "dispatcher");
- }
- }
Copy
最后,我们可以将 Spring Boot 与上下文连接起来,以查看传入请求的日志记录是否按预期工作:
- @Test
- public void givenRequest_whenFetchTaxiFareRateCard_thanOK() {
- TestRestTemplate testRestTemplate = new TestRestTemplate();
- TaxiRide taxiRide = new TaxiRide(true, 10l);
- String fare = testRestTemplate.postForObject(
- URL + "calculate/",
- taxiRide, String.class);
-
- assertThat(fare, equalTo("200"));
- }
Copy
在本文中,我们学习了如何使用拦截器实现基本的 Web 请求日志记录。我们还探讨了此解决方案的局限性和挑战。
然后,我们讨论了内置筛选器类,它提供了现成的简单日志记录机制。
与往常一样,示例和代码片段的实现可在GitHub 上找到。