前言
一般来说,Spring boot 项目的所有 Controller 调用,入参要统一进行 log 记录,也可以纪录方法花费的时间,定位问题,我们可以借助 aop 以及反射,将其统一处理,不需要每一个方法都写一遍 log。这可以说是 aop 最典型的实践。
在此基础之上,还可以再将其进一步简化,利用注解,只要加了注解的方法都可以进行 log,更加简洁。
注解
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogAnnotation {
/**
* 模块
*/
String title() default "";
/**
* 功能
*/
String action() default "";
// 各种需要标记,记录的信息
……
}
当 Controller 中的方法需要 log 的时候,加上注解,就会在受到调用的时候自动记录相关信息。
日志实体类
package com.example.ccf.vo;
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
public class SysLog {
private String method;
private String params;
private String operation;
private String result;
private long during;
}
根据自己的需要添加属性。
切片
package com.example.ccf.controller;
import com.alibaba.fastjson.JSON;
import com.example.ccf.annotion.LogAnnotation;
import com.example.ccf.vo.SysLog;
import lombok.extern.slf4j.Slf4j;
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.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
@Aspect
@Component
@Slf4j
public class SysLogAspect {
@Pointcut("@annotation(com.example.ccf.annotion.LogAnnotation)")
public void logPointCut(){}
/**
* 环绕增强,相当于MethodInterceptor
*/
@Around("logPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
long beginTime = System.currentTimeMillis();
SysLog sysLog = new SysLog();
//请求的方法名
String className = point.getTarget().getClass().getName();
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
String methodName = signature.getName();
sysLog.setMethod(className + "." + methodName + "()");
// 1. 方法调用前,打印请求参数
try {
// 请求的参数
Object[] args = point.getArgs();
// 去除掉不能序列化的参数
List<Object> arguments = new ArrayList<>();
String params = null;
if (args != null && args.length != 0) {
for (Object arg : args) {
if (arg instanceof ServletRequest || arg instanceof ServletResponse || arg instanceof MultipartFile) {
continue;
}
arguments.add(arg);
}
params = JSON.toJSONString(arguments);
}
sysLog.setParams(params);
log.info("调用方法: {}, 请求参数: {}", sysLog.getMethod(), sysLog.getParams());
} catch (Exception e) {
log.error("调用方法: " + sysLog.getMethod(), e);
}
// 实际调用方法, 如果发生异常则抛出
Object result = point.proceed();
// 2. 方法调用后,打印响应结果
try {
LogAnnotation logAnnotation = method.getAnnotation(LogAnnotation.class);
if (logAnnotation != null) {
//注解上的描述
sysLog.setOperation(logAnnotation.title() + "-" + logAnnotation.action());
}
String resultParam = JSON.toJSONString(result);
sysLog.setResult(resultParam);
//执行时长(毫秒)
long time = System.currentTimeMillis() - beginTime;
sysLog.setDuring(time);
log.info("调用方法: {}, 响应结果: {},响应时间{}ms",
sysLog.getMethod(), sysLog.getResult(), sysLog.getDuring());
} catch (Exception e) {
log.error("调用方法: " + sysLog.getMethod(), e);
}
return result;
}
}
具体的记录过程,通过反射获取 method 的相关信息,包括方法名称,参数,等等,在执行方法完成后记录响应结果与花费时间。
如果有其他需要也可以自行增加 log 内容其实有些HandlerMethodArgumentResolver
的工作也能放在这里做,也能实现但不是很合适,还是放在它应该在的地方吧。
也可以根据需要储存sysLog
对象。
文档信息
- 本文作者:nyaaar
- 本文链接:https://nyaaarlathotep.github.io/2022/03/13/%E5%88%A9%E7%94%A8%E6%B3%A8%E8%A7%A3%E8%BF%9B%E8%A1%8Caop-log/
- 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)