springboot uses aop to implement system log and operation log records

1. Purpose

Record abnormal information and specific operation logs to the database through aop and annotation.

2. Introduce dependencies

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

3. Custom annotations

4. Aspect defines the entry point and entry processing

@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;
    }

Here we talk about Before will be called before the method is executed, AfterThrowing will be called when an exception is thrown. The most important thing is that if you catch the exception with try catch in your method, you will not go AfterThrowing, unless you manually use throw in the catch to restart Throwing an exception , this problem has bothered me for a long time.

Then I added an onlyErr field in the comment. When it is true, it will only record when the method has an exception. When it is false, it will not only record the exception, but also record the custom operation log, so that I only want to record the exception but don’t want to record it. Just add this field to the operation log.

5. Start queue processing, and periodically insert logs into the database in batches.

@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. Tools

@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. This is basically completed, the usage is as follows:

    @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;
		}
	}

Add @OperationAnnotation annotations where you want to log, and that's it! ! !

 

The effect is as follows

Guess you like

Origin blog.csdn.net/qq_36961530/article/details/99674179