背景
在项目实际运作中,我们经常需要监听用户进行的操作并进行统计,比如调用了哪个接口,发送了什么请求参数,花了多久时间,这些接口操作记录不仅可以帮助我们时刻观察接口的性能,还能当系统发生错误时保留报错信息,以便于问题复现,因此实现接口操作记录的留存是具有必要性的。
但对于开发流程而言,如果为了留存记录而需要对项目代码进行调整甚至大改,难免有些繁琐且得不偿失。于是在本文中,采用了Aop面向切面编程技术来实现接口日志监听,在不侵入业务代码逻辑的前提下实现了接口使用信息的监听与统计。并使用了mybatis将记录存储至数据库中,提高了代码的可复用性。
效果展示
定义范围为controller包下的接口
当接口被调用时,数据库进行表的自创建并加入数据
代码复用流程
将代码放入项目文件夹中
然后在com.example.received.logStore.StoreLogAspect中修改监听包
实现过程
创建日志信息封装类StoreLog
import lombok.Data;
@Data
public class StoreLog {
// private String username;
// @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
private String startTime;
private Integer spendTime;
private String requestUrl;
private String requestParams;
private String methodName;
private Integer resultCode;
private Object resultInfo;
}
创建切面类StoreLogAspect
@Aspect
@Component
@Order(1)
@Slf4j
public class StoreLogAspect {
@Autowired
logService logService;
long startTime;
//定义切点表达式,指定通知功能被应用的范围
@Pointcut("execution(public * com.example.received.controller.*.*(..))")
public void webLog() {
}
@Before("webLog()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
startTime = System.currentTimeMillis();
}
/**value切入点位置
* returning 自定义的变量,标识目标方法的返回值,自定义变量名必须和通知方法的形参一样
* 特点:在目标方法之后执行的,能够获取到目标方法的返回值,可以根据这个返回值做不同的处理
*/
@AfterReturning(value = "webLog()", returning = "ret")
public void doAfterReturning(Object ret) throws Throwable {
log.info("--------");
long end = System.currentTimeMillis();
System.out.println("耗时:" + (end - startTime) + "ms");
}
//通知包裹了目标方法,在目标方法调用之前和之后执行自定义的行为
//ProceedingJoinPoint切入点可以获取切入点方法上的名字、参数、注解和对象
@Around("webLog()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable
{
//获取当前请求对象
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
//记录请求信息
StoreLog webLog = new StoreLog();
Object result = new Object();
//前面是前置通知,后面是后置通知
webLog.setResultInfo("");
try {
result = joinPoint.proceed();
Map res = (Map)result;
webLog.setResultCode((int)res.get("code"));
} catch (Throwable e) {
webLog.setResultCode(500);
webLog.setResultInfo(e.getCause().toString());
}
long endTime = System.currentTimeMillis();
webLog.setMethodName(request.getMethod());
webLog.setRequestParams(getParameter(joinPoint));
webLog.setSpendTime((int) (endTime - startTime));
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT+8:00"));
webLog.setStartTime( dateFormat.format(new Date(startTime)));
webLog.setRequestUrl(request.getRequestURL().toString());
log.info(webLog.toString());
if(!logService.existTable("storeLog"))
logService.createTable("storeLog");
if(!request.getMethod().equals("GET") || !webLog.getResultInfo().equals("")|| webLog.getSpendTime()>200)
logService.insertLog(webLog.getStartTime(), webLog.getSpendTime(), webLog.getRequestUrl(),webLog.getRequestParams(),
webLog.getMethodName(), webLog.getResultCode(), webLog.getResultInfo().toString());
return result;
}
private String getParameter(ProceedingJoinPoint joinPoint){
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Object[] argsName = methodSignature.getParameterNames();
Object[] args = joinPoint.getArgs();
String res = "";
for (int i = 0; i < args.length; i++)
log.info(String.valueOf(args[i]));
for (int i = 0; i < argsName.length; i++)
{
log.info(String.valueOf(argsName[i]));
res += String.valueOf(argsName[i]);
res += ": ";
if(args[i]!=null){
res += String.valueOf(args[i]);
}
res += "; ";
}
return res;
}
}
将日志记录存入数据库
@Service
public interface logService {
Boolean insertLog(String startTime, Integer spendTime, String requestUrl,String requestParams,
String methodName, Integer resultCode,String resultInfo);
Boolean existTable(String tableName);
Boolean createTable(String tableName);
}
@Service
public class logImpl implements logService {
@Autowired
logDao dao;
@Override
public Boolean insertLog(String startTime, Integer spendTime, String requestUrl,String requestParams,
String methodName, Integer resultCode,String resultInfo) {
return dao.insertBoard(startTime, spendTime, requestUrl,requestParams, methodName, resultCode,resultInfo);
}
@Override
public Boolean existTable(String tableName){
return dao.existsTable(tableName);
};
@Override
public Boolean createTable(String tableName){
return dao.createTable(tableName);
};
}
@Mapper
public interface logDao {
@Insert("insert into storeLog (startTime,spendTime,requestUrl,requestParams,methodName,resultCode,resultInfo)" +
" values(#{startTime},#{spendTime},#{requestUrl},#{requestParams},#{methodName},#{resultCode},#{resultInfo})")
Boolean insertBoard(String startTime, Integer spendTime, String requestUrl,String requestParams,
String methodName, Integer resultCode,String resultInfo);
@Select(" SELECT COUNT(*) as count FROM information_schema.TABLES WHERE table_name = #{tableName}")
Boolean existsTable(@Param("tableName") String tableName);
@Update(" DROP TABLE IF EXISTS `${tableName}`;\n" +
"CREATE TABLE `${tableName}` (\n" +
" `eventId` bigint NOT NULL AUTO_INCREMENT,\n" +
" `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,\n" +
" `startTime` datetime NULL DEFAULT NULL,\n" +
" `spendTime` int NULL DEFAULT NULL,\n" +
" `requestUrl` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,\n" +
" `requestParams` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,\n" +
" `methodName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,\n" +
" `resultCode` int NULL DEFAULT NULL,\n" +
" `resultInfo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,\n" +
" PRIMARY KEY (`eventId`) USING BTREE\n" +
") ENGINE = InnoDB AUTO_INCREMENT = 82 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;")
Boolean createTable(@Param("tableName") String tableName);
}