持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情
使用@Aspect注解进行接口日志统一记录
前文
本文内容为采用@Aspect注解进行日志记录的一次实现方案,以及对于其中一些出现问题的记录。
方案实现逻辑
本方案的实现逻辑主要是采用@Aspect注解在各服务中进行切面处理。需要进行记录的接口采用特定的注解进行标记,在切面类中对于该类接口进行切面编程。而在切点的处理逻辑中,在切点中获取请求所携带的信息。由于存在网关服务,因此ip不能通过切面中直接获取,而是选择在网关中进行预先的处理。在网关中获取来自客户端真实的ip,并将ip存储到后续请求的header中。实际的切点处则采用请求中所携带的ip作为真实的请求ip。如果直接在各服务进行ip的获取,会出现获取的ip是网关ip而不是实际请求ip的问题。获取到日志数据后,将其通过kafka消息队列写入,又专门的日志处理服务进行后续的操作处理。
方案代码
@Aspect
@Component
public class ReqAspect {
private static Logger logger = LoggerFactory.getLogger(ReqAspect.class);
private String requestHeadClientIp = "request_header_client_ip";
@Value("${spring.kafka.api_log_topic}")
private String logTopic;
@Pointcut("@annotation(cc.crrc.business.annotation.ApiReqAspect)")
public void cutPoint(){
}
@Around(value = "cutPoint() && @annotation(apiReqAspect)",argNames = "proceedingJoinPoint,apiReqAspect")
public Object processAround(ProceedingJoinPoint proceedingJoinPoint, ApiReqAspect apiReqAspect) throws Exception {
Object ret = null;
ApiLogPO apiLogPO = new ApiLogPO();
String ip = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getHeader(requestHeadClientIp);
long start = System.currentTimeMillis();
long end = System.currentTimeMillis();
int code;
try {
ret = proceedingJoinPoint.proceed();
end = System.currentTimeMillis();
if(ret instanceof Result){
if(((Result)ret).isSuccess()){
code = HttpStatus.OK.value();
}else{
code = Integer.parseInt((((Result)ret).getError()).getCode());
}
}else if(ret instanceof LinkedHashMap){
LinkedHashMap<String,Object> data = (LinkedHashMap<String,Object>)ret;
if((Boolean) data.get("success") == false){
code = Integer.parseInt(String.valueOf(((LinkedHashMap<String,Object>)data.get("error")).get("code")));
}else{
code = HttpStatus.OK.value();
}
}else{
code = HttpStatus.OK.value();
}
} catch (Exception e) {
end = System.currentTimeMillis();
if(e instanceof RestApiException){
code = Integer.valueOf(((RestApiException) e).getErrorCode());
}else{
code = HttpStatus.INTERNAL_SERVER_ERROR.value();
}
apiLogPO.setDuration(end - start);
apiLogPO.setStatus(code);
KafkaUtil.sendMessageSync(logTopic, JsonUtils.serialize(apiLogPO));
throw e;
} catch (Throwable throwable) {
code = HttpStatus.INTERNAL_SERVER_ERROR.value();
throwable.printStackTrace();
}
apiLogPO.setDuration(end -start);
apiLogPO.setStatus(code);
try {
KafkaUtil.sendMessageSync(logTopic, JsonUtils.serialize(apiLogPO));
}catch (Exception e){
}
return ret;
}
}
由于篇幅所限,部分代码存在删减。
注意点
需要注意的是,切点中proceedingJoinPoint.proceed()属于后续逻辑的实际执行,不可反复调用。如果需要对结果进行判断,要实现将其存储于变量中操作。
后记
- 千古兴亡多少事?悠悠。不尽长江滚滚流。