spring记录http请求日志
在应用或接口开发中往往需要对一次请求的入参和出参进行完整的记录,有一些操作可能不仅需要文件记录,可能还需要存库。在不侵入业务代码情况下,怎么做呢?很正常的会想到两个东西过滤器,AOP切面。
过滤器
我们本身就需要对请求进行一些默认的过滤器配置,如编码过滤器,XSS过滤器。针对输入日志spring有提供CommonsRequestLoggingFilter过滤器。可以进行请求日志的打印。
首先要把该类对应日志级别设置为DEBUG
logging.level.org.springframework.web.filter.CommonsRequestLoggingFilter=debug
配置filter输出内容
@Bean
public CommonsRequestLoggingFilter loggingFilter(){
CommonsRequestLoggingFilter filter
= new CommonsRequestLoggingFilter();
filter.setIncludeQueryString(true);
filter.setIncludePayload(true);//是否包含payload
filter.setMaxPayloadLength(10000);//设置最大长度
filter.setIncludeHeaders(false);//是否记录header信息
filter.setAfterMessagePrefix("请求数据: ");
filter.setBeforeMessagePrefix("请求地址:");
return filter;
}
这样请求数据就会被输出到log文件,不过这个有个缺点是不能对response数据。
另外其实可以在自己定义的filte里自定义打印,只要有request对象。
AOP切面代理
这个学动态代理的时候肯定都学过,代理记录日志。
spring提供的几个切面编程注解
@Pointcut 定义切面
//controller下所有public方法
@Pointcut("execution(public * com.test.*.controller.*.*(..)))")
public void logPoint(){};
@Before 切入方法执行前处理
@Before(value = "logPoint()")
public void before(JoinPoint joinPoint){
//
}
@AfterReturning 返回后处理;@AfterThrowing 返回后处理。
@AfterReturning(value = "logPoint()",returning = "result")
public void afterReturn(JoinPoint joinPoint,Object result) throws Throwable {
handleLog(joinPoint,result,null);
}
@AfterThrowing( pointcut = "logPoint()",throwing = "ex")
public void afterThrow(JoinPoint joinPoint, Exception ex){
handleLog(joinPoint,null,ex);
}
还有@Aroud自己处理。需要在中间自己调方法procced继续执行。不能和前面的共用。这里用前面三个注解就够了。每个注解都可以获取到JoinPoint对象。然后通过反射获取当前方法,入参出参就都能拿到,当然也可以自定义注解打在该方法上,定义一些模块,操作类型标识这样通过反射都可以获取到。
假定所有的请求都是post json格式放到body里
请求信息在@Before里进行处理,一种方式通过反射拿到方法的入参进行打印,另一个通过request对象。这里不仅可以记录日志,限流什么的也可以在这里执行。如果要记录方法执行时间这个时候可以将开始时间放到request的attribute中,如果需要记录的东西多的话还可以定义一个对象去存储。当然这个地方也可以放到ThreadLocal中,这样就不需要类型转换了,记得在后面的after里要执行remove操作。
@AfterReturning可以直接获取到返回值对象。响应就可以直接获取到。异常时候会走@AfterThrowing也可以拿到异常信息。
- request对象获取
1、通过RequestContextHolder获取
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
2、可以直接使用注解注入获取,已经是一个spring的bean。
- 请求body获取
我们知道request的输入流只能读取一次。我们在controller方法@RequestBody注解入参赋值的时候肯定已经使用过滤,在通过流读请求数据就会报流不可重复读错误。我们可以借助spring提供的一个请求包装类ContentCachingRequestWrapper来解决这个问题。他其实就是把请求流包装了以下,然后流读取的时候同时自己将读取的数据copy了一份。当copy数据有值的时候就直接使用不在从流中读取。
主要代码
public int read(byte[] b) throws IOException {
int count = this.is.read(b);
//读的时候写到cache一份
writeToCache(b, 0, count);
return count;
}
//获取cache
public byte[] getContentAsByteArray() {
return this.cachedContent.toByteArray();
}
最后在切面里获取内容
ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
if (wrapper != null) {
byte[] buf = wrapper.getContentAsByteArray();
if (buf.length > 0) {
return new String(buf, 0, buf.length, wrapper.getCharacterEncoding());
}
}