logback - 自定义日志脱敏组件,一种不错的脱敏方案,。

完全借鉴了 https://blog.csdn.net/qq_40885085/article/details/113385261

应该是extends logback的实际Appender,然后,在这个自定义Appender中调用脱敏工具,脱敏工具需要有个配置,也就需要一个logback-desensitize.xml,这个和工具类的指定。读取这个配置类的工具为YmlUtils。感觉也应该可以用该方法来实现log4j2的。


log-desensitization项目

项目目录图片:

logback - 自定义日志脱敏组件,一种不错的脱敏方案_字符串

DesensitizationAppender.java

import ch.qos.logback.classic.spi.LoggingEvent;
import top.imddy.logdesensitization.utils.DesensitizationUtils;

import java.lang.reflect.Field;

/**
 * @ClassName DesensitizationAppender
 * @Description 脱敏类 - 将日志进行脱敏
 * @Author 柳成荫
 * @Date 2021/1/9
 */
public class DesensitizationAppender {
    /**
     * LoggingEvent的属性 - message
     * 格式化前的日志信息,如log.info("your name : {}", "柳成荫")
     * message就是"your name : {}"
     */
    private static final String MESSAGE = "message";
    /**
     * LoggingEvent的属性 - formattedMessage
     * 格式化后的日志信息,如log.info("your name : {}", "柳成荫")
     * formattedMessage就是"your name : 柳成荫"
     */
    private static final String FORMATTED_MESSAGE = "formattedMessage";

    public void operation(LoggingEvent event) {
        // event.getArgumentArray() - 获取日志中的参数数组
        // 如:log.info("your name : {}, your id : {}", "柳成荫", 11)
        // event.getArgumentArray() => ["柳成荫",11]
        if (event.getArgumentArray() != null) {
            // 获取格式化后的Message
            String eventFormattedMessage = event.getFormattedMessage();
            DesensitizationUtils util = new DesensitizationUtils();
            // 获取替换后的日志信息
            String changeMessage = util.customChange(eventFormattedMessage);
            if (!(null == changeMessage || "".equals(changeMessage))) {
                try {
                    // 利用反射的方式,将替换后的日志设置到原event对象中去
                    Class<? extends LoggingEvent> eventClass = event.getClass();
                    // 保险起见,将message和formattedMessage都替换了
                    Field message = eventClass.getDeclaredField(MESSAGE);
                    message.setAccessible(true);
                    message.set(event, changeMessage);
                    Field formattedMessage = eventClass.getDeclaredField(FORMATTED_MESSAGE);
                    formattedMessage.setAccessible(true);
                    formattedMessage.set(event, changeMessage);
                } catch (IllegalAccessException | NoSuchFieldException e) {
                    e.printStackTrace();
                }
            }

        }
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.

LcyConsoleAppender.java

package top.imddy.logdesensitization.logbackadvice;

import ch.qos.logback.classic.spi.LoggingEvent;
import ch.qos.logback.core.ConsoleAppender;

/**
 * @ClassName ConsoleAppenderDS
 * @Description
 * @Author 柳成荫
 * @Date 2021/1/9
 */
public class LcyConsoleAppender extends ConsoleAppender {

    @Override
    protected void subAppend(Object event) {
        DesensitizationAppender appender = new DesensitizationAppender();
        appender.operation((LoggingEvent)event);
        super.subAppend(event);
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.

LcyFileAppender.java

package top.imddy.logdesensitization.logbackadvice;

import ch.qos.logback.classic.spi.LoggingEvent;
import ch.qos.logback.core.FileAppender;

/**
 * @ClassName FileAppenderDS
 * @Description
 * @Author 柳成荫
 * @Date 2021/1/9
 */
public class LcyFileAppender extends FileAppender {

    @Override
    protected void subAppend(Object event) {
        DesensitizationAppender appender = new DesensitizationAppender();
        appender.operation((LoggingEvent) event);
        super.subAppend(event);
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.

LcyRollingFileAppender.java

package top.imddy.logdesensitization.logbackadvice;

import ch.qos.logback.classic.spi.LoggingEvent;
import ch.qos.logback.core.rolling.RollingFileAppender;

/**
 * @ClassName RollingFileAppenderDS
 * @Description
 * @Author 柳成荫
 * @Date 2021/1/9
 */
public class LcyRollingFileAppender extends RollingFileAppender {

    @Override
    protected void subAppend(Object event) {
        DesensitizationAppender appender = new DesensitizationAppender();
        appender.operation((LoggingEvent)event);
        super.subAppend(event);
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.

DesensitizationUtils.java

package top.imddy.logdesensitization.utils;


import org.springframework.util.CollectionUtils;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @ClassName DesensitizationUtil
 * @Description 脱敏工具类
 * @Author 柳成荫
 * @Date 2021/1/9
 */
public class DesensitizationUtils {
    /**
     * 正则匹配模式 - 该正则表达式第三个()可能无法匹配以某些特殊符号开头和结尾的(如果像密码这种字段,前后如果有很多特殊字段,则无法匹配,建议密码直接加密,无需脱敏)
     */
    public static final Pattern REGEX_PATTERN = Pattern.compile("\\s*([\"]?[\\w]+[\"]?)(\\s*[:=]+[^\\u4e00-\\u9fa5@,.*{\\[\\w]*\\s*)([\\u4e00-\\u9fa5_\\-@.\\w]+)[\\W&&[^\\-@.]]?\\s*");
    // 该正则表达式第三个()可以匹配以某些特殊字符开头和结尾的,但是对于日志来说,处理也很麻烦
    // public static final Pattern REGEX_PATTERN = Pattern.compile("\\s*([\"]?[\\w]+[\"]?)(\\s*[::=><]+\\s*)([\\S]+[\\u4e00-\\u9fa5\\w]+[\\S]+)[\\W&&[^\\-@.]]?\\s*");

    /**
     * 匹配非数字
     */
    public static final Pattern REGEX_NUM = Pattern.compile("[^0-9]");

    /**
     * 是否开启脱敏
     */
    public static Boolean openFlag = false;
    /**
     * 是否忽略key的大小写
     */
    public static Boolean ignoreFlag = true;
    /**
     * 作为ignoreFlag初始的标记
     */
    private static Boolean initIgnoreFlag = false;
    /**
     * 作为openFlag初始化的标记
     */
    private static Boolean initOpenFlag = false;
    /**
     * 所有key:value配置匹配对
     */
    public static Map<String, Object> allPattern;
    /**
     * key为全小写的allPattern - pattern和patterns
     */
    public static Map<String, Object> lowerCaseAllPattern;
    /**
     * 手机
     */
    public static final String PHONE = "phone";
    /**
     * 邮箱
     */
    public static final String EMAIL = "email";
    /**
     * 身份证
     */
    public static final String IDENTITY = "identity";
    /**
     * 自定义
     */
    public static final String OTHER = "other";
    /**
     * 密码
     */
    public static final String PASSWORD = "password";

    /**
     * 将event对象的formattedMessage脱敏
     *
     * @param eventFormattedMessage LoggingEvent的formattedMessage属性
     * @return 脱敏后的日志信息
     */
    public String customChange(String eventFormattedMessage) {
        try {
            // 原始信息 - 格式化后的
            String originalMessage = eventFormattedMessage;
            boolean flag = false;
            // 获取Yml配置文件内容 - Map格式
            Map<String, Object> patternMap = YmlUtils.patternMap;
            if (!CollectionUtils.isEmpty(patternMap)) {
                // 如果没有开启脱敏,返回"",则不会做脱敏操作
                if (!this.checkOpen(patternMap)) {
                    return "";
                }
                // 获取一个原始Message的正则匹配器
                Matcher regexMatcher = REGEX_PATTERN.matcher(eventFormattedMessage);
                // 如果部分匹配(一个对象/JSON字符串/Map/List<对象/Map>等会有多个匹配),就根据分组来获取key和value
                while (regexMatcher.find()) {
                    // group(1)就是key,group(2)就是分隔符(如:和=),group(3)就是value
                    try {
                        // 获取key - 将引号替换掉,去掉两边空格(JSON字符串去引号)
                        String key = regexMatcher.group(1).replaceAll("\"", "").trim();
                        // 获取原始Value
                        String originalValue = regexMatcher.group(3);
                        // 获取Key对应规则
                        Object keyPatternValue = this.getKeyIgnoreCase(key);
                        if (null != keyPatternValue && null != originalValue && !"null".equals(originalValue)) {
                            // 将原始Value - 引号替换掉,去掉两边空格(JSON字符串去引号)
                            String value = originalValue.replaceAll("\"", "").trim();
                            if (!"null".equals(value) || value.equalsIgnoreCase(key)) {
                                String patternVales = getMultiplePattern(keyPatternValue, value);
                                if ("".equals(patternVales)) {
                                    // 不符规则/没有规则的不能影响其他符合规则的
                                    continue;
                                }
                                patternVales = patternVales.replaceAll(" ", "");
                                if(PASSWORD.equalsIgnoreCase(patternVales)){
                                    String origin = regexMatcher.group(1) + regexMatcher.group(2) + regexMatcher.group(3);
                                    originalMessage = originalMessage.replace(origin, regexMatcher.group(1) + regexMatcher.group(2) + "******");
                                    flag=true;
                                    // 密码级别的,直接替换为全*,继续下一轮匹配
                                    continue;
                                }
                                // 原始的规则(完整)
                                String originalPatternValues = patternVales;
                                // 判断这个规则是否带括号,带括号的需要把括号拿出来 - 核心规则
                                String filterData = this.getBracketPattern(patternVales);
                                if (!"".equals(filterData)) {
                                    patternVales = filterData;
                                }
                                // 以逗号分割
                                String[] split = patternVales.split(",");
                                value = getReplaceValue(value, patternVales, split, originalPatternValues);
                                if (value != null && !"".equals(value)) {
                                    flag = true;
                                    String origin = regexMatcher.group(1) + regexMatcher.group(2) + regexMatcher.group(3);
                                    originalMessage = originalMessage.replace(origin, regexMatcher.group(1) + regexMatcher.group(2) + value);
                                }
                            }
                        }
                    } catch (Exception e) {
                        // 捕获到异常,直接返回结果(空字符串) - 这个异常可能发生的场景:同时开启控制台和输出文件的时候
                        // 当控制台进行一次脱敏之后,文件的再去脱敏,是对脱敏后的message脱敏,则正则匹配会出现错误
                        // 比如123456789@.com 脱敏后:123***456789@qq.com,正则匹配到123,这个123去substring的时候会出错
                        return "";
                    }
                }
            }
            return flag ? originalMessage : "";
        } catch (Exception e) {
            return "";
        }
    }


    /**
     * 获取替换后的value
     * @param value value
     * @param patternVales 核心规则
     * @param split 分割
     * @param originalPatternValues 原始规则
     * @return
     */
    private String getReplaceValue(String value, String patternVales, String[] split, String originalPatternValues) {
        if (split.length >= 2 && !"".equals(patternVales)) {
            String append = "";
            String start = REGEX_NUM.matcher(split[0]).replaceAll("");
            String end = REGEX_NUM.matcher(split[1]).replaceAll("");
            int startSub = Integer.parseInt(start) - 1;
            int endSub = Integer.parseInt(end) - 1;
            // 脱敏起点/结尾符下标
            int index;
            String flagSub;
            int indexOf;
            int newValueL;
            String newValue;
            // 脱敏结尾
            if (originalPatternValues.contains(">")) {
                // 获取>的下标
                index = originalPatternValues.indexOf(">");
                // 获取标志符号
                flagSub = originalPatternValues.substring(0, index);
                // 获取标志符号的下标
                indexOf = value.indexOf(flagSub);
                // 获取标志符号前面数据
                newValue = value.substring(0, indexOf);
                // 获取数据的长度
                newValueL = newValue.length();
                // 获取标识符及后面的数据
                append = value.substring(indexOf);
                value = this.dataDesensitization(Math.max(startSub, 0), endSub >= 0 ? (endSub <= newValueL ? endSub : newValueL - 1) : 0, newValue) + append;
            } else if (originalPatternValues.contains("<")) {
                // 脱敏起点
                index = originalPatternValues.indexOf("<");
                flagSub = originalPatternValues.substring(0, index);
                indexOf = value.indexOf(flagSub);
                newValue = value.substring(indexOf + 1);
                newValueL = newValue.length();
                append = value.substring(0, indexOf + 1);
                value = append + this.dataDesensitization(Math.max(startSub, 0), endSub >= 0 ? (endSub <= newValueL ? endSub : newValueL - 1) : 0, newValue);
            } else if (originalPatternValues.contains(",")) {
                newValueL = value.length();
                value = this.dataDesensitization(Math.max(startSub, 0), endSub >= 0 ? (endSub <= newValueL ? endSub : newValueL - 1) : 0, value);
            }
        } else if (!"".equals(patternVales)) {
            int beforeIndexOf = patternVales.indexOf("*");
            int last = patternVales.length() - patternVales.lastIndexOf("*");
            int lastIndexOf = value.length() - last;
            value = this.dataDesensitization(beforeIndexOf, lastIndexOf, value);
        }
        return value;
    }

    /**
     * 根据key获取对应的规则(也许是Map,也许是String)
     *
     * @param key key
     * @return key对应的规则(也许是Map , 也许是String)
     */
    private Object getKeyIgnoreCase(String key) {
        // 获取所有pattern
        if (CollectionUtils.isEmpty(allPattern)) {
            allPattern = YmlUtils.getAllPattern();
        }
        // 作为ignoreFlag初始化的标记,第一次ignoreFlag需要从Yml中获取是否开启
        // 后面就不用去Yml里获取了
        if (!initIgnoreFlag) {
            initIgnoreFlag = true;
            // 仅在第一次会去获取,无论true还是false(默认是开启忽略大小写)
            ignoreFlag = YmlUtils.getIgnore();
            if (ignoreFlag) {
                // 如果忽略大小写,就去获取一份key小写化的allPattern
                lowerCaseAllPattern = this.transformUpperCase(allPattern);
            }
        }
        // 只有忽略大小写的时候,才去从lowerCaseAllPattern里获取
        if (ignoreFlag) {
            return lowerCaseAllPattern.get(key.toLowerCase());
        } else {
            // 否则从原始的pattern中取
            return allPattern.get(key);
        }
    }



    /**
     * 将pattern的key值全部转换为小写
     *
     * @param pattern pattern
     * @return 转换后的pattern
     */
    public Map<String, Object> transformUpperCase(Map<String, Object> pattern) {
        Map<String, Object> resultMap = new HashMap();
        if (pattern != null && !pattern.isEmpty()) {
            // 获取Key的Set集合
            Set<String> keySet = pattern.keySet();
            Iterator<String> iterator = keySet.iterator();
            // 黄线强迫症,用for代替while
            for (; iterator.hasNext(); ) {
                String key = iterator.next();
                // 把key转换为小写字符串
                String newKey = key.toLowerCase();
                // 重新放入
                resultMap.put(newKey, pattern.get(key));
            }
        }
        return resultMap;
    }


    /**
     * 获取规则字符串
     *
     * @param patternVale 规则
     * @param newValue    key对应的值 - 如 name:liuchengyin  这个参数就是liuchengyn
     * @return 规则的字符串
     */
    private String getMultiplePattern(Object patternVale, String newValue) {
        if (patternVale instanceof String) {
            // 如果规则是String类型,直接转换为String类型返回
            return (String) patternVale;
        } else if (patternVale instanceof Map) {
            // 获取规则 - Map类型(不推荐,有风险)
            return this.getPatternByMap((Map<String, Object>) patternVale, newValue);
        } else { // 获取规则 - List<Map>类型,一个Key可能有多种匹配规则
            if (patternVale instanceof List) {
                List<Map<String, Object>> list = (List<Map<String, Object>>) patternVale;
                if (!CollectionUtils.isEmpty(list)) {
                    Iterator<Map<String, Object>> iterator = list.iterator();
                    // 遍历每一种规则
                    for (; iterator.hasNext(); ) {
                        Map<String, Object> map = iterator.next();
                        String patternValue = this.getPatternByMap(map, newValue);
                        // 如果是空的,表示没匹配上该规则,去匹配下一个规则
                        if (!"".equals(patternValue)) {
                            return patternValue;
                        }
                    }
                }
            }
            return "";
        }
    }


    /**
     * 获取规则
     *
     * @param map   规则
     * @param value key对应的值 - 如 name:liuchengyin  这个参数就是liuchengyn
     * @return
     */
    private String getPatternByMap(Map<String, Object> map, String value) {
        if (CollectionUtils.isEmpty(map)) {
            // 为空就是无规则
            return "";
        } else {
            // 获取匹配规则 - 自定义规则(正则)
            Object customRegexObj = map.get("customRegex");
            // 获取脱敏方式
            Object positionObj = map.get("position");
            // 获取匹配规则 - 自定义规则(正则)
            String customRegex = "";
            // position必须有
            String position = "";
            if (customRegexObj instanceof String) {
                customRegex = (String) customRegexObj;
            }
            if (positionObj instanceof String) {
                position = (String) positionObj;
            }
            // 如果日志中的值能够匹配,直接返回其对应的规则
            if (!"".equals(customRegex) && value.matches(customRegex)) {
                return position;
            } else {
                // 如果不能匹配到正则,就看他是不是内置规则
                Object defaultRegexObj = map.get("defaultRegex");
                String defaultRegex = "";
                if (defaultRegexObj instanceof String) {
                    defaultRegex = (String) defaultRegexObj;
                }
                // 这段代码写的多多少少感觉有点问题,可以写在一个if里,但是阿里检测代码的工具会警告
                if (!"".equals(defaultRegex)) {
                    if(IDENTITY.equals(defaultRegex) && isIdentity(value)){
                        return position;
                    }else if(EMAIL.equals(defaultRegex) && isEmail(value)){
                        return position;
                    }else if(PHONE.equals(defaultRegex) && isMobile(value)){
                        return position;
                    }else if(OTHER.equals(defaultRegex)){
                        return position;
                    }
                }
                return "";
            }
        }
    }


    /**
     * 获取规则 - 判断是否带括号,带括号则返回括号内数据
     * @param patternVales 规则
     * @return 规则
     */
    private String getBracketPattern(String patternVales) {
        // 是否存在括号
        if (patternVales.contains("(")) {
            int startCons = patternVales.indexOf("(");
            int endCons = patternVales.indexOf(")");
            patternVales = patternVales.substring(startCons + 1, endCons);
            return patternVales;
        } else {
            return "";
        }
    }

    public static boolean isEmail(String str) {
        return str.matches("^[\\w-]+@[\\w-]+(\\.[\\w-]+)+$");
    }

    public static boolean isIdentity(String str) {
        return str.matches("(^\\d{18}$)|(^\\d{15}$)");
    }

    public static boolean isMobile(String str) {
        return str.matches("^1[0-9]{10}$");
    }

    /**
     * 检查是否开启脱敏
     *
     * @param pattern Yml配置文件内容 - Map格式
     * @return 是否开启脱敏
     */
    private Boolean checkOpen(Map<String, Object> pattern) {
        // 作为openFlag初始化的标记,第一次openFlag需要从Yml中获取是否开启
        // 后面就不用去Yml里获取了
        if (!initOpenFlag) {
            initOpenFlag = true;
            // 仅在第一次会去获取
            openFlag = YmlUtils.getOpen();
        }
        // 第二次以后openFlag已经有值,无论true还是false(默认是未开启)
        return openFlag;
    }


    /**
     * 脱敏处理
     * @param start 脱敏开始下标
     * @param end 脱敏结束下标
     * @param value value
     * @return
     */
    public String dataDesensitization(int start, int end, String value) {
        char[] chars;
        int i;
        // 正常情况 - end在数组长度内
        if (start >= 0 && end + 1 <= value.length()) {
            chars = value.toCharArray();
            // 脱敏替换
            for (i = start; i < chars.length && i < end + 1; ++i) {
                chars[i] = '*';
            }
            return new String(chars);
        } else if (start >= 0 && end >= value.length()) {
            // 非正常情况 - end在数组长度外
            chars = value.toCharArray();
            for (i = start; i < chars.length; ++i) {
                chars[i] = '*';
            }
            return new String(chars);
        } else {
            // 不符要求,不脱敏
            return value;
        }
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.
  • 189.
  • 190.
  • 191.
  • 192.
  • 193.
  • 194.
  • 195.
  • 196.
  • 197.
  • 198.
  • 199.
  • 200.
  • 201.
  • 202.
  • 203.
  • 204.
  • 205.
  • 206.
  • 207.
  • 208.
  • 209.
  • 210.
  • 211.
  • 212.
  • 213.
  • 214.
  • 215.
  • 216.
  • 217.
  • 218.
  • 219.
  • 220.
  • 221.
  • 222.
  • 223.
  • 224.
  • 225.
  • 226.
  • 227.
  • 228.
  • 229.
  • 230.
  • 231.
  • 232.
  • 233.
  • 234.
  • 235.
  • 236.
  • 237.
  • 238.
  • 239.
  • 240.
  • 241.
  • 242.
  • 243.
  • 244.
  • 245.
  • 246.
  • 247.
  • 248.
  • 249.
  • 250.
  • 251.
  • 252.
  • 253.
  • 254.
  • 255.
  • 256.
  • 257.
  • 258.
  • 259.
  • 260.
  • 261.
  • 262.
  • 263.
  • 264.
  • 265.
  • 266.
  • 267.
  • 268.
  • 269.
  • 270.
  • 271.
  • 272.
  • 273.
  • 274.
  • 275.
  • 276.
  • 277.
  • 278.
  • 279.
  • 280.
  • 281.
  • 282.
  • 283.
  • 284.
  • 285.
  • 286.
  • 287.
  • 288.
  • 289.
  • 290.
  • 291.
  • 292.
  • 293.
  • 294.
  • 295.
  • 296.
  • 297.
  • 298.
  • 299.
  • 300.
  • 301.
  • 302.
  • 303.
  • 304.
  • 305.
  • 306.
  • 307.
  • 308.
  • 309.
  • 310.
  • 311.
  • 312.
  • 313.
  • 314.
  • 315.
  • 316.
  • 317.
  • 318.
  • 319.
  • 320.
  • 321.
  • 322.
  • 323.
  • 324.
  • 325.
  • 326.
  • 327.
  • 328.
  • 329.
  • 330.
  • 331.
  • 332.
  • 333.
  • 334.
  • 335.
  • 336.
  • 337.
  • 338.
  • 339.
  • 340.
  • 341.
  • 342.
  • 343.
  • 344.
  • 345.
  • 346.
  • 347.
  • 348.
  • 349.
  • 350.
  • 351.
  • 352.
  • 353.
  • 354.
  • 355.
  • 356.
  • 357.
  • 358.
  • 359.
  • 360.
  • 361.
  • 362.
  • 363.
  • 364.
  • 365.
  • 366.
  • 367.
  • 368.
  • 369.
  • 370.
  • 371.
  • 372.
  • 373.
  • 374.
  • 375.
  • 376.
  • 377.
  • 378.
  • 379.
  • 380.
  • 381.
  • 382.
  • 383.
  • 384.
  • 385.
  • 386.
  • 387.
  • 388.
  • 389.
  • 390.
  • 391.
  • 392.
  • 393.
  • 394.
  • 395.
  • 396.
  • 397.
  • 398.
  • 399.
  • 400.
  • 401.
  • 402.
  • 403.
  • 404.
  • 405.
  • 406.
  • 407.
  • 408.
  • 409.
  • 410.
  • 411.
  • 412.
  • 413.
  • 414.
  • 415.
  • 416.
  • 417.
  • 418.
  • 419.
  • 420.
  • 421.
  • 422.
  • 423.
  • 424.
  • 425.
  • 426.
  • 427.
  • 428.
  • 429.
  • 430.
  • 431.
  • 432.
  • 433.
  • 434.
  • 435.

YmlUtils.java

package top.imddy.logdesensitization.utils;

import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.springframework.util.CollectionUtils;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;

/**
 * @ClassName YmlUtils
 * @Description Yml配置文件操作相关
 * @Author 柳成荫
 * @Date 2021/1/9
 */
public class YmlUtils {
    /** 默认脱敏配置文件名 - 默认在resources目录下 */
    public static String PROPERTY_NAME = "logback-desensitize.yml";
    /** Key:pattern - 单规则 */
    public static final String PATTERN = "pattern";
    /** Key:patterns - 多规则 */
    public static final String PATTERNS = "patterns";
    /** Key:open - 是否开启脱敏 */
    public static final String OPEN_FLAG = "open";
    /** Key:ignore - 是否开启忽略大小写匹配 */
    public static final String IGNORE = "ignore";
    /** Key:脱敏配置文件头Key */
    public static final String YML_HEAD_KEY = "log-desensitize";
    /** key:patterns对应key下的规则Key */
    public static final String CUSTOM = "custom";
    /** Yml脱敏配置文件内容 - Map格式 */
    public static Map<String, Object> patternMap;
    public static final DumperOptions OPTIONS = new DumperOptions();


    static {
        OPTIONS.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
        patternMap = getYmlByName(PROPERTY_NAME);
    }

    /**
     * 获取Yml配置文件的内容 - 以Map的格式
     * @param fileName Yml配置文件名
     * @return 配置信息(Map格式)
     */
    private static Map<String, Object> getYmlByName(String fileName) {
        if (CollectionUtils.isEmpty(patternMap)) {
            Object fromYml = null;
            try {
                // 获取Yml配置文件的Map对象
                fromYml = getFromYml(fileName, YML_HEAD_KEY);
                // LinkedHashMap,如果不是Map类型(比如配置文件里只有log-desensitize=123456),直接返回patternMap本身
                if (fromYml instanceof Map) {
                    return (Map)fromYml;
                }
            } catch (Exception e) {
                return null;
            }
        }
        return patternMap;
    }

    /**
     * 通过key获取value从yml配置文件
     * @param fileName Yml文件名
     * @param key key
     * @return value或者map本身
     */
    public static Object getFromYml(String fileName, String key){
        // 创建一个Yaml对象
        Yaml yaml = new Yaml(OPTIONS);
        // 获得流
        InputStream inputStream = YmlUtils.class.getClassLoader().getResourceAsStream(fileName);
        HashMap<String, Object> map = (HashMap<String, Object>)yaml.loadAs(inputStream, HashMap.class);
        // 如果map内有值,直接返回key对应的Value,否则返回map本身
        return Objects.nonNull(map) && map.size() > 0 ? map.get(key) : map;
    }

    /**
     * 获取key为pattern的值
     * @return pattern对应的map,或者null(如pattern=123这种情况)
     */
    public static Map<String, Object> getPattern() {
        Object pattern = patternMap.get(PATTERN);
        if (pattern instanceof Map) {
            return (Map<String, Object>)pattern;
        } else {
            return null;
        }
    }

    /**
     * 获取所有pattern,含key为pattern,key为patterns
     * @return pattern
     */
    public static Map<String, Object> getAllPattern() {
        Map<String, Object> allPattern = new HashMap<String, Object>();
        Map<String, Object> pattern = getPattern();
        Map<String, Object> patterns = getPatterns();
        if (!CollectionUtils.isEmpty(patterns)) {
            allPattern.putAll(patterns);
        }
        // 注意:patterns中的key与pattern的key重复,patterns中的不生效(Map无重复Key)
        if (!CollectionUtils.isEmpty(pattern)) {
            allPattern.putAll(pattern);
        }
        return allPattern;
    }

    /**
     * 获取key为patterns的值
     * @return patterns对应的map,或者null(如patterns=123这种情况)
     */
    public static Map<String, Object> getPatterns() {
        Map<String, Object> map = new HashMap<String, Object>();
        Object patterns = patternMap.get(PATTERNS);
        // patterns下有多个key的时候(List)
        if (patterns instanceof List) {
            // 获取key为"patterns"的值(List<Map<String, Object>>)
            List<Map<String, Object>> list = (List<Map<String, Object>>)patterns;
            if (!CollectionUtils.isEmpty(list)) {
                Iterator<Map<String, Object>> iterator = list.iterator();
                // 黄线强迫症,用for代替while
                for (;iterator.hasNext();){
                    Map<String, Object> maps = (Map<String, Object>)iterator.next();
                    assembleMap(map, maps);
                }
                return map;
            }
        }
        // patterns只有一个key的时候,且非List
        if (patterns instanceof Map) {
            assembleMap(map, (Map<String, Object>)patterns);
            return map;
        } else {
            return null;
        }
    }

    /**
     * 将patterns中每个key对应的规则按<key,规则>的方式放入map
     * @param map map
     * @param patterns patterns
     */
    private static void assembleMap(Map<String, Object> map, Map<String, Object> patterns) {
        // 获取patterns里key值为"key"的值(脱敏关键字)
        Object key = patterns.get("key");
        if (key instanceof String) {
            // 清除空格
            String keyWords = ((String) key).replace(" ", "");
            // 以逗号分隔出一个key数组
            String[] keyArr = keyWords.split(",");
            for(String keyStr : keyArr){
                map.put(keyStr, patterns.get(CUSTOM));
            }
        }
    }

    /**
     * 是否开启脱敏,默认不开启
     * @return 是否开启脱敏
     */
    public static Boolean getOpen() {
        Object flag = patternMap.get(OPEN_FLAG);
        return flag instanceof Boolean ? (Boolean)flag : false;
    }

    /**
     * 是否忽略大小写匹配,默认开启
     * @return 是否忽略大小写匹配
     */
    public static Boolean getIgnore() {
        Object flag = patternMap.get(IGNORE);
        return flag instanceof Boolean ? (Boolean)flag : true;
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>top.imddy</groupId>
    <artifactId>log-desensitization</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.6.15</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.8</version>
        </dependency>
    </dependencies>

</project>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.


log-test项目(用于测试)

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>top.imddy</groupId>
    <artifactId>log-test</artifactId>
    <version>1.0-SNAPSHOT</version>


    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>


    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.6.15</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>


    <dependencies>
        <dependency>
            <groupId>top.imddy</groupId>
            <artifactId>log-desensitization</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!--lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

    </dependencies>


    <build>
        <finalName>log-test</finalName>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
            </resource>
        </resources>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.6.15</version>
                <configuration>
                    <fork>true</fork>
                    <includeSystemScope>true</includeSystemScope>
                    <!--fork : 如果没有该项配置,肯呢个devtools不会起作用,即应用不会restart -->
                    <!--这里写上main方法所在类的路径-->
                    <mainClass>top.imddy.logtest.Application</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.2</version>
                <configuration>
                    <skip>true</skip>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <version>3.3.1</version>
                <configuration>
                    <encoding>UTF-8</encoding>
                    <!-- 过滤后缀为pem、pfx的证书文件 -->
                    <nonFilteredFileExtensions>
                        <nonFilteredFileExtension>p12</nonFilteredFileExtension>
                        <nonFilteredFileExtension>cer</nonFilteredFileExtension>
                        <nonFilteredFileExtension>pem</nonFilteredFileExtension>
                        <nonFilteredFileExtension>pfx</nonFilteredFileExtension>
                    </nonFilteredFileExtensions>
                </configuration>
            </plugin>
        </plugins>
    </build>


</project>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.

Test01Controller.java

package top.imddy.logtest.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;

@RestController
@RequestMapping("/test01")
public class Test01Controller {
    private static final Logger log = LoggerFactory.getLogger(Test01Controller.class);


    @GetMapping("/info")
    public String info() {
        log.info("localMobile:{},localMobile:{}", "023-55327459", "123456");//规范语法 -推荐,格外推荐使用=分割。
        log.info("your email:{},your phone:{}", "123456789@q9.com", "15310763497");
        log.info("your email={},your cellphone={}", "123456789@q9.com", "15310763497");
        log.info("email:{},mobile:}", "123456789@qq.com", "023-55327459");
        log.info("identity:{}", "123456789012345");
        // Map类型
        HashMap<String, String> map = new HashMap<>();
        map.put("phone", "15310763497");
        map.put("email", "123456789@qq.com");
        log.info("one map={}", map);
        log.info("one json={}", "{" + "\t\"email\":\"123456789@gq.com\"," + "\t\"phone\":\"15310764682\"" + "}");// 对象类型
        log.info("student:{}", new Student("柳成荫", "15372746384", "530365199703153648")); // 对于需要完全密文的
        log.info("password:{}", "189498asd6489s");
        //非规范 -但也能用 - 不推荐
        log.info(" mobile :{}.email:{}", 123456, "123456789@qq.com");
        log.info(" mobile :{}email:{}", 123456, "123456789@qq.com");
        log.info(" mobile :{}: email:{}", 123456, "123456789@qg.com");
        return "柳成荫";
    }



}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.

logback-spring.xml

2、替换日志文件配置类(logback.xml)
日志打印方式都只需要替换成脱敏的类即可,如果你的业务不需要,则无需替换。
①ConsoleAppender - 控制台脱敏
// 原类
ch.qos.logback.core.ConsoleAppender
// 替换类
pers.liuchengyin.logbackadvice.LcyConsoleAppenderRollingFileAppender - 滚动文件
// 原类
ch.qos.logback.core.rolling.RollingFileAppender
// 替换类
pers.liuchengyin.logbackadvice.LcyRollingFileAppenderFileAppender - 文件
// 原类
ch.qos.logback.core.FileAppender
// 替换类
pers.liuchengyin.logbackadvice.LcyFileAppender
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>

	<property name="logback.logdir" value="/logs/logtest" />
	<property name="logback.appname" value="logtest"/>

	<appender name="consoleLog"
		class="top.imddy.logdesensitization.logbackadvice.LcyConsoleAppender">
		<layout class="ch.qos.logback.classic.PatternLayout">
			<pattern>
				%date{yyyy-MM-dd HH:mm:ss.SSS} %-5level[%thread]%logger{56}.%method:%L -%msg%n
			</pattern>
		</layout>
	</appender>

	<appender name="fileInfoLog"
		class="top.imddy.logdesensitization.logbackadvice.LcyRollingFileAppender">
		<filter class="ch.qos.logback.classic.filter.LevelFilter">
			<level>ERROR</level>
			<onMatch>DENY</onMatch>
			<onMismatch>ACCEPT</onMismatch>
		</filter>
		<encoder>
			<pattern>
				%date{yyyy-MM-dd HH:mm:ss.SSS} %-5level[%thread]%logger{56}.%method:%L -%msg%n
			</pattern>
		</encoder>
		<!-- 滚动策略 -->
		<rollingPolicy
			class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
			<!-- 路径 -->
			<fileNamePattern>${logback.logdir}/info.${logback.appname}.%d.log</fileNamePattern>
			<MaxHistory>7</MaxHistory>
		</rollingPolicy>
	</appender>
	
		<appender name="fileDebugLog"
		class="top.imddy.logdesensitization.logbackadvice.LcyRollingFileAppender">
		<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
			<level>DEBUG</level>
		</filter>
		<encoder>
			<pattern>
				%date{yyyy-MM-dd HH:mm:ss.SSS} %-5level[%thread]%logger{56}.%method:%L -%msg%n
			</pattern>
		</encoder>

		<!-- 设置滚动策略 -->
		<rollingPolicy
			class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
			<!-- 路径 -->
			<fileNamePattern>${logback.logdir}/debug.${logback.appname}.%d.log</fileNamePattern>
			<MaxHistory>7</MaxHistory>
		</rollingPolicy>
	</appender>
	
	<appender name="fileWarnLog"
		class="top.imddy.logdesensitization.logbackadvice.LcyRollingFileAppender">
		<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
			<level>WARN</level>
		</filter>
		<encoder>
			<pattern>
				%date{yyyy-MM-dd HH:mm:ss.SSS} %-5level[%thread]%logger{56}.%method:%L -%msg%n
			</pattern>
		</encoder>

		<!-- 设置滚动策略 -->
		<rollingPolicy
			class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
			<!-- 路径 -->
			<fileNamePattern>${logback.logdir}/warn.${logback.appname}.%d.log</fileNamePattern>
			<MaxHistory>7</MaxHistory>
		</rollingPolicy>
	</appender>

	<appender name="fileErrorLog"
		class="top.imddy.logdesensitization.logbackadvice.LcyRollingFileAppender">
		<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
			<level>ERROR</level>
		</filter>
		<encoder>
			<pattern>
				%date{yyyy-MM-dd HH:mm:ss.SSS} %-5level[%thread]%logger{56}.%method:%L -%msg%n
			</pattern>
		</encoder>

		<!-- 设置滚动策略 -->
		<rollingPolicy
			class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
			<!-- 路径 -->
			<fileNamePattern>${logback.logdir}/error.${logback.appname}.%d.log</fileNamePattern>
			<MaxHistory>7</MaxHistory>
		</rollingPolicy>
	</appender>
	
	<root level="INFO">
		<appender-ref ref="consoleLog" />
		<appender-ref ref="fileInfoLog" />
		<appender-ref ref="fileDebugLog" />
		<appender-ref ref="fileWarnLog" />
		<appender-ref ref="fileErrorLog" />
	</root>
</configuration>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.

logback-desensitize.yml

# 日志脱敏
log-desensitize:
  # 是否忽略大小写匹配,默认为true
  ignore: true
  # 是否开启脱敏,默认为false
  open: true
  # pattern下的key/value为固定脱敏规则
  pattern:
    # 邮箱 - @前第4-7位脱敏
    email: "@>(4,7)"
    # qq邮箱 - @后1-3位脱敏
    qqemail: "@<(1,3)"
    # 姓名 - 姓脱敏,如*杰伦
    name: 1,1
    # 密码 - 所有需要完全脱敏的都可以使用内置的password
    password: password
  patterns:
    # 身份证号,key后面的字段都可以匹配以下规则(用逗号分隔)
    - key: identity,idcard
      # 定义规则的标识
      custom:
        # defaultRegex表示使用组件内置的规则:identity表示身份证号 - 内置的18/15- defaultRegex: identity
          position: 9,13
        # 内置的other表示如果其他规则都无法匹配到,则按该规则处理
        - defaultRegex: other
          position: 9,10
    # 电话号码,key后面的字段都可以匹配以下规则(用逗号分隔)
    - key: phone,cellphone,mobile
      custom:
        # 手机号 - 内置的11位手机匹配规则
        - defaultRegex: phone
          position: 4,7
        # 自定义正则匹配表达式:座机号(带区号,号码七位|八位)
        - customRegex: "^0[0-9]{2,3}-[0-9]{7,8}"
          # -后面的1-4位脱敏
          position: "-<(1,4)"
        # 自定义正则匹配表达式:座机号(不带区号)
        - customRegex: "^[0-9]{7,8}"
          position: 3,5
        # 内置的other表示如果其他规则都无法匹配到,则按该规则处理
        - defaultRegex: other
          position: 1,3
    # 这种方式不太推荐 - 一旦匹配不上,就不会脱敏
    - key: localMobile
      custom:
        customRegex: "^0[0-9]{2,3}-[0-9]{7,8}"
        position: 1,3
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.

测试结果

logback - 自定义日志脱敏组件,一种不错的脱敏方案_字符串_02

logback - 自定义日志脱敏组件,一种不错的脱敏方案_字符串_03