文章目录
1 摘要
关于使用Spring AOP 做日志记录是java 后台一种常用的日志记录方案,作者也参考了网上的很多方案,但是在实际的操作中还是遇到了一些问题:
- POST 方式请求不能同时获取到 body 里边的 json 格式参数和常规的 parameter 中参数
- 文件上传与下载不是抛错就是把整个文件都打印出来了
针对以上情况,作者对 AOP 的日志记录方式进行了整理
2 核心代码
切点类代码:
com.ljq.demo.springboot.web.acpect.LogAspect
类
package com.ljq.demo.springboot.web.acpect;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.aspectj.lang.ProceedingJoinPoint;
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 java.lang.reflect.Method;
import java.util.Map;
import java.util.UUID;
/**
* @Description: 日志记录切点
* @Author: junqiang.lu
* @Date: 2018/11/1
*/
@Aspect
@Component
public class LogAspect {
private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);
/**
* controller 层切点
*/
@Pointcut("execution(* com.ljq.demo.springboot.web.controller..*.*(..))")
public void controllerPointcut() {
}
/**
* controller 层出入参日志记录
*
* @param joinPoint 切点
* @return
*/
@Around(value = "controllerPointcut()")
public Object controllerLogAround(ProceedingJoinPoint joinPoint) throws Throwable {
/**
* 获取 request 中包含的请求参数
*/
String uuid = UUID.randomUUID().toString();
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
/**
* 获取切点请求参数(class,method)
*/
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
String params = getRequestParams(request, joinPoint);
/**
* 入参日志
*/
logger.info("[AOP-LOG-START]\n\trequestMark: {}\n\trequestIP: {}\n\tcontentType:{}\n\trequestUrl: {}\n\t" +
"requestMethod: {}\n\trequestParams: {}\n\ttargetClassAndMethod: {}#{}", uuid, request.getRemoteAddr(),
request.getHeader("Content-Type"),request.getRequestURL(), request.getMethod(), params,
method.getDeclaringClass().getName(), method.getName());
/**
* 出参日志
*/
Object result = joinPoint.proceed();
logger.info("[AOP-LOG-END]\n\t{}", result);
return result;
}
/**
* 获取请求参数
*
* @param request
* @param joinPoint
* @return
*/
private String getRequestParams(HttpServletRequest request, ProceedingJoinPoint joinPoint) throws JsonProcessingException {
StringBuilder params = new StringBuilder();
if ("GET".equalsIgnoreCase(request.getMethod())) {
params.append(request.getQueryString());
}
if ("POST".equalsIgnoreCase(request.getMethod())) {
/**
* 获取 request parameter 中的参数
*/
Map<String,String[]> parameterMap = request.getParameterMap();
if (parameterMap != null && !parameterMap.isEmpty()) {
for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
params.append(entry.getKey() + " = " + entry.getValue()[0] + ";");
}
}
/**
* 获取非 request parameter 中的参数
*/
Object[] objects = joinPoint.getArgs();
for (Object arg : objects) {
if (arg == null) {
break;
}
String className = arg.getClass().getName().toLowerCase();
String contentType = request.getHeader("Content-Type");
/**
* 文件参数,上传文件信息
*/
if (className.contains("MultipartFile".toLowerCase())) {
MultipartFile multipartFile = (MultipartFile) arg;
params.append("fileSize = " + multipartFile.getSize() + ";");
params.append("fileContentType = " + multipartFile.getContentType() + ";");
params.append("fieldName = " + multipartFile.getName() + ";");
params.append("fileOriginalName = " + multipartFile.getOriginalFilename() + ";");
}
if (contentType != null && contentType.contains("application/json")){
/**
* json 参数
*/
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
params.append(mapper.writeValueAsString(arg));
}
}
}
return params.toString();
}
}
3 注意事项
说明:
Object[] objects = joinPoint.getArgs();
这种方式获取的参数只能获取到 POST
请求的 body
中的参数,不能获取到请求头中使用 key=value 方式拼接的参数
Map<String,String[]> parameterMap = request.getParameterMap();
这种方式能够获取到 GET/POST
请求中 paramter
中的参数,即在请求头中使用 key=value方式拼接的参数,不能获取到body
中的参数
实际的项目中会有这样的场景: 使用 POST
请求方式,但是值却用 key=value 的方式传递,而非都在 body
中
针对文件 上传/下载 的日志记录,如果不做单独处理,会将整个文件输出到日志,这样会导致日志文件异常巨大
4 测试效果
代码行不行,直接看效果
4.1 get 请求
请求参数:
日志记录:
4.2 POST请求,json格式
请求参数:
日志记录:
4.3 post方式请求,key=value参数
请求参数:
日志记录:
4.4 POST方式请求,@PathVariable
与 @RequestBody
混合
请求参数:
日志记录:
4.5 POST 文件上传
请求参数:
日志记录:
4.6 文件下载
请求参数:
日志记录:
至此,项目中常用的请求方式都已经测试过了,文章最初提到的两个问题也得到了解决。
5 Github 源码
个人公众号:404Code,分享半个互联网人的技术与思考,感兴趣的可以关注.