背景和要求
项目中要求记录部分业务请求的返回值,需要全部记录,且可以通过具体的请求字段,能够查询到,用来做问题查询和处理。项目本身接入了一套ELK体系,能够查询相关的请求日志,但是没有记录返回值,且这个实现是全局的,如果直接扩展,记录返回,可能导致日志内容超大而无法处理,况且目前没有记录返回值的日志本身已经足够大量了。所以,要求能够定制地在某些接口请求中记录返回值,项目是springboot,首先想到的是使用注解,在对应接口上添加注解,再设置拦截器,从而方便获取对应的返回值。
实现
原理介绍
添加新的注解,注解在需要记录返回值的接口方法上,然后通过切面,获取相应的返回值,处理数据后,写入现有的ELK日志中,最终可以通过kibana来查询。其中有些点值得详细说下。
首先,这种日志统计,可以单独放到独立的线程池中去处理,避免增加业务请求的耗时,如下的代码中有个单独的线程池;其次请求中除了统一的模板参数,对于自己定义的参数,也应该记录,如@RequestParam
标记的参数,这样一个日志条目就包括了完整的请求参数,完整的返回值,得到了所有需要的东西。目前的项目中,header使用不多,所以暂时不需要记录。
代码详解
注解类,建议只在方法上添加注解:
import java.lang.annotation.*;
@Inherited
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RecordResp {
}
切面类
@Slf4j
@Aspect
@Component
public class RecordRespInterceptor {
@Resource(name = "respRecorderExecutor")
private ExecutorService respRecorderExecutor;
@Resource
private RespRecorder respRecorder;
@Pointcut("@annotation(com.xxxx.xxxx.aop.RecordResp)")
private void pointcut() {
}
@Around(value = "pointcut()")
public Object after(ProceedingJoinPoint joinPoint) throws Throwable {
Object object = joinPoint.proceed();
HttpServletRequest request = getRequest();
HttpServletResponse response = getResponse();
respRecorderExecutor.submit(() -> respRecord(object, joinPoint, request, response));
//object是方法返回值
return object;
}
private void respRecord(Object object, ProceedingJoinPoint joinPoint, HttpServletRequest request, HttpServletResponse response) {
try {
Parameter[] parameters = ((MethodSignature) joinPoint.getSignature()).getMethod().getParameters();
Object[] args = joinPoint.getArgs();
Map<String, Object> paramMap = new HashMap<>();
for (int i = 0; i < parameters.length; i++) {
if (args[i] instanceof HttpServletRequest) {
continue;
}
if (args[i] instanceof HttpServletResponse) {
continue;
}
//如果还有其他类型参数,也要略过,否则是很大的对象内容,这里只关心需要的。
paramMap.put(parameters[i].getName(), args[i]);//(1)
}
respRecorder.write(xxx, paramMap, object, request, response); //xxx为通用模板参数对象
} catch (Exception e) {
log.error("recorder resp error.", e);
}
}
private HttpServletRequest getRequest() {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
return attributes.getRequest();
}
private HttpServletResponse getResponse() {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
return attributes.getResponse();
}
}
其中 RespRecorder
是已有实现的ELK日志写入类,不方便放代码,请见谅。 (1)标注的一行paramMap.put(parameters[i].getName(), args[i]);
,需要在项目中添加编译参数-parameters
,否则获取的参数名称为arg0,arg1
类似值,无法实际区分,在jdk1.8才支持,默认是关闭的。maven项目可以在pom配置里添加如下内容:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<fork>true</fork>
<meminitial>1024m</meminitial>
<maxmem>1024m</maxmem>
<verbose>true</verbose>
<encoding>UTF-8</encoding>
<compilerArgs>
<compilerArg>-parameters</compilerArg>
</compilerArgs>
</configuration>
</plugin>
总结
以上就是全部内容,很明显通过注解来实现记录指定方法的返回值是比较简便和高效的,同时也添加了异步处理和补充的参数日志。后续计划再补充一篇内容,介绍ELK部署和接入使用,最好能尝试完成全docker部署。
感谢阅读!
网友评论