虽然容器都有记录接口的访问日志,但是毕竟不是自己程序记录的,由于各种原因,也会出现偏差,再者,容器记录的访问日志,不一定满足自己的需求,所以,这时就需要用到AOP,针对接口层做日志记录
具体代码如下:
package cn.xdf.xadd.aop;
import cn.xdf.xadd.context.UserInfoContext;
import cn.xdf.xadd.lang.util.Result;
import cn.xdf.xadd.util.RealIpUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Map;
/**
* AOP日志记录
*
* @author zhurunhua
* @date 2020-08-11 16:47
*/
@Aspect
@Component
@Slf4j
public class LogRecordAspect {
/**
* 定义切面表达式
*/
private static final String POINT = "execution(* cn.xdf.xadd.controller..*.*(..))";
/**
* 记录每次请求的开始时间
*/
private static final ThreadLocal<Long> START_TIME = new ThreadLocal<>();
/**
* 记录每次请求的随机序列号
*/
private static final ThreadLocal<String> SEQUENCE = new ThreadLocal<>();
@Pointcut(POINT)
public void executeService() {
//empty point cut
}
@Before("executeService()")
public void before(JoinPoint pjp) {
//请求信息
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes) ra;
HttpServletRequest httpServletRequest = sra.getRequest();
//获取客户端真实IP
String realIp = RealIpUtils.getClientIp(httpServletRequest);
//解析Http Header
Enumeration names = httpServletRequest.getHeaderNames();
StringBuilder header = new StringBuilder();
while (names.hasMoreElements()) {
String name = (String) names.nextElement();
header.append(name).append(":").append(httpServletRequest.getHeader(name)).append("|");
}
//解析参数
Object[] args = pjp.getArgs();
Map<String, String[]> parameterMap = httpServletRequest.getParameterMap();
StringBuilder stringBuilder = new StringBuilder();
parameterMap.forEach((k, v) -> stringBuilder.append(k).append(":").append(Arrays.deepToString(v)).append("|"));
//每次请求的序列号,避免方便匹配同一个请求以及对应的响应
SEQUENCE.set(getRandom());
//记录日志
log.info("trace log begin --> sequence::{},userName::{},RealIp::{},User-Agent::{},requestUri::{}" +
",header::{},requestMethod::{},request::{}, args::{}",
SEQUENCE.get(), UserInfoContext.getUserName(), realIp, httpServletRequest.getHeader("User-Agent"),
httpServletRequest.getRequestURI(), header.toString(), httpServletRequest.getMethod(),
stringBuilder.toString(), Arrays.deepToString(args));
//记录当前时间戳
START_TIME.set(System.currentTimeMillis());
}
@AfterReturning(pointcut = "executeService()", returning = "result")
public void after(Object result) {
try {
String res = "not standard response";
//自定义标准返回类 也可以直接输出 不做判断
if (result instanceof Result) {
res = result.toString();
}
//记录返回信息
log.info("trace log end --> sequence::{}, used::{}ms, responseBody::{}", SEQUENCE.get(), (System.currentTimeMillis() - START_TIME.get()), res);
} finally {
START_TIME.remove();
SEQUENCE.remove();
}
}
/**
* 生成8位随机字符串
*
* @return java.lang.String
* @date 2020-08-11 16:52
*/
private String getRandom() {
return RandomStringUtils.random(8, true, true);
}
}
特点:
- 主要记录了请求的header、参数、当前登陆用户的信息(可以去掉,用户相关的工具类没上传)
- 每次请求与响应有唯一的序列号对应
- 记录每次请求的耗时
- 可针对自定义响应记录标准响应日志,其他的异常响应不做记录
其他待日后完善。