雖然容器都有記錄接口的通路日志,但是畢竟不是自己程式記錄的,由于各種原因,也會出現偏差,再者,容器記錄的通路日志,不一定滿足自己的需求,是以,這時就需要用到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、參數、目前登陸使用者的資訊(可以去掉,使用者相關的工具類沒上傳)
- 每次請求與響應有唯一的序列号對應
- 記錄每次請求的耗時
- 可針對自定義響應記錄标準響應日志,其他的異常響應不做記錄
其他待日後完善。