1. 程式人生 > >springboot搭建專案之日誌AOP,支援日誌內容可配置控制(黑名單欄位不會列印或其他處理方式)

springboot搭建專案之日誌AOP,支援日誌內容可配置控制(黑名單欄位不會列印或其他處理方式)

一、問題描述及試用場景

在專案除錯或生產環境追查問題時,日誌檔案是我們最常用的方式。為了滿足日誌規範和方便日誌追查,一般會寫個日誌攔截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 @Configuration public 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包

其中

importorg.egg.utils.HideDataUtil;
import org.egg.utils.JsonUtil;

程式碼所用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>

宣告:此專案僅是拋磚引玉,內容不是特別完善。如有轉載,請註明此處。