基于springAOP的日志采集实现

hl.wang

发布于 2021.04.17 16:25 阅读 2388 评论 0

提出问题

       近期在实习中负责了一个日志系统,主要功能为每个项目收集到用户的操作日志,将这些日志发送到一个专门收集统计日志的项目对这些日志进行管理与统计

 

 

 

分析问题

    在这个系统当中目前据我认为有两个相对于的难点,第一个是用户操作日志的收集,第二个是当项目与用户量增多时的并发问题的处理。针对于这两个问题首先用户操作日志的收集可以利用spring的aop,我们自定义一个注解,我们需要收集哪些操作时我们给这个方法打上这个注解,然后进入到我们编写的切面中,在切面中进行用户操作的解析。本文主要解决用过aop切面完成对用户操作进行收集的功能

 

 

 

 

解决问题

   如果要详细了解如何进行切面编程请去查看https://blog.csdn.net/qq_36761831/article/details/90299680

   下面我们来介绍一下如何通过spring的aop来实现我们的业务需要,首先我们自定义一个注解Mylog

/**
*
* 操作日志注解
*
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyLog {
    /**
     * 记录操作描述
     */
    String remark() default "";
    /**
     * 是否打日志 默认打
     */
    boolean isLog() default true;
}

   

 

注解中我们定义两个属性一个是remark属性,我们用来记录当前方法的主要功能描述,isLog属性我们用于判断是否需要记录该操作日志,如果还有其他的需要,可以自行添加其余的属性。

 

 

下面我们建立一个切面来拦截这个注解进行处理,首先我们定义拦截点

@Pointcut("@annotation(com.jtexplorer.aop.annotation.MyLog)")
private void sendLog() {
}

 

 

定义完了拦截点之后,根据我们的业务需求在方法在拦截的方法执行之前,我们首先设置一些默认值

@Before("sendLog()")
public void before(JoinPoint joinPoint) {
    //初始化
    //requestMap = new HashMap<>();
    try {
        requestMap.put("usloDbStartTime", TimeTools.transformDateFormat(new Date(), "yyyy-MM-dd HH:mm:ss"));
        startTime = System.currentTimeMillis();
    } catch (ParseException e) {
        e.printStackTrace();
    }
    MethodSignature signature = (MethodSignature) joinPoint.getSignature();
    MyLog myLog = signature.getMethod().getAnnotation(MyLog.class);
    if (myLog != null) {
        if (myLog.isLog()) {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            //封装域名、url
            requestMap.put("usloDomain", request.getServerName());
            requestMap.put("usloUrl", request.getServletPath());
            //请求类型
            if(request.getMethod().equals("POST")){
                requestMap.put("usloType","1");
            }else if(request.getMethod().equals("GET")){
                requestMap.put("usloType","2");
            }else if(request.getMethod().equals("PUT")){
                requestMap.put("usloType","3");
            }else if(request.getMethod().equals("DELETE")){
                requestMap.put("usloType","4");
            }
            String projectName = PropertiesUtil.GetValueByKey(Thread.currentThread().getContextClassLoader().getResource(FILE_PATH).getPath(), "sendLogProjectName");
            if(StringUtil.isNotEmpty(projectName)){
                requestMap.put("usloItemName", projectName);
            }
            String projectId = PropertiesUtil.GetValueByKey(Thread.currentThread().getContextClassLoader().getResource(FILE_PATH).getPath(), "sendLogProjectId");
            if(StringUtil.isNotEmpty(projectId)){
                requestMap.put("usloItemId", projectId);
            }
            //方法描述
            try {
                //获得http还是https
                String networkProtocol = request.getScheme();
                //获得端口号
                int port = request.getServerPort();
                //获得服务器ip和机器名
                InetAddress ia = InetAddress.getLocalHost();
                String hotsAddress = ia.getHostAddress();
                String hostName = ia.getHostName();
                requestMap.put("usloPor", String.valueOf(port));
                //服务器ip
                requestMap.put("usloServerAddress", hotsAddress);
                //机器名称
                requestMap.put("usloHostName", hostName);
                if (networkProtocol.equals("http")) {
                    requestMap.put("usloIdentification", String.valueOf(1));
                } else if (networkProtocol.equals("https")) {
                    requestMap.put("usloIdentification", String.valueOf(2));
                }
                String methodDescription = getControllerMethodDescription(joinPoint);
                //方法描述
                requestMap.put("usloDescription", methodDescription);
                // String ipAddress =  InetAddress.getLocalHost().toString().substring(InetAddress.getLocalHost().toString().lastIndexOf("/") + 1);
                String requestIp = IPUtil.getIpFromRequest(request);
                //访问者ip
                requestMap.put("usloVisitAddress", requestIp);
            } catch (Exception e) {
                e.printStackTrace();
            }
            //拼接请求参数
            StringBuffer params = new StringBuffer();
            params.append("{");
            Enumeration<?> enumeration = request.getParameterNames();
            while (enumeration.hasMoreElements()) {
                String paramName = enumeration.nextElement().toString();
                params.append("\"");
                params.append(paramName + "\":\"" + request.getParameter(paramName) + "\",");
            }
            params.append("}");
            //请求数据
            requestMap.put("usloRequestData", params.toString());
            //响应数据
            requestMap.put("usloMethodName", joinPoint.getSignature().getName());
        }
    }
}

 

/**
* 获得注解中的描述信息
*
* @param joinPoint
* @return
* @throws ClassNotFoundException
*/
public static String getControllerMethodDescription(JoinPoint joinPoint) throws ClassNotFoundException {
    String targetName = joinPoint.getTarget().getClass().getName();
    String methodName = joinPoint.getSignature().getName();
    Object[] arguments = joinPoint.getArgs();
    Class targetClass = Class.forName(targetName);
    Method[] methods = targetClass.getMethods();
    String description = "";
    for (Method method : methods) {
        if (method.getName().equals(methodName)) {
            Class[] clazzs = method.getParameterTypes();
            if (clazzs.length == arguments.length) {
                description = method.getAnnotation(MyLog.class).remark();
                break;
            }
        }
    }
    return description;
}

 

@before执行完成之后根据执行流程会进入到@Around注解中去,@Around注解环绕了方法执行前和执行后,根据我们此次的业务需求,在此方法里只需要我们注解所标注的方法的返回数据

 

@Around("sendLog()")
public Object timeAround(ProceedingJoinPoint joinPoint) throws Throwable {
    Object result = null;
    requestMap = new HashMap<>();
    result = joinPoint.proceed();
    if (result != null) {
        //提交日志需要
        requestMap.put("usloResponseData", result.toString());
    }
    return result;
}

 

当@Around执行结束之后,我们的切面就回去执行@After注解的方法在该方法执行时说明我们的方法一定是响应结束了,在这里面我们记录方法的结束时间,用于求总的响应时间

 

@After("sendLog()")
public void after(JoinPoint joinPoint) {
    try {
        requestMap.put("usloDbEndTime", TimeTools.transformDateFormat(new Date(), "yyyy-MM-dd HH:mm:ss"));
    } catch (ParseException e) {
        e.printStackTrace();
    }
    endTime = System.currentTimeMillis();
    //响应所耗费时间
    requestMap.put("usloRequestTime", String.valueOf(endTime-startTime));
}

 

 最后我们只剩下@AfterReturning注解与@AfterThrowing注解,其实从名字上我们也可以看出来这两个注解的方法是用来干什么的,@AfterReturning就是在正常响应之后会进入到这个注解之中根据我们的业务需求主要用来记录我们的响应状态码

 

@AfterReturning("sendLog()")
public void afterReturning(JoinPoint joinPoint) {
    try {
        HttpServletResponse httpServletResponse = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
        int status = httpServletResponse.getStatus();
        //状态码 200 500
        requestMap.put("usloAction", String.valueOf(status));
        HttpServletRequest httpServletRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        Employee employee = SessionUtil.getLoginEmp(httpServletRequest.getSession());
        if (employee != null) {
            requestMap.put("usloUserId", employee.getEmpId().toString());
            requestMap.put("usloUserName", employee.getEmpName());
        }
        if (requestMap.get("usloAction").equals("200")) {
            requestMap.put("usloErrorContent", "");
        }
        String url = PropertiesUtil.GetValueByKey(Thread.currentThread().getContextClassLoader().getResource(FILE_PATH).getPath(), "sendLogIP");
        sendLogByThreadPool(url);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

 

@AfterThrowing注解则是当代码出现错误的时候会被拦截到改注解之中,用于处理当后台代码报错时我们应存储什么操作日志,根据实际的业务需求进行变动在这里我们只需要记录状态码和错误原因

 

@AfterThrowing(value = "execution(* com.*.controller..*(..))", throwing = "throwable")
public void afterThrowing(JoinPoint joinPoint, Throwable throwable) {
    throwable.printStackTrace();
    StringWriter sw = new StringWriter();
    PrintWriter pw = new PrintWriter(sw);
    throwable.printStackTrace(pw);
    String[] errors = sw.toString().split("\r\n");
    int i = 0;
    StringBuffer sb = new StringBuffer();
    for(String error : errors){
        if(i<10){
            sb.append(errors[i] + "\r\n");
        }else{
            break;
        }
        if(i == 0){
            requestMap.put("usloErrorName",errors[i]);
        }
        i++;
    }
    requestMap.put("usloErrorContent", sb.toString());
    requestMap.put("usloAction", "500");
    String url = PropertiesUtil.GetValueByKey(Thread.currentThread().getContextClassLoader().getResource(FILE_PATH).getPath(), "sendLogIP");
    sendLogByThreadPool(url);
}

 

至此我们通过aop切面编程完成对用户操作日志的收集就完成了。

 

 

 

总结

      本文主要使用了spring的aop编写自定义注解来实现了用户操作日志的收集工作。aop中注解的执行顺序为@before->@around->@after->@afterReturning当出现异常时执行顺序为:@before->@around->@after->@afterThrowing。before我们一般用于初始值的赋值,具体的业务逻辑我们一般编写在后面的过程之中。