1. Introduction
1.介绍
In this quick tutorial, we’ll demonstrate the basics of logging incoming requests using Spring’s logging filter. If we’re just getting started with logging, we can check out this logging intro article, as well as the SLF4J article.
在这个快速教程中,我们将演示使用Spring的日志过滤器记录传入请求的基础知识。如果我们刚刚开始使用日志,我们可以查看这篇日志介绍文章,以及SLF4J文章。
2. Maven Dependencies
2.Maven的依赖性
The logging dependencies will be the same as the ones in the intro article; we’ll simply add Spring here:
日志的依赖性将与介绍文章中的相同;我们只需在这里添加Spring。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
The latest version can be found here for spring-core.
最新的版本可以在这里找到spring-core>。
3. Basic Web Controller
3.基本网络控制器
First, we’ll define a controller to use in our example:
首先,我们将定义一个控制器,在我们的例子中使用。
@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
}
}
4. Custom Request Logging
4.自定义请求记录
Spring provides a mechanism for configuring user-defined interceptors to perform actions before and after web requests.
Spring提供了一种机制来配置用户定义的拦截器,以便在Web请求前后执行动作。
Among the Spring request interceptors, one of the noteworthy interfaces is HandlerInterceptor, which we can use to log the incoming request by implementing the following methods:
在Spring请求拦截器中,其中一个值得注意的接口是 HandlerInterceptor,我们可以通过实现以下方法来记录传入的请求。
- preHandle() – we execute this method before the actual controller service method
- afterCompletion() – we execute this method after the controller is ready to send the response
Furthermore, Spring provides the default implementation of the HandlerInterceptor interface in the form of the HandlerInterceptorAdaptor class, which the user can extend.
此外,Spring以HandlerInterceptor类的形式提供了HandlerInterceptorAdaptor接口的默认实现,用户可以扩展该类。
Let’s create our own interceptor by extending HandlerInterceptorAdaptor as:
让我们通过扩展HandlerInterceptorAdaptoras来创建我们自己的拦截器。
@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) {
//
}
}
Finally, we’ll configure the TaxiRideRequestInterceptor inside the MVC lifecycle to capture the pre and post-processing of controller method invocations that map to the path /taxifare defined in the TaxiFareController class:
最后,我们将在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/*/");
}
}
In conclusion, the WebMvcConfigurer adds the TaxiFareRequestInterceptor inside the spring MVC lifecycle by invoking the addInterceptors() method.
总之,WebMvcConfigurer通过调用addInterceptors()方法在spring MVC生命周期内添加了TaxiFareRequestInterceptor。
The biggest challenge is to get the copies of the request and response payload for logging, and still leave the requested payload for the servlet to process it:
最大的挑战是如何获得请求和响应有效载荷的副本以进行记录,并且仍然将请求的有效载荷留给servlet来处理它。
The main issue with the reading request is that, as soon as the input stream is read for the first time, it’s marked as consumed and cannot be read again.
读取请求的主要问题是,一旦输入流被第一次读取,它就被标记为已被消耗,不能再被读取。
The application will throw an exception after reading the request stream:
应用程序将在读取请求流后抛出一个异常。
{
"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/"
}
To overcome this problem, we can leverage caching to store the request stream and use it for logging.
为了克服这个问题,我们可以利用缓存来存储请求流并将其用于记录。
Spring provides a few useful classes, such as ContentCachingRequestWrapper and ContentCachingResponseWrapper, which can be used for caching the request data for logging purposes.
Spring提供了一些有用的类,例如ContentCachingRequestWrapper和ContentCachingResponseWrapper,它们可用于缓存请求数据以进行记录。
Let’s adjust our preHandle() of the TaxiRideRequestInterceptor class to cache the request object using the ContentCachingRequestWrapper class:
让我们调整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;
}
As we can see, we cache the request object using the ContentCachingRequestWrapper class, which we can use to read the payload data for logging without disturbing the actual request object:
正如我们所看到的,我们使用ContentCachingRequestWrapper类来缓存请求对象,我们可以用它来读取有效载荷数据进行记录,而不干扰实际的请求对象。
requestCacheWrapperObject.getContentAsByteArray();
Limitation
局限性。
- The ContentCachingRequestWrapper class only supports the following:
Content-Type:application/x-www-form-urlencoded
Method-Type:POST
- We must invoke the following method to ensure that the request data is cached in ContentCachingRequestWrapper before using it:
requestCacheWrapperObject.getParameterMap();
5. Spring Built-In Request Logging
5.Spring内置的请求记录
Spring provides a built-in solution to log payloads. We can use the ready-made filters by plugging into the Spring application using configuration.
Spring提供了一个内置的解决方案来记录有效载荷。我们可以通过使用配置插入到Spring应用程序中来使用现成的过滤器。
AbstractRequestLoggingFilter is a filter that provides the basic functions of logging. Subclasses should override the beforeRequest() and afterRequest() methods to perform the actual logging around the request.
AbstractRequestLoggingFilter是一个过滤器,提供了日志记录的基本功能。子类应该覆盖beforeRequest()和afterRequest()方法以执行围绕请求的实际日志记录。
The Spring framework provides three concrete implementation classes that we can use to log the incoming request. These three classes are:
Spring框架提供了三个具体的实现类,我们可以用它们来记录传入的请求。这三个类是。
- CommonsRequestLoggingFilter
- Log4jNestedDiagnosticContextFilter (deprecated)
- ServletContextRequestLoggingFilter
Now let’s move on to the CommonsRequestLoggingFilter, and configure it to capture incoming requests for logging.
现在让我们转到CommonsRequestLoggingFilter,并配置它以捕获传入的请求进行记录。
5.1. Configure Spring Boot Application
5.1.配置Spring Boot应用程序
We can configure the Spring Boot application by adding a bean definition to enable request logging:
我们可以通过添加一个Bean定义来配置Spring Boot应用程序,以启用请求日志。
@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;
}
}
This logging filter also requires us to set the log level to DEBUG. We can enable the DEBUG mode by adding the below element in logback.xml:
这个日志过滤器还要求我们将日志级别设置为DEBUG。我们可以通过在logback.xml中添加以下元素来启用DEBUG模式。
<logger name="org.springframework.web.filter.CommonsRequestLoggingFilter">
<level value="DEBUG" />
</logger>
Another way of enabling the DEBUG level log is to add the following in application.properties:
另一种启用DEBUG级别日志的方法是在application.properties中添加以下内容。
logging.level.org.springframework.web.filter.CommonsRequestLoggingFilter=
DEBUG
5.2. Configure Traditional Web Application
5.2.配置传统的Web应用程序
In the standard Spring web application, we can set the Filter via either XML configuration or Java configuration. So let’s set up the CommonsRequestLoggingFilter using conventional Java based configuration.
在标准的Spring Web应用程序中,我们可以通过XML配置或Java配置来设置Filter。所以让我们使用传统的基于Java的配置来设置CommonsRequestLoggingFilter。
As we know, the includePayload attribute of the CommonsRequestLoggingFilter is set to false by default. We would need a custom class to override the value of the attribute to enable includePayload before injecting into the container using Java configuration:
正如我们所知,CommonsRequestLoggingFilter的includePayload属性默认被设置为false。我们需要一个自定义类来覆盖该属性的值,以便在使用Java配置注入容器之前启用includePayload。
public class CustomeRequestLoggingFilter
extends CommonsRequestLoggingFilter {
public CustomeRequestLoggingFilter() {
super.setIncludeQueryString(true);
super.setIncludePayload(true);
super.setMaxPayloadLength(10000);
}
}
Then we need to inject the CustomeRequestLoggingFilter using the Java based web initializer:
然后我们需要使用基于Java的网络初始化器注入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");
}
}
6. Example in Action
6.行动中的例子
Finally, we can wire up a Spring Boot with context to see in action that the logging of incoming requests works as expected:
最后,我们可以把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"));
}
7. Conclusion
7.结论
In this article, we learned how to implement basic web request logging using interceptors. We also explored the limitations and challenges of this solution.
在这篇文章中,我们学习了如何使用拦截器实现基本的Web请求记录。我们还探讨了这种解决方案的局限性和挑战。
Then we discussed the built-in filter class, which provides ready to use and simple logging mechanisms.
然后我们讨论了内置的过滤器类,它提供了随时可用的简单日志机制。
As always, the implementation of the example and code snippets are available over on GitHub.
一如既往,例子的实现和代码片段可在GitHub上获得。。