场景:看日志是后端常用的操作,但是日志过于多的时候,很难分清日志打印的是不是同一个调用里面的。所以在controller的方法的开始和结尾的地方,打印日志,并且打印入参和出参,这样就能够很好的分析日志的逻辑了。
由于每一个controller层的方法都需要打印进入和返回的日志,所以使用AOP的思想可以很好的解决,我们这边使用静态代理AspectJ。
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.9.5version>
dependency>
下面直接给出代码,修改一下Pointcut
的路径,便可以直接使用:
@Component
@Aspect
@Slf4j
@Order(-98)
public class LogAspect {
@Autowired
HttpServletRequest request;
/**
* 【注意】 修改为你自己项目中controller层路径
*/
@Pointcut("execution(public * com.qsm.xxx.xx.xxxx.controller..*.*(..))")
public void pointcut() {
}
@Around("pointcut()")
public Object handle(ProceedingJoinPoint joinPoint) throws Throwable {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
//请求controller名称,使用@ControllerMethodTitle注解
String controllerTitle = getControllerMethodTitle(joinPoint);
//方法路径
String methodName = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
//IP地址
String iP = getIp(request);
//请求入参
String requestParam = JSON.toJSONString(Arrays.stream(joinPoint.getArgs())
.filter(param -> !(param instanceof HttpServletRequest)
&& !(param instanceof HttpServletResponse)
&& !(param instanceof MultipartFile)
&& !(param instanceof MultipartFile[])
).collect(Collectors.toList()));
log.info("\n [Controller start], {}, methodName->{}, IP->{}, requestParam->{},", controllerTitle, methodName, iP, requestParam);
long begin = System.currentTimeMillis();
Object result = joinPoint.proceed();
log.info("\n [Controller end], {}, 耗时->{}ms, result->{}", controllerTitle, System.currentTimeMillis() - begin, JSONObject.toJSONString(result));
return result;
}
/**
* 获取Controller的方法名
*/
private String getControllerMethodTitle(ProceedingJoinPoint joinPoint) {
Method[] methods = joinPoint.getSignature().getDeclaringType().getMethods();
for (Method method : methods) {
if (StringUtils.equalsIgnoreCase(method.getName(), joinPoint.getSignature().getName())) {
ControllerMethodTitle annotation = method.getAnnotation(ControllerMethodTitle.class);
if (ObjectUtils.isNotEmpty(annotation)) {
return annotation.value();
}
}
}
return "该Controller的方法使用未使用注解@ControllerMethodTitle,请使用该注解说明方法作用";
}
/**
* 获取目标主机的ip
*
* @param request
* @return
*/
private String getIp(HttpServletRequest request) {
List<String> ipHeadList = Stream.of("X-Forwarded-For", "Proxy-Client-IP", "WL-Proxy-Client-IP", "HTTP_CLIENT_IP", "X-Real-IP").collect(Collectors.toList());
for (String ipHead : ipHeadList) {
if (checkIP(request.getHeader(ipHead))) {
return request.getHeader(ipHead).split(",")[0];
}
}
return "0:0:0:0:0:0:0:1".equals(request.getRemoteAddr()) ? "127.0.0.1" : request.getRemoteAddr();
}
/**
* 检查ip存在
*/
private boolean checkIP(String ip) {
return !(null == ip || 0 == ip.length() || "unknown".equalsIgnoreCase(ip));
}
}
还有一个自定义的注解
/**
* @author qsm
* @date 2020/9/3 14:20
* @description controller层的方法上,给该方法取一个名字。作用是给LogAspect打印进入方法退出方法的日志
*/
@Target({
ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ControllerMethodTitle {
String value() default "";
}
使用方法非常的简单,直接在controller的方法上面直接使用自定义的注解@ControllerMethodTitle
即可。
@RestController
@RequestMapping("/log")
@Slf4j
public class LogController {
@PostMapping("/qsm")
@ControllerMethodTitle("我是一个可爱的测试获取qsm的接口")
public Qsm2 http(@RequestBody @Valid Qsm2 qsm2, HttpServletRequest request) {
log.info("\n [处理中], 程序正在处理请求逻辑");
return qsm2;
}
}
打印的日志
2020-09-07 19:25:14.773 INFO 13276 --- [nio-8080-exec-3] c.j.i.q.d.web.commom.aspact.LogAspect :
[开始], 我是一个可爱的测试获取qsm的接口, methodName->com.xxx.ins.qsm.demo.web.controller.LogController.http, IP->127.0.0.1, reqParam->[{"name":"demoData","status":"123"}],
2020-09-07 19:25:14.783 INFO 13276 --- [nio-8080-exec-3] c.j.i.q.d.web.controller.LogController :
[处理中], 程序正在处理请求逻辑
2020-09-07 19:25:14.784 INFO 13276 --- [nio-8080-exec-3] c.j.i.q.d.web.commom.aspact.LogAspect :
[结束], 我是一个可爱的测试获取qsm的接口, 耗时->10ms, result->{"name":"demoData","status":"123"}
【完】
正在去BAT的路上修行