一、背景:
1.打印请求和响应结果
2.计算出请求耗时
3.不打印流
4.指定切面
二、代码
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.time.LocalDateTime;
import java.util.Enumeration;
/**
* 请求日志切面
*
* @author
*/
@Aspect
@Component
public class WebLogAspect {
private final static Logger logger = LoggerFactory.getLogger(WebLogAspect.class);
/**
* 以 controller 包下定义的所有请求为切入点
*/
@Pointcut("(execution(public * com.jiesz.mall.api..*.*(..)))&& (@within(org.springframework.web.bind.annotation.RestController))")
public void webLog() {
}
/**
* 环绕
*
* @param proceedingJoinPoint
* @return
* @throws Throwable
*/
@Around("webLog()")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// 开始打印请求日志
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
Signature signature = proceedingJoinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
//2.最关键的一步:通过这获取到方法的所有参数名称的字符串数组
String[] parameterNames = methodSignature.getParameterNames();
Object[] oArr = proceedingJoinPoint.getArgs();
JSONObject param = new JSONObject();
if (oArr != null && oArr.length > 0) {
for (int i = 0; i < oArr.length; i++) {
if (oArr[i] instanceof MultipartFile || oArr[i] instanceof File) {
oArr[i] = "上传文件不打印";
}
if (oArr[i] instanceof HttpServletResponse) {
oArr[i] = "响应头不打印";
}
if (oArr[i] instanceof HttpServletRequest) {
oArr[i] = "请求头不打印";
}
if (oArr[i] != null) {
param.put(parameterNames[i], oArr[i]);
}
}
}
// 记录下请求内容
StringBuilder sb = new StringBuilder();
sb.append("\n========================================== Start ==========================================\n")
.append("Request Time :").append(LocalDateTime.now().toString()).append("\n")
.append("URL :").append(request.getMethod()).append(" ").append(request.getRequestURL()).append("\n");
try {
sb.append("Request Args : ").append(param.toJSONString()).append("\n");
} catch (Exception e) {
logger.error("打印请求日志报错,存在无法json化的对象", e);
}
if (logger.isDebugEnabled()) {
sb.append("Request Header :").append(getHeader(request)).append("\n")
.append("Class Method : ").append(proceedingJoinPoint.getSignature().getDeclaringTypeName()).append(".").append(proceedingJoinPoint.getSignature().getName()).append("\n")
.append("IP : ").append(getIpAddress(request)).append("\n");
}
long startTime = System.currentTimeMillis();
Object result = proceedingJoinPoint.proceed();
String resultJson = "";
if (result instanceof byte[]) {
resultJson = "方法的结果是文件流,不输出";
} else if (result != null) {
try {
resultJson = JSON.toJSONString(result);
if (resultJson.length() > 2000) {
resultJson = resultJson.substring(0, 2000) + "...结果过长...";
}
} catch (Exception e) {
logger.error("打印结果日志报错,存在无法json化的对象", e);
}
}
sb.append("Response : ").append(resultJson).append("\n")
.append("Time-Consuming : ").append(System.currentTimeMillis() - startTime).append("ms\n")
.append("=========================================== End ===========================================\n");
// 执行耗时
if (logger.isDebugEnabled()) {
logger.debug(sb.toString());
} else {
logger.info(sb.toString());
}
return result;
}
private String getHeader(HttpServletRequest request) {
JSONObject header = new JSONObject();
Enumeration<String> names = request.getHeaderNames();
while (names.hasMoreElements()) {
String name = names.nextElement();
header.put(name, request.getHeader(name));
}
return header.toJSONString();
}
/**
* 获取用户真实IP地址,不使用request.getRemoteAddr()的原因是有可能用户使用了代理软件方式避免真实IP地址,
* 可是,如果通过了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP值
*
* @return ip
*/
public static String getIpAddress(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
// 多次反向代理后会有多个ip值,第一个ip才是真实ip
if (ip.indexOf(",") != -1) {
ip = ip.split(",")[0];
}
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Real-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
三、打印调用第三方接口得请求参数和返回结果
@Component
public class LogInterceptor implements ClientHttpRequestInterceptor {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
traceRequest(request, body);
ClientHttpResponse response = execution.execute(request, body);
traceResponse(response);
return response;
}
/**
* 打印请求信息
*
* @param request
* @param body
* @throws IOException
*/
private void traceRequest(HttpRequest request, byte[] body) throws IOException {
logger.info("》》URI : {}", request.getMethod().toString() + " " + request.getURI().toString());
request.getHeaders().keySet().stream().forEach((key) -> {
logger.debug("》》Header : {}", key + ":" + request.getHeaders().get(key).toString());
});
logger.info("》》Request body: {}", new String(body, StandardCharsets.UTF_8));
}
/**
* 响应数据
*
* @param response
* @throws IOException
*/
private void traceResponse(ClientHttpResponse response) throws IOException {
logger.info("《《 Status : {}", response.getStatusCode());
if (logger.isDebugEnabled()) {
response.getHeaders().keySet().stream().forEach((key) -> {
logger.debug("《《 Header : {}", key + ":" + response.getHeaders().get(key).toString());
});
StringBuilder inputStringBuilder = new StringBuilder();
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(response.getBody(), StandardCharsets.UTF_8))) {
String line = bufferedReader.readLine();
while (line != null) {
inputStringBuilder.append(line);
inputStringBuilder.append('\n');
line = bufferedReader.readLine();
}
}
String resp = inputStringBuilder.length() > 1000 ? inputStringBuilder.substring(0, 1000) : inputStringBuilder.toString();
logger.debug("《《 Response body: {}", resp);
}
}
public Logger getLogger() {
return logger;
}
}
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(LogInterceptor logInterceptor) {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setConnectTimeout(30000);
requestFactory.setReadTimeout(30000);
RestTemplate restTemplate;
if (logInterceptor.getLogger().isDebugEnabled()) {
// BufferingClientHttpRequestFactory 多次读取 response body
ClientHttpRequestFactory factory = new BufferingClientHttpRequestFactory(requestFactory);
restTemplate = new RestTemplate(factory);
} else {
restTemplate = new RestTemplate(requestFactory);
}
restTemplate.setInterceptors(Collections.singletonList(logInterceptor));
return restTemplate;
}
}
参考资料:
https://www.cnblogs.com/quanxiaoha/p/10414681.html AOP 切面统一处理请求日志
https://blog.csdn.net/sky_jiangcheng/article/details/81564149 使用aop写一个简单日志切片
https://blog.csdn.net/qq525099302/article/details/53996344 Spring AOP 中pointcut expression表达式解析及配置