前言
实现统一日志的方式有很多种,基本上通过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); } } }