springboot 利用aop实现系统日志和操作日志记录

1.目的

通过aop及注解的方式,记录异常信息和特定的操作日志到数据库。

2.引入依赖

   <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
   </dependency>

3.自定义注解

4.aspect定义切入点和切入处理

@Aspect
@Component
@EnableAsync
public class SystemLogAspect extends EsgBaseController {
    private static Log log = LogFactory.getLog(SystemLogAspect.class);

    @Autowired
    SysLogService sysLogService;

    @Autowired
    LogQueue logQueue;

    @Pointcut("@annotation(com.mixislink.common.OperationAnnotation)")
    public void logPointCut() {}

    //@AfterRunning: 返回通知 rsult为返回内容
    @Before(value="logPointCut()")
    public void before(JoinPoint joinPoint){
        log.info("调用了前置通知");
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        handle(joinPoint,null,request);

    }
    //@AfterThrowing: 异常通知
    @AfterThrowing(pointcut="logPointCut()",throwing="e")
    public void afterReturningMethod(JoinPoint joinPoint, Exception e) {
        log.info("调用了异常通知");
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        handle(joinPoint,e,request);

    }

//    @Async("asyncServiceExecutor")
    @Async
    public void handle(final JoinPoint joinPoint,final Exception e,final HttpServletRequest request){
        SysLog sysLog = new SysLog();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        OperationAnnotation op = method.getAnnotation(OperationAnnotation.class);
        if (op != null){
            sysLog.setRemark(op.remark());
            sysLog.setSystype(op.sysType());
            sysLog.setOpType(op.opType());
        }
        //请求的 类名、方法名
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = signature.getName();
        String ip = this.getIpAddress(request);
        String url = request.getRequestURI();
        String param = getParams(joinPoint);

        sysLog.setRequestUrl(url + "&"+ param);
        sysLog.setMethod(className + "." + methodName + "()");
        sysLog.setIpAddress(ip);

        try {
            if (e != null){
                sysLog.setRemark(e.getMessage());
                sysLog.setLogType(1);
                logQueue.add(sysLog);
            }else {
                if (!op.onlyErr()){
                    sysLog.setLogType(0);
                    logQueue.add(sysLog);
                }
            }
        } catch (Exception ex) {
            log.error("handle systemLog 出现异常",ex);
        }
    }

    public String getIpAddress(HttpServletRequest request){
        String ipAddress = request.getHeader("x-forwarded-for");
        if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("Proxy-Client-IP");
        }
        if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("WL-Proxy-Client-IP");
        }
        if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getRemoteAddr();
            if(ipAddress.equals("127.0.0.1") || ipAddress.equals("0:0:0:0:0:0:0:1")){
                //根据网卡取本机配置的IP
                InetAddress inet=null;
                try {
                    inet = InetAddress.getLocalHost();
                } catch (UnknownHostException e) {
                    e.printStackTrace();
                }
                ipAddress= inet.getHostAddress();
            }
        }
        //对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
        if(ipAddress!=null && ipAddress.length()>15){ //"***.***.***.***".length() = 15
            if(ipAddress.indexOf(",")>0){
                ipAddress = ipAddress.substring(0,ipAddress.indexOf(","));
            }
        }
        return ipAddress;
    }

    public String getParams(JoinPoint joinPoint){
        ParameterRequestWrapper requestWrapper = (ParameterRequestWrapper) joinPoint.getArgs()[0];
        Map map = requestWrapper.getParameterMap();
        return map != null ? JSON.toJSONString(map): null;
    }

这里讲一下Before会在方法执行前调用,AfterThrowing会抛异常时调用,最最最重要的是你的方法里如果用try catch捕获了异常,则不会走AfterThrowing,除非在catch里手动用throw重新抛出异常,这个问题之前困扰我很久。

然后我再注解里多加了一个onlyErr字段,为true时,只在方法出异常时记录,为false时,不仅会记录异常,还会记录自定义的操作日志,这样在只想记录异常,不想记录操作日志的地方加上这个字段就可以了。

5.启动queue处理,定时将日志批量插入到数据库。

@Slf4j
@Component
public class LogTask {

    @Autowired
    LogQueue logQueue;

    @Autowired
    SysLogService sysLogService;

    private volatile List<SysLog> operLogs = Collections.synchronizedList(new ArrayList<>());

    @Scheduled(fixedDelay = 500)
    public void logFixDelay(){
        //获取日志信息
        while (true){
            SysLog operLog = logQueue.poll();
            if(null==operLog){
                break;
            }

            operLogs.add(operLog);
        }
        if(operLogs.size()>0){
            try{
                log.info("######################批量添加系统日志"+operLogs.size());
                sysLogService.insertAll(operLogs);
            }catch (Exception e){
                log.error("批量添加系统日志异常:",e);
                operLogs.clear();
            }
            operLogs.clear();
        }
    }
}

6.工具类

@Component
public class LogQueue {

    //LinkedList实现了Queue接口,可以用LinkedList做一个队列,这里使用阻塞队列BlockingQueue
    private  volatile Queue<SysLog> dataQueue = new LinkedBlockingQueue<>();
    //添加日志信息
    public void add(SysLog logininfor) {
        dataQueue.add(logininfor);
    }
    //获取日志信息,用于插入到数据库中。
    public SysLog poll() { return dataQueue.poll(); }
}

7.到这里基本完成了,用法如下:

    @RequiresPermissions("sys:sysLog:list")
    @OperationAnnotation(remark = "分页查询SysLog信息",sysType = 1,opType = 4)
	@RequestMapping(value = "/selectAll",method = RequestMethod.GET)
	public HttpEntity selectAll(HttpServletRequest request)  throws Exception {
		EngineParameter ep = initParameter(request);
		try {
			return sysLogService.selectAll(ep);
		} catch (Exception e) {
			log.error("SysLog",e);
			throw e;
		}
	}

在要记录日志的地方加上@OperationAnnotation注解,就可以啦!!!

效果如下

猜你喜欢

转载自blog.csdn.net/qq_36961530/article/details/99674179