springboot搭建项目之日志AOP,支持日志内容可配置控制(黑名单字段不会打印或其他处理方式)
来源:互联网 发布:ipad下载软件付费 编辑:程序博客网 时间:2024/05/19 06:14
一、问题描述及试用场景
在项目调试或生产环境追查问题时,日志文件是我们最常用的方式。为了满足日志规范和方便日志追查,一般会写个日志拦截AOP切面注入相关方法,打印入参,出参。但有个头疼问题,某些参数里包含一些敏感字段,给予数据安全一般不允许打印,比如:用户密码,银行卡卡号,手机号等等。下面就是这个问题的基本解决方案和思路。
二、解决方案思路:
在AOP增强方法中,加入黑名单概念,既如果切入点方法参数含有黑名单中的字段名称,则另行处理。
1.通过java反射获取方法参数的字段名称及内容;
2.通过配置文件配置黑名单数据
3.,对于类似于 foo(String name,String pwd)这类无实体参数方法需要通过jdk1.8 Parameter来实现获取形参名。遗憾的是java8默认编译不记录形参,需要配置javac -parameters打开此功能;
IDEA配置位置:settings->Build,Excution,Deployment->Compiler->Java Compiler中的javac Options里的Additional command line parameters.
三、样例源码:
package org.egg.aop;import org.apache.commons.lang3.ArrayUtils;import org.apache.commons.lang3.StringUtils;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.*;import org.egg.utils.HideDataUtil;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.context.annotation.Configuration;import org.springframework.util.CollectionUtils;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.lang.reflect.Field;import java.lang.reflect.Method;import java.lang.reflect.Parameter;import java.util.ArrayList;import java.util.Arrays;import java.util.List;import java.util.Map;/** * @author dataochen * @Description 高级别(取敏感数据logAop) * 支持实体嵌套实体,支持实体包含list,map,支持无实体多参数方法 * 不支持含有重载方法的类 * 如果含有重载的方法(isOverWirte=true),不支持入参的参数为空,因为入参参数为空,导致拿不到为空参数的参数类型,从而找不到对应的方法 * * @date: 2017/11/7 18:42 */@Aspect@Configurationpublic class SuperLoggerAop { private static final Logger LOGGER = LoggerFactory.getLogger(SuperLoggerAop.class); /** * [大小写不敏感]日志拦截字段名黑名单 不打印 */ private final String[] blackArray = {"password"}; /** * ##[大小写不敏感]日志拦截卡号级别字段 前6后四 */ private final String[] cardNoArray = {"cardNo", "cardNum"}; /** * [大小写不敏感]日志拦截手机号级别字段 前三后4 */ private final String[] phoneNumArray = {"phonenum", "phoneno", "tel"}; /** * 是否显示null字段 */ private final Boolean isDisplayNull = true; /** * 查找方法是否含有重载方法 */ private final Boolean isOverWirte = false; //匹配org.egg.controller包及其子包下的所有类的所有方法 @Pointcut("execution(* org.egg.controller..*.*(..)) || execution(* org.egg.service..*.*(..))") public void executeService() { } /** * 前置通知,方法调用前被调用 * * @param joinPoint */ @Before("executeService()") public void doBeforeAdvice(JoinPoint joinPoint) throws IllegalAccessException { StringBuilder stringBuilder = new StringBuilder("Integration Method:["); stringBuilder.append(joinPoint.getSignature().getDeclaringTypeName()).append("."); stringBuilder.append(joinPoint.getSignature().getName()); stringBuilder.append("],Parameters:"); Method declaredMethod = null; if (isOverWirte) { declaredMethod = getDeclaredMethod(joinPoint); } else { declaredMethod = getDeclaredMethodForName(joinPoint); } if (declaredMethod != null) {// 参数值 Object[] args = joinPoint.getArgs(); Parameter[] parameters = declaredMethod.getParameters(); if (parameters != null) { stringBuilder.append("["); for (int i = 0; i < parameters.length; i++) { String s = convertLog(stringBuilder, parameters[i], args[i], false, parameters[i].getName()); if (StringUtils.isNotBlank(s)) { stringBuilder.append(","); } } stringBuilder.replace(stringBuilder.length() - 1, stringBuilder.length(), "]"); } } LOGGER.info(stringBuilder.toString()); } @AfterReturning(value = "executeService()", returning = "returnValue") public void doAfterAdvice(JoinPoint joinPoint, Object returnValue) throws IllegalAccessException { StringBuilder stringBuilder = new StringBuilder("Integration result:["); stringBuilder.append(joinPoint.getSignature().getDeclaringTypeName()).append("."); stringBuilder.append(joinPoint.getSignature().getName()); stringBuilder.append("],Parameters:"); convertLog(stringBuilder, null, returnValue, true, "result"); LOGGER.info(stringBuilder.toString()); System.out.println("后置通知执行了!!!!"); } @Around("executeService()") public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { long time = System.currentTimeMillis(); Object retVal = proceedingJoinPoint.proceed(); time = System.currentTimeMillis() - time; StringBuilder stringBuilder = new StringBuilder("Integration performance Method:["); stringBuilder.append(proceedingJoinPoint.getSignature().getDeclaringTypeName()).append("."); stringBuilder.append(proceedingJoinPoint.getSignature().getName()); stringBuilder.append("],Time: ").append(time).append(" ms"); return retVal; } private String convertLog(StringBuilder stringBuilder, Parameter parameter, Object arg, Boolean isCustomClass, String fileName) { if (parameter != null) { if (arg == null || parameter.getType().equals(HttpServletRequest.class) || parameter.getType().equals(HttpServletResponse.class)) { return ""; } } else { if (arg == null || arg instanceof HttpServletRequest || arg instanceof HttpServletResponse) { return ""; } }// 判断是否是基础类// parameter.getClass().getClassLoader()不好使 if (arg.getClass().getClassLoader() != null) { if (!isCustomClass) { stringBuilder.append(parameter.getName()).append(":["); } else { stringBuilder.append("["); }// 父类 ArrayList<Field> fields = new ArrayList<Field>(); getFields(fields, arg); for (Field declaredField : fields) { declaredField.setAccessible(true); Object target = null; try { target = declaredField.get(arg); } catch (IllegalAccessException e) { e.printStackTrace(); return ""; } String targetStr = ""; if (isDisplayNull && target == null) { continue; } if (target != null) { if (target.getClass().getClassLoader() != null) { targetStr = convertLog(new StringBuilder(), null, target, true, declaredField.getName()); } else {// list map StringBuilder stringBuilder1 = new StringBuilder(); if (target instanceof List) { List argList = (List) target; stringBuilder1.append("["); argList.forEach(argItem -> { String s = convertLog(new StringBuilder(), null, argItem, true, ""); stringBuilder1.append(s); stringBuilder1.append(","); }); if (CollectionUtils.isEmpty(argList)) { stringBuilder1.append("]"); } else { stringBuilder1.replace(stringBuilder1.length() - 1, stringBuilder1.length(), "]"); } targetStr=stringBuilder1.toString(); } else if (target instanceof Map) { Map<Object, Object> argMap = (Map) target; stringBuilder1.append("["); argMap.entrySet().forEach(Item -> { String s = convertLog(new StringBuilder(), null, Item.getValue(), true, Item.getKey().toString()); stringBuilder1.append(s); stringBuilder1.append(","); }); if (CollectionUtils.isEmpty(argMap)) { stringBuilder1.append("]"); } else { stringBuilder1.replace(stringBuilder1.length() - 1, stringBuilder1.length(), "]"); } targetStr=stringBuilder1.toString(); } else { targetStr = target.toString(); } } } else { targetStr = "null"; } invokeRule(stringBuilder, declaredField.getName(), targetStr); stringBuilder.append(","); } stringBuilder.replace(stringBuilder.length() - 1, stringBuilder.length(), "]"); } else {// 基础类 if (arg instanceof List) { List argList = (List) arg; stringBuilder.append("["); argList.forEach(argItem -> { String s = convertLog(new StringBuilder(), null, argItem, true, ""); stringBuilder.append(s); stringBuilder.append(","); }); if (CollectionUtils.isEmpty(argList)) { stringBuilder.append("]"); } else { stringBuilder.replace(stringBuilder.length() - 1, stringBuilder.length(), "]"); } } else if (arg instanceof Map) { Map<Object, Object> argMap = (Map) arg; stringBuilder.append("["); argMap.entrySet().forEach(Item -> { String s = convertLog(new StringBuilder(), null, Item.getValue(), true, Item.getKey().toString()); stringBuilder.append(s); stringBuilder.append(","); }); if (CollectionUtils.isEmpty(argMap)) { stringBuilder.append("]"); } else { stringBuilder.replace(stringBuilder.length() - 1, stringBuilder.length(), "]"); } } else { invokeRule(stringBuilder, fileName, arg); } } return stringBuilder.toString(); } /** * 获取增强方法 * * @param joinPoint * @return */ private Method getDeclaredMethod(JoinPoint joinPoint) { ArrayList<Class> classes = new ArrayList<>(); if (joinPoint.getArgs() != null) { for (Object o : joinPoint.getArgs()) { if (o instanceof HttpServletRequest) { classes.add(HttpServletRequest.class); } else if (o instanceof HttpServletResponse) { classes.add(HttpServletResponse.class); } else if (o instanceof List) { classes.add(List.class); } else if (o instanceof Map) { classes.add(Map.class); } else { // FIXME: 2017/11/29 请求中的参数a如果为空,就无法获取a的类名了,导致全不能用了 classes.add(o.getClass()); } } } try { Method declaredMethod = joinPoint.getTarget().getClass().getDeclaredMethod(joinPoint.getSignature().getName(), classes.toArray(new Class[classes.size()])); return declaredMethod; } catch (NoSuchMethodException e) { e.printStackTrace(); return null; } } /** * 获取增强方法 * 不用参数 * * @param joinPoint * @return */ private Method getDeclaredMethodForName(JoinPoint joinPoint) { try { Method[] methods = joinPoint.getTarget().getClass().getMethods(); Method declaredMethod = null; if (methods != null) { for (Method method : methods) { if (method.getName().equals(joinPoint.getSignature().getName())) { declaredMethod = method; } } } return declaredMethod; } catch (Exception e) { e.printStackTrace(); return null; } } /** * 执行规则 * * @param stringBuilder * @param parameterName * @param arg */ private void invokeRule(StringBuilder stringBuilder, String parameterName, Object arg) { if (arg == null) { return; } stringBuilder.append(parameterName).append(":"); if (ArrayUtils.contains(blackArray, parameterName)) { stringBuilder.append("***"); } else if (ArrayUtils.contains(cardNoArray, parameterName)) { stringBuilder.append(HideDataUtil.hideCardNo(arg.toString())); } else if (ArrayUtils.contains(phoneNumArray, parameterName)) { stringBuilder.append(HideDataUtil.hidePhoneNo(arg.toString())); } else { stringBuilder.append(arg); } } /** * 递归获取arg对的所有属性 * * @param fields * @param arg */// private void getFields(ArrayList<Field> fields, Class arg) {// if (!arg.getSuperclass().equals(Object.class)) {// getFields(fields,arg.getSuperclass());// List<Field> fields1 = Arrays.asList(arg.getDeclaredFields());// fields.addAll(fields1);// } else {// List<Field> fields1 = Arrays.asList(arg.getDeclaredFields());// fields.addAll(fields1);// }// } private void getFields(ArrayList<Field> fields, Object arg) { Class tempClass = arg.getClass(); while (!tempClass.equals(Object.class)) { List<Field> fields1 = Arrays.asList(tempClass.getDeclaredFields()); fields.addAll(fields1); tempClass = tempClass.getSuperclass(); } }}
四、相关jar包
其中
import org.egg.utils.HideDataUtil;import org.egg.utils.JsonUtil;
这俩工具类详见:http://blog.csdn.net/hupoling/article/details/78512246;http://blog.csdn.net/hupoling/article/details/78512298
代码所用jar包maven坐标:
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.11</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.12.RELEASE</version>
</dependency>
项目地址:https://github.com/SuperEggMan/renting_frame_finish_bek; ps:感兴趣的可以start哦!
声明:此项目仅是抛砖引玉,内容不是特别完善。如有转载,请注明此处。
- springboot搭建项目之日志AOP,支持日志内容可配置控制(黑名单字段不会打印或其他处理方式)
- springboot学习(7)springboot使用AOP打印日志信息
- SpringBoot 通过AOP代理简单打印日志
- springboot实际项目:日志打印、表单验证、异常处理
- SpringBoot进阶之AOP统一处理http请求日志
- SpringBoot AOP统一处理请求日志
- springboot【19】日志管理之使用AOP统一处理Web请求日志
- 方法日志打印demo(可灵活处理方法参数)
- springboot log日志打印
- SpringBoot初始教程之日志处理(二)
- SpringBoot学习笔记之日志处理
- Springboot学习-使用AOP统一处理Web请求日志(六)
- SpringBoot+Maven项目实战(6):整合Log4j和Aop,实现简单的日志记录
- 日志aop方式
- springboot 日志配置
- SpringBoot Logback日志配置
- springboot logback日志配置
- springBoot配置日志文件
- hp电脑开机显示正在准备自动修复,进不了系统
- 内部类与外部类
- yii2 如何使用数据小部件之DetailView
- 开放平台关于获取key时需要填写SHA1安全码的总结
- 我的第一篇
- springboot搭建项目之日志AOP,支持日志内容可配置控制(黑名单字段不会打印或其他处理方式)
- 3.3
- 不需要ajax实现搜索数据保留翻页
- java 锁机制
- 简单选择排序(O(n2))
- 截取IP地址
- 使用main函数的参数,实现一个整数计算器,程序可以接受三个参数,第一个参数“-a”选项执行加法,“-s”选项执行减法,“-m”选项执行乘法,“-d”选项执行除法,后面两个参数为操作数。
- Reading Note: Progressive Growing of GANs for Improved Quality, Stability, and Variation
- 使用MVP注册登录模块+封装的OKhttp,拦截器+QQ第三方登录+RecyclerView+SpringView上拉加载下拉刷新网络数据