使用自定义注解实现系统日志记录
本文的方案背景:系统记录所有的敏感操作日志(增删改)
方案技术:无线程池、无监听订阅、无多数据源、无错误消息监控推送。
自定义注解
@Target({ElementType.PARAMETER, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ULog { //功能描述 String option(); //是否缓存请求参数 boolean isSaveRequestData() default true; //是否缓存响应结果 boolean isSaveResponseData() default false; }
Aspect
@Slf4j @Aspect @Component public class ULogAspect { @Resource SystemLogService systemLogService; @Resource UserInfoCacheHandler cacheHandler; @Pointcut("@annotation(com.hd.uws.common.annotation.ULog)") public void logPointCut() { //do nothing.... } @Pointcut("@annotation(com.hd.uws.common.annotation.ULog)") public void exceptionPointCut() { //do nothing.... } /** * 方法执行的前后切面处理 * * @param point 切面 * @return java.lang.Object * @author Jackpot * @date 2022/5/26 09:12 */ @Around("logPointCut()") public Object aroundMethod(ProceedingJoinPoint point) throws Throwable { //请求开始时间 long beginTime = System.currentTimeMillis(); //处理方法执行日志切面(不要捕获异常抛出 由下面的异常方法处理) Object result = point.proceed(); //执行时长(毫秒) long endTime = System.currentTimeMillis(); SavaLogBO logBO = new SavaLogBO(); logBO.setBeginTime(LocalDateTime.now()); logBO.setEndTime(LocalDateTime.now()); logBO.setRuntime(endTime - beginTime); logBO.setJoinPoint(point); logBO.setResult(result); saveLog(logBO); //切面返回响应 return result; } /** * 异常返回通知,用于拦截异常日志信息 连接点抛出异常后执行 * * @param point 切面 * @param e 异常 * @author Jackpot * @date 2022/5/26 09:12 */ @AfterThrowing(pointcut = "exceptionPointCut()", throwing = "e") public void afterThrowing(JoinPoint point, Throwable e) { //请求开始时间 long beginTime = System.currentTimeMillis(); //获取当前请求的错误日志信息 String exceptionMsg = stackTraceToString(e.getClass().getName(), e.getMessage(), e.getStackTrace()); long endTime = System.currentTimeMillis(); long runtime = endTime - beginTime; //保存日志 SavaLogBO logBO = new SavaLogBO(); logBO.setBeginTime(LocalDateTime.now()); logBO.setEndTime(LocalDateTime.now()); logBO.setRuntime(runtime); logBO.setJoinPoint(point); logBO.setExceptionMsg(exceptionMsg); saveLog(logBO); } /** * 记录当前错误请求日志信息 * * @param exceptionName 异常名称 * @param exceptionMessage 异常内容主体 * @param elements 元素 * @return java.lang.String * @author Jackpot * @date 2022/5/26 09:13 */ public String stackTraceToString(String exceptionName, String exceptionMessage, StackTraceElement[] elements) { //定义一个缓存 StringBuilder strbuff = new StringBuilder(); //拼接错误信息 for (StackTraceElement stet : elements) { //处理 strbuff.append(stet).append("\n"); } //返回 return exceptionName + ":" + exceptionMessage + "\n\t" + strbuff; } /** * 日志入库 * * @param logBO 入库bo * @author Jackpot * @date 2022/5/26 10:32 */ private void saveLog(SavaLogBO logBO) { //获取当前注解对象 ULog uLogAnnotation = getAnnotation(logBO.getJoinPoint()); //获取自定义的注解描述 if (uLogAnnotation != null) { //定义一个日志对象缓存 SystemLog sysLog = new SystemLog(); //获取当前的请求对象 HttpServletRequest currentRequest = getCurrentRequest(sysLog); //获取自定义的操作 String option = uLogAnnotation.option(); //注解上的描述 sysLog.setOption(Strings.isNullOrEmpty(option) ? "操作" : option); sysLog.setBeginTime(logBO.getBeginTime()); sysLog.setEndTime(logBO.getEndTime()); //缓存当前请求的耗时 sysLog.setRuntime(logBO.getRuntime()); //获取请求的url地址 sysLog.setUrl(currentRequest.getRequestURI()); //返回发出请求的IP地址 sysLog.setIp(IpUtils.getIpAddress(currentRequest)); //请求参数处理 formatParamEvent(logBO.getJoinPoint(), uLogAnnotation, sysLog); //判断当前是否有指定错误信息 if (Strings.isNullOrEmpty(logBO.getExceptionMsg())) { //当前没有指定的错误 表示请求正常 formatResultEvent(uLogAnnotation, logBO.getResult(), sysLog); } else { //当前没有指定的错误 表示请求系统异常 sysLog.setCode(ResCode.SYSTEM_UNKNOWN_ERROR.getCode()); //系统异常错误信息 sysLog.setException(logBO.getExceptionMsg()); } //异常捕获处理 try { // 保存系统日志 systemLogService.add(sysLog); } catch (Exception e) { e.printStackTrace(); } } } /** * 获取对应的注解对象 * * @param joinPoint 切点 * @return com.hd.uws.annotation.ULog * @author Jackpot * @date 2022/5/26 10:32 */ private ULog getAnnotation(JoinPoint joinPoint) { //获取当前请求方法对象 MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); //返回对应的注解对象 return method.getAnnotation(ULog.class); } /** * 获取当前的请求对象(并且缓存当前用户信息) * * @param sysLog 实体 * @return javax.servlet.http.HttpServletRequest * @author Jackpot * @date 2022/5/26 10:33 */ private HttpServletRequest getCurrentRequest(SystemLog sysLog) { // 获取request RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); //从获取RequestAttributes中获取HttpServletRequest的信息 assert requestAttributes != null; HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST); //获取请求中的cookie集合 assert request != null; sysLog.setUserName(cacheHandler.getUserInfoCache(request).getUsername()); //返回 return request; } /** * 格式处理请求缓存和处理 * * @param joinPoint 切面 * @param uLogAnnotation 注解 * @param sysLog 实体 * @author Jackpot * @date 2022/5/26 10:33 */ private void formatParamEvent(JoinPoint joinPoint, ULog uLogAnnotation, SystemLog sysLog) { //判断当前是否应该缓存请求参数 if (uLogAnnotation.isSaveRequestData()) { //获取当前请求参数对象 try { //获取参数对象 Object[] args = joinPoint.getArgs(); //参数缓存 formatRequestParam(args, sysLog); } catch (Exception e) { //请求参数处理异常 log.error("请求参数处理异常", e); } } } /** * 格式化请求参数 * * @param args 参数 * @param sysLog 实体 * @author Jackpot * @date 2022/5/26 10:35 */ private void formatRequestParam(Object[] args, SystemLog sysLog) { //定义一个集合缓存 JSONArray paramArray = new JSONArray(); //遍历获取每个参数对象 for (Object arg : args) { //判断当前参数中是否存在无法转换的对象 try { //判断是否可以转换 Object value = JSON.toJSON(arg); //缓存 paramArray.add(value); } catch (Exception e) { //参数无法转换的不处理 } } //缓存对应的请求参数 sysLog.setParam(paramArray); } /** * 格式处理响应参数的缓存和处理 * * @param uLogAnnotation 切面 * @param result 注解 * @param sysLog 实体 * @author Jackpot * @date 2022/5/26 10:34 */ private void formatResultEvent(ULog uLogAnnotation, Object result, SystemLog sysLog) { //判断当前的响应参数是否可以转换为json格式 try { //请求结果 JSONObject resultJson = JSON.parseObject(JSON.toJSONString(result)); //获取响应状态 Integer resultCode = resultJson.getInteger("code"); //缓存本次请求的code sysLog.setCode(resultCode); //如果响应不成功 缓存对应的错误信息 if (!CommonConstants.SYSTEM_SUCCESS_CODE.equals(resultCode)) { String msg = resultJson.getString("msg"); String tip = resultJson.getString("tip"); //缓存对应的错误信息 sysLog.setException(!Strings.isNullOrEmpty(msg) ? msg : !Strings.isNullOrEmpty(tip) ? tip : null); } //判断是否应该缓存响应 if (uLogAnnotation.isSaveResponseData()) { //请求方法的响应结果 sysLog.setResponseResult(resultJson); } } catch (Exception e) { //请求参数处理异常 log.error("请求响应处理异常", e); } } @Data static class SavaLogBO { private JoinPoint joinPoint; private LocalDateTime beginTime; private LocalDateTime endTime; private Long runtime; private Object result; private String exceptionMsg; } }