springboot+自定义注解+aop+全局异常处理实现简单的日志监控系统

前言

先上两张图大家体验一下
在这里插入图片描述
在这里插入图片描述
看完是不是感觉自己心中想的不太一样,这里所统计的数据确实不够多,但是对于一个单结构工程项目来说完全已经够了,可能还差了报警功能,比如异常的时候发送报警邮件或者短信之类的,这些在搞懂触发点之后不是简简单单?
这里直介绍数据源的获取,数据这块的可视化就不用我说了吧

介绍

1.先说一下为什么会采用AOP,其实拦截器也可以实现啊,主要是因为AOP的优先级比拦截器低,在库表建好后,一份文件即插即用,不用考虑其他拦截器处理问题
2.数据源:这边统计的都是常规数据,一个日志主表一个错误日志附表
sys_log_info主表如下:
在这里插入图片描述
sys_info_error如下:
在这里插入图片描述

代码实现

WebLogControl如下:

@Aspect
@Component
public class WebLogControl {
    private static final Logger logger = LoggerFactory.getLogger(WebLogControl.class);

    @Autowired
    private SysLogInfoMapper sysLogInfoMapper;
    @Autowired
    private SysLogErrorMapper sysLogErrorMapper;

    @Pointcut("execution(public * com.inet.fitTrack.controller.*.*(..))")
    public void webLog() {

    }

    @Around("webLog()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        //开始时间
        long start = System.currentTimeMillis();
        //执行方法
        Object result = point.proceed();
        long end = System.currentTimeMillis();
        logger.info(" SPEND TIME : " + (end-start)+"ms");
        SysLogInfoWithBLOBs sysLogInfo=SysLogInfoWithBLOBs.getWebContext();
        sysLogInfo.setRequestTime(Integer.valueOf(String.valueOf((end-start))));
        sysLogInfoMapper.updateByPrimaryKeySelective(sysLogInfo);
        return result;
    }

    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        // 1.接收到请求,记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();


        MethodSignature signature = (MethodSignature)joinPoint.getSignature();
//        2.获取请求的方法所在类名
        String beanName = joinPoint.getSignature().getDeclaringTypeName(); //方法所在的类名
        //3.获取请求的方法
        Method method = signature.getMethod();
        //4.获取注解参数值
        LogAnnotation annotation = signature.getMethod().getAnnotation(LogAnnotation.class);
        // 打印日志
        logger.info("  TypeName  : " + beanName);
        logger.info(" MethodName : " + method.getName());
        logger.info("  U  R  L   : " + request.getServletPath());
        logger.info("HTTP_METHOD : " + request.getMethod());
        logger.info("    I P     : " + request.getRemoteAddr());
        //        获取具体的请求值转JSon输出日志     慎用!
//        logger.info("   PARAM    : " + GsonUtils.toJson(joinPoint.getArgs()));

        SysLogInfoWithBLOBs sysLogInfo=SysLogInfoWithBLOBs.getWebContext();
        WebContext webContext = WebContext.getWebContext();
        //下面进行日志填充
        //这里的主键我采用的是   时间戳+6位随机数
        sysLogInfo.setId(Long.valueOf(String.valueOf(System.currentTimeMillis())+String.valueOf(RandomUtil.randomNumberString(6))));
        sysLogInfo.setLogType(Constant.LOG_INFO);  //代表是正常请求 为1
        sysLogInfo.setUserId(webContext.getUserId()==null ? null:Integer.valueOf(String.valueOf(webContext.getUserId())));
        sysLogInfo.setUserName(webContext.getUserName()==null ? null:webContext.getUserName());
        sysLogInfo.setHttpType(request.getMethod());
        sysLogInfo.setRequestBeanName(beanName);
        sysLogInfo.setRequestUrl(request.getServletPath());
        sysLogInfo.setRequestIp(request.getRemoteAddr());
        sysLogInfo.setRequestMethod(method.getName());
        sysLogInfo.setUrlRemark(annotation==null ? null:annotation.requestRemark());
//        获取具体的请求值转JSon填充  慎用!
//        sysLogInfo.setRequestParam(GsonUtils.toJson(joinPoint.getArgs()));
        sysLogInfo.setCreateTime(new Date());

        sysLogInfoMapper.insertSelective(sysLogInfo);
    }

    @AfterReturning(returning = "ret", pointcut = "webLog()")
    public void doAfterReturning(Object ret) throws Throwable {
        SysLogInfoWithBLOBs sysLogInfo=SysLogInfoWithBLOBs.getWebContext();
        // 下面是将返回值转成自己的Vo类   来获取具体的返回码状态
        if (ret instanceof ResultVO) {
            ResultVO resultVO = (ResultVO)ret;
            sysLogInfo.setRequestReturnStatus(resultVO.getStatus());
        }else if (ret instanceof Response){
            Response resultVO = (Response)ret;
            sysLogInfo.setRequestReturnStatus(Integer.valueOf(resultVO.getStatus()));
        }else {
            sysLogInfo.setRequestReturnStatus(0000);
        }

        // 处理完请求,返回内容   返回值转json  可以获取具体返回的内容    慎用!
//        logger.info("RESPONSE : " + GsonUtils.toJson(ret));
//        sysLogInfo.setRequestReturnValue(GsonUtils.toJson(ret));
        sysLogInfoMapper.updateByPrimaryKeySelective(sysLogInfo);
    }

    @AfterThrowing( pointcut = "webLog()",throwing = "e")
    public void logThrowing(JoinPoint joinPoint, Throwable e){
        logger.error("**************开始抛出异常***************");
        logger.error("请求类方法:"+joinPoint.getSignature().getName());
        logger.error("异常内容:  "+getTrace(e));
        logger.error("***************抛出异常结束***************");
        SysLogInfoWithBLOBs sysLogInfo=SysLogInfoWithBLOBs.getWebContext();
        //下面填充
        sysLogInfo.setLogType(Constant.LOG_ERRO);//代表请求异常  为2
        sysLogInfoMapper.updateByPrimaryKeySelective(sysLogInfo);
        //插入主表后获取主表id  后插入error附表
        SysLogErrorWithBLOBs sysLogError = new SysLogErrorWithBLOBs();
        sysLogError.setCreateTime(new Date());
        sysLogError.setLogId(sysLogInfo.getId()==null ? -1:sysLogInfo.getId());
        sysLogError.setErrorContent(getTrace(e));
        sysLogError.setErrorInfo(e.getMessage());
        sysLogErrorMapper.insertSelective(sysLogError);
    }

    //异常信息转化 主要是获取异常的详细信息
    private static String getTrace(Throwable t) {
        StringWriter stringWriter= new StringWriter();
        PrintWriter writer= new PrintWriter(stringWriter);
        t.printStackTrace(writer);
        StringBuffer buffer= stringWriter.getBuffer();
        return buffer.toString();
    }

}

自定义注解:LogAnnotation如下
用来说明此接口是用来干嘛的

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogAnnotation {

    String requestRemark() ;
}

关于sys_log_info和sys_log_error这两个表对应的实体类和Mapper文件这里就不贴了,因为没有其他多余的操作,但有个点需要注意
sys_log_info表的实体类要采用TreadLoal存储,可以修改实体类构造方法如下:
在这里插入图片描述
为什么要用TreadLoal,因为
1.我们需要在AOP中多个方法内共用同一个实例,并在第一个方法执行后插入数据,在其他方法获取到相同的实例并填充数据后更新
2.防止并发问题,aop内的方法并不是串行的执行的,就是说并不是一个请求就所有方法执行一遍,有可能200个请求同时执行完doBefore方法后,才去执行doAfterReturning方法,所以每个请求产生的实例都需要保存。所以主键没有采用自增长
3.关于这里的并发问题,我暂时也没有想到更好的方法解决,只是暂时采用了TreadLoal

这里说一下很多人以为insert插入是不会有并发问题的,但是在某种情况下就会有,那就是在insert后需要获取到insert的id的时候。

传参和返回值说明:为什么说这两个地方需要慎用呢?因为传入的参数或者返回的参数有可能是图片或者文件流,这个时候转json就会异常

异常说明:当中途有异常发生的时候,会跳过doAfterReturning方法直接到logThrowing方法,所以发生异常的时候,返回值是没有写到库的。
这个时候可以通过全局异常处理来解决,这里以自定义异常为例,如果异常没有被捕捉到那就没办法了
在这里插入图片描述

总结

对于单结构工程,自我感觉这种已经够用了,如果是聚合工程那可能又得多一层处理,分布式那就别想了,这里只是日志监控,关于liunx系统的监控或者说是服务监控的话,可以提供一个思路那就采用snmp,通过old获取系统各种状态然后可视化,像内存、cpu、流入流出网速都是可以的

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值