基于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);
        }
    }
}

 

基于Springboot+Aop实现统一日志管理(选择性输出日志)

上一篇:算法探究-3.CenterNet(论文,原理和代码讲解)


下一篇:Spring Boot-Feign-2. feign调用上传文件