zoukankan      html  css  js  c++  java
  • 基于Springboot+Aop实现统一日志管理(选择性输出日志)

    前言

    实现统一日志的方式有很多种,基本上通过aop切入所有的controller接口,打印入参出参就可以了,但是由于博主这个接到的需求比较妖,所以实现的略微复杂

    功能介绍:输出所有的方法的入参&出参,根据@LoggerOut注解,输出入参/出参对象中字段含有@LoggerOut注解的字段值,也可以单独输出基本数据类型和String类型的形参值

    温馨提示:如果只需要简单的实现controller的入参/出参打印,本文可能不太适合你,当然如果你愿意花费这个时间来看博主也是非常欢迎的~~~

    1.添加依赖

    <!-- slf4j日志包依赖 -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.22</version>
    </dependency>
    
    <!-- joor反射包 -->
    <dependency>
        <groupId>org.jooq</groupId>
        <artifactId>joor-java-8</artifactId>
        <version>0.9.13</version>
    </dependency>
    
    <!-- springcloud的eureka客户端包依赖,包含了aspectJ的依赖 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <!-- 没有依赖上面springcloud的eureka包,可以使用这个包 -->
    <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjrt -->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjrt</artifactId>
        <version>1.9.6</version>
        <scope>runtime</scope>
    </dependency>

    2.自定义注解

    /**
     * 日志输出注解
     * 1.标注在对象的字段上
     * 2.标注在形参上
     * 该注解需要配合LogHandler一起使用
     * @author YiLiChenTu
     * @date 2021/8/30 14:37
     */
    @Target({ElementType.FIELD,ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface LoggerOut {
    }
    /**
     * 日志处理注解,将该注解标注在方法上,会扫描方法的入参和出参,
     * 将标注了LoggerOut的对象中标注了LoggerOut的字段值输出日志
     *
     * @author YiLiChenTu
     * @date 2021/8/26 16:21
     */
    @Target({ElementType.METHOD,ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface LogHandler {
    
        /**
         * 打印的日志级别
         * @return
         */
        LoggerLevel value() default LoggerLevel.DEBUG;
    
    }

    3.日志级别枚举类

    public enum LoggerLevel {
        INFO,
        DEBUG,
        WARN,
        TRACE
    }

    4.切面实现

    @Component
    @Slf4j
    @Aspect
    public class LogHandlerAspect {
    
        private static final String QUOTES = """;
    
        /**
         * 匹配所有带有@LogHandler注解的方法
         * @author YiLiChenTu
         * @date 2021/8/26 18:44
         * @return
         * @throws
         **/
        @Pointcut("execution(@com.xxx.common.annotation.LogHandler * *(..)) ")
        public void resultLog(){}
    
        @Around(value = "resultLog()")
        public Object around(ProceedingJoinPoint point) throws Throwable {
            MethodSignature signature = (MethodSignature) point.getSignature();
            //获取切入点所在的方法
            Method method = signature.getMethod();
            LogHandler logHandler = method.getAnnotation(LogHandler.class);
            if (logHandler == null){
                return point.proceed();
            }
            //获取请求的类名
            String className = point.getTarget().getClass().getName();
            //获取请求的方法名
            String methodName = method.getName();
            //入参日志
            before(point,signature,method,className,methodName,logHandler);
            // 执行方法
            Object result =  point.proceed();
            boolean fileOrStream = isFileOrStream(result);
            Assert.isTrue(!fileOrStream,"LogHandler====>> LoggerHandler日志注解不能作用于流或文件上");
            //后置日志
            afterLog(result,className,methodName,logHandler);
            return result;
        }
    
        /**
         * 接口入参打印
         * @author YiLiChenTu
         * @date 2021/8/30 15:24
         * @param joinPoint
         * @param signature
         * @param method
         * @param className
         * @param methodName
         * @param logHandler
         * @return
         * @throws
         **/
        public void before(JoinPoint joinPoint,MethodSignature signature,Method method,String className,String methodName,LogHandler logHandler) {
            try{
                Object[] args = joinPoint.getArgs();
                String[] parameterNames = signature.getParameterNames();
                Annotation[][] annotationArr = method.getParameterAnnotations();
                for (int i = 0; i < parameterNames.length; i++){
                    Annotation[] annotations = annotationArr[i];
                    LoggerOut loggerOut = null;
                    for (Annotation annotation : annotations) {
                        if (annotation instanceof LoggerOut){
                            loggerOut = (LoggerOut) annotation;
                            break;
                        }
                    }
                    if (loggerOut == null){
                        //未携带注解的参数不处理
                        continue;
                    }
                    String paramName = parameterNames[i];
                    Object arg = args[i];
                    if (arg == null){
                        printLog(logHandler,"LogHandler-param-log====>> className:{},methodName:{},param:{},value:{}",
                                className,methodName, paramName, null);
                        continue;
                    }
                    boolean fileOrStream = isFileOrStream(arg);
                    Assert.isTrue(!fileOrStream,"LogHandler-param-log====>> LoggerHandler日志注解不能作用于流或文件上");
                    boolean flag = isBaseTypeOrString(arg);
                    if (flag){
                        //8中基本数据类型的包装类型,或者String类型,直接打印
                        printLog(logHandler,"LogHandler-param-log====>> className:{},methodName:{},param:{},value:{}",
                                className,methodName, paramName,arg);
                    }else if (arg instanceof Collection){
                        //入参为集合类型,判断集合中是否为对象类型
                        Collection<?> list = (Collection<?>) arg;
                        int size = list.size();
                        if (size == 0 || isBaseTypeOrString(list.toArray()[0])){
                            printLog(logHandler,"LogHandler-param-log====>> className:{},methodName:{},param:{},value:{}",
                                    className,methodName, paramName,JSON.toJSONString(arg));
                        }else {
                            log.warn("LogHandler-param-log====>> className:{},methodName:{},入参:{} 为集合类型,不打印",
                                    className,methodName,paramName);
                        }
                    }else if (arg instanceof Object[]){
                        Object[] arr = (Object[]) arg;
                        if (arr.length == 0 || isBaseTypeOrString(arr[0])){
                            printLog(logHandler,"LogHandler-param-log====>> className:{},methodName:{},param:{},value:{}",
                                    className,methodName, paramName,JSON.toJSONString(arg));
                        }else {
                            log.warn("LogHandler-param-log====>> className:{},methodName:{},param:{} 为对象数组,不打印",
                                    className, methodName,paramName);
                        }
                    }else if (arg instanceof Map){
                        log.warn("LogHandler-param-log====>> className:{},methodName:{},入参:{} 为集合类型,不打印",
                                className,methodName,paramName);
                    }else {
                        //入参为对象,反射获取对象字段值
                        String builder = paramToString(arg);
                        printLog(logHandler,"LogHandler-param-log====>> className:{},methodName:{},param:{},value:{}",
                                className,methodName, paramName,builder);
                    }
                }
            }catch (Exception e){
                log.error("LogHandler打印入参异常",e);
            }
        }
    
        /**
         * 返回结果日志输出
         * @author YiLiChenTu
         * @date 2021/8/30 15:24
         * @param result
         * @param className
         * @param methodName
         * @param logHandler
         * @return
         * @throws
         **/
        private void afterLog(Object result,String className,String methodName,LogHandler logHandler){
            try {
                if (result instanceof Ret){
                    Ret<?> ret = (Ret<?>) result;
                    Object data = ret.getData();
                    logOut(data, logHandler, className, methodName);
                }else {
                    logOut(result, logHandler, className, methodName);
                }
            }catch (Exception e){
                log.error("日志切面后置处理异常",e);
            }
        }
    
        /**
         * 获取需要打印的字段转换字符串
         * @author YiLiChenTu
         * @date 2021/8/30 10:31
         * @param result
         * @return
         * @throws
         **/
        private String paramToString(Object result) {
            StringBuilder builder = new StringBuilder();
            builder.append("{");
            List<Field> fields = getAllFields(result);
            Reflect reflect = Reflect.on(result);
            Map<String, Reflect> fieldValueMap = reflect.fields();
            for (int i = 0; i < fields.size(); i++) {
                Field field = fields.get(i);
                LoggerOut loggerOut = field.getAnnotation(LoggerOut.class);
                if (loggerOut == null){
                    continue;
                }
                String fieldName = field.getName();
                Object value = fieldValueMap.get(fieldName).get();
                if (isFileOrStream(value)){
                    throw new XXXException("不要把日志注解标注在流或文件类型的字段上");
                }
                if (value instanceof Collection || value instanceof Map || value instanceof Object[]){
                    continue;
                }
                builder.append(QUOTES);
                builder.append(fieldName);
                builder.append(QUOTES);
                builder.append(":");
                builder.append(JSON.toJSONString(value));
                builder.append(Symbol.COMMA);
            }
            if (builder.length() > 1){
                builder.deleteCharAt(builder.length()-1);
            }
            builder.append("}");
            return builder.toString();
        }
    
        private List<Field> getAllFields(Object result){
            List<Field> fields = new ArrayList<>();
            Class<?> clazz = result.getClass();
            //向上循环  遍历父类
            for (; clazz != Object.class; clazz = clazz.getSuperclass()) {
                Field[] field = clazz.getDeclaredFields();
                for (Field f : field) {
                    f.setAccessible(true);
                    fields.add(f);
                }
            }
            return fields;
        }
    
        /**
         * 日志输出
         * @author YiLiChenTu
         * @date 2021/8/30 10:31
         * @param result
         * @param methodLog
         * @param className
         * @param methodName
         * @return
         * @throws
         **/
        private void logOut(Object result, LogHandler methodLog, String className, String methodName) {
            if (result == null){
                printLog(methodLog,"LogHandler-result-log====>> className:{},methodName:{},result:{}",
                        className,methodName, null);
                return;
            }
            if (isBaseTypeOrString(result)){
                //基本数据类型或者字符串,直接输出日志
                printLog(methodLog,"LogHandler-result-log====>> className:{},methodName:{},result:{}",
                        className, methodName, JSON.toJSONString(result));
                return;
            }
            if (result instanceof Map){
                log.warn("LogHandler-result-log====>> className:{},methodName:{},返回结果为集合类型,不打印",
                        className, methodName);
                return;
            }
            if (result instanceof Collection){
                Collection<?> list = (Collection<?>) result;
                int size = list.size();
                if (size == 0 || isBaseTypeOrString(list.toArray()[0])){
                    printLog(methodLog,"LogHandler-result-log====>> className:{},methodName:{},result:{}",
                            className, methodName, JSON.toJSONString(result));
                }else {
                    log.warn("LogHandler-result-log====>> className:{},methodName:{},返回结果为集合类型,不打印",
                            className, methodName);
                }
                return;
            }
            if (result instanceof Object[]){
                Object[] arr = (Object[]) result;
                if (arr.length == 0 || isBaseTypeOrString(arr[0])){
                    printLog(methodLog,"LogHandler-result-log====>> className:{},methodName:{},result:{}",
                            className, methodName, JSON.toJSONString(result));
                }else {
                    log.warn("LogHandler-result-log====>> className:{},methodName:{},返回结果为对象数组,不打印",
                            className, methodName);
                }
                return;
            }
            //入参为对象,反射获取对象字段值
            String builder = paramToString(result);
            printLog(methodLog,"LogHandler-result-log====>> className:{},methodName:{},result:{}",
                    className,methodName, builder);
        }
    
        /**
         * 判断是否为文件或者流类型
         * @author YiLiChenTu
         * @date 2021/8/30 10:32
         * @param obj
         * @return
         * @throws
         **/
        private boolean isFileOrStream(Object obj){
            return  obj instanceof InputStreamSource
                    || obj instanceof InputStreamSource[]
                    || obj instanceof File
                    || obj instanceof File[]
                    || obj instanceof InputStream
                    || obj instanceof InputStream[];
        }
    
        /**
         * 判断是否基本数据类型或者字符串类型
         * @author YiLiChenTu
         * @date 2021/8/30 10:32
         * @param obj
         * @return
         * @throws
         **/
        private boolean isBaseTypeOrString(Object obj){
            return obj instanceof Byte
                    || obj instanceof Short
                    || obj instanceof Integer
                    || obj instanceof Long
                    || obj instanceof Character
                    || obj instanceof Float
                    || obj instanceof Double
                    || obj instanceof Boolean
                    || obj instanceof String;
        }
    
        /**
         * 根据日志级别,打印不同级别的日志
         * @author YiLiChenTu
         * @date 2021/8/30 10:32
         * @param logHandler
         * @param formatStr
         * @param formats
         * @return
         * @throws
         **/
        private void printLog(LogHandler logHandler, String formatStr, Object... formats){
            if (logHandler.value() == LoggerLevel.INFO){
                log.info(formatStr,formats);
            }else if (logHandler.value() == LoggerLevel.DEBUG){
                log.debug(formatStr,formats);
            }else if (logHandler.value() == LoggerLevel.WARN){
                log.warn(formatStr,formats);
            }else if (logHandler.value() == LoggerLevel.TRACE){
                log.warn(formatStr,formats);
            }
        }
    }
  • 相关阅读:
    mysql 严格模式 Strict Mode
    PHP中NULL和‘'的区别
    nginx 出现413 Request Entity Too Large问题的解决方法
    mysql 转换NULL数据方法
    mysql大小写敏感配置
    mysql导入大批量数据出现MySQL server has gone away的解决方法
    mysql函数concat与group_concat使用说明
    Linux下aMule安装教程
    四、YOLO-V1原理与实现(you only look once)
    tf.cast(ndarray,dtype)
  • 原文地址:https://www.cnblogs.com/yilichentu/p/15208142.html
Copyright © 2011-2022 走看看