logback实现日志信息脱敏

一、一些敏感信息比如 手机号、身份证号,不在明文在日志中打印,自定义logback转换器,将这些字段脱敏后输出

1、定义一个ESensitiveDataConverter 类如下:

import ch.qos.logback.classic.pattern.MessageConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import org.apache.commons.lang3.StringUtils;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ESensitiveDataConverter extends MessageConverter {
    /**
     * 日志脱敏开关
     */
    private static String converterCanRun = "true";
    /**
     * 日志脱敏关键字
     */
    private static String sensitiveDataKeys = "email,mobile,idCard,bankNo";

    private static Pattern pattern = Pattern.compile("[0-9a-zA-Z]");

    @Override
    public String convert(ILoggingEvent event){
        // 获取原始日志
        String oriLogMsg = event.getFormattedMessage();

        // 获取脱敏后的日志
        String afterLogMsg = invokeMsg(oriLogMsg);
        return afterLogMsg;
    }


    /**
     * 处理日志字符串,返回脱敏后的字符串
     * @return
     */
    public String invokeMsg(final String oriMsg){
        String tempMsg = oriMsg;
        if("true".equals(converterCanRun)){
            // 处理字符串
            if(sensitiveDataKeys != null && sensitiveDataKeys.length() > 0){
                String[] keysArray = sensitiveDataKeys.split(",");
                for(String key: keysArray){
                    int index= -1;
                    do{
                        index = tempMsg.indexOf(key, index+1);
                        if(index != -1){
                            // 判断key是否为单词字符
                            if(isWordChar(tempMsg, key, index)){
                                continue;
                            }
                            // 寻找值的开始位置
                            int valueStart = getValueStartIndex(tempMsg, index + key.length());

                            // 查找值的结束位置(逗号,分号)........................
                            int valueEnd = getValuEndEIndex(tempMsg, valueStart);

                            // 对获取的值进行脱敏
                            String subStr = tempMsg.substring(valueStart, valueEnd);
                            subStr = tuomin(subStr, key);
                            ///
                            tempMsg = tempMsg.substring(0,valueStart) + subStr + tempMsg.substring(valueEnd);
                        }
                    }while(index != -1);
                }
            }
        }
        return tempMsg;
    }

    private String tuomin(String submsg, String key){
        // idcard:身份证号, name:姓名, bankcard:银行卡号, mobile:手机号
        if(!StringUtils.isEmpty(submsg)){
            return submsg.substring(0,1)+"******";
        }else{
            return "******";
        }

    }


    /**
     * 判断从字符串msg获取的key值是否为单词 , index为key在msg中的索引值
     * @return
     */
    private boolean isWordChar(String msg, String key, int index){

        // 必须确定key是一个单词............................
        if(index != 0){ // 判断key前面一个字符
            char preCh = msg.charAt(index-1);
            Matcher match = pattern.matcher(preCh + "");
            if(match.matches()){
                return true;
            }
        }
        // 判断key后面一个字符
        char nextCh = msg.charAt(index + key.length());
        Matcher match = pattern.matcher(nextCh + "");
        if(match.matches()){
            return true;
        }
        return false;
    }

    /**
     * 获取value值的开始位置
     * @param msg 要查找的字符串
     * @param valueStart 查找的开始位置
     * @return
     */
    private int getValueStartIndex(String msg, int valueStart ){
        // 寻找值的开始位置.................................
        do{
            char ch = msg.charAt(valueStart);
            if(ch == ':' || ch == '='){ // key与 value的分隔符
                valueStart ++;
                ch = msg.charAt(valueStart);
                if(ch == '"'){
                    valueStart ++;
                }
                break;    // 找到值的开始位置
            }else{
                valueStart ++;
            }
        }while(true);
        return valueStart;
    }

    /**
     * 获取value值的结束位置
     * @return
     */
    private int getValuEndEIndex(String msg,int valueEnd){
        do{
            if(valueEnd == msg.length()){
                break;
            }
            char ch = msg.charAt(valueEnd);

            if(ch == '"'){ // 引号时,判断下一个值是结束,分号还是逗号决定是否为值的结束
                if(valueEnd+1 == msg.length()){
                    break;
                }
                char nextCh = msg.charAt(valueEnd+1);
                if(nextCh ==';' || nextCh == ','){
                    // 去掉前面的 \  处理这种形式的数据
                    while(valueEnd>0 ){
                        char preCh = msg.charAt(valueEnd-1);
                        if(preCh != '\\'){
                            break;
                        }
                        valueEnd--;
                    }
                    break;
                }else{
                    valueEnd ++;
                }
            }else if (ch ==';' || ch == ',' || ch == '}'){
                break;
            }else{
                valueEnd ++;
            }

        }while(true);
        return valueEnd;
    }


}

2、logback.xml中添加如下配置:

注意conversionRule 配置的位置,必须在前,否则不生效

<conversionRule conversionWord="msg" converterClass="com.test.encrypt.ESensitiveDataConverter"> </conversionRule>

无需再添加其它Jar引入,利用logback的Converter,自定义日志格式转换符,然后继承ClassicConverter就可以了 。
可以看到这里日志脱敏是通过 解析格式化后的日志来 查找需要脱敏的关键字,然后脱敏关键字后面的字符串来实现。这种方法很慢。那么还可以重写ConsoleAppender

二、重新定义一个ConsoleAppender

1、logback.xml中原本或许是这样

ConsoleAppender是logback中定义的

   <!-- 管控台日志打印,发布生产需注释 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder charset="utf-8">  
            <Pattern>[%date{yyyy-MM-dd HH:mm:ss.SSS}] %level [%X{TRACE_LOG_ID}] [%file:%method:%line] %msg%n</Pattern>
        </encoder>
    </appender>

2、重新定义一个Appender继承ConsoleAppender

@Slf4j
public class SensitiveConsoleAppender extends ConsoleAppender {

    @Override
    protected void subAppend(Object event) {
        try {
             //处理log.info("xxx:{}",arg) 日志中的参数
             this.DealArg((LoggingEvent) event);
        }catch (Exception e){
            log.error("log error:{}!", Throwables.getStackTraceAsString(e));
        }finally {
            //这里最终需要调用父类的subAppend,让日志往下走
            super.subAppend(event);
        }
    }

    private void DealArg(LoggingEvent event){
       //获取到参数,这里注意,只能处理对象类型的参数
       //比如:log.info("用户信息:{}",user)
       //处理不了log.info("邮箱",email);这种类型的。因为这里片是脱敏是 通过判断对象的属性是否是需要脱敏的字段 来决定的
        Object[] args =  event.getArgumentArray();
        if(!Objects.isNull(args)){
            for(int i =0; i<args.length; i++){
                Object arg = args[i];
                //对象中需要脱敏的字段,脱敏。
                String s = MaskUtil.mask(arg);
                args[i] = s;
            }
            //event.setArgumentArray(rargs);
        }

    }
}

3、脱敏方法的注意

log.info("用户信息:{}",user);
//注意调用上面打印日志后,不应改变原对象user的值,因为如果打印user这个对象的日志后,再用user去处理其它业务逻辑  ,如果得到的user对象是脱敏后的对象 那么不对。 如果脱敏的方法不应改变user对象本身。

比如你可以这样:

 public static String toJSONString(Object o){
     try{
         String t = JSON.toJSONString(o);
         Object target = JSON.parseObject(t,o.getClass());
         return MaskUtil.deal(o);
     }catch (Exception e){
         log.info("MaskUtilerror:{}", Throwables.getStackTraceAsString(e));
         return JSON.toJSONString(o);
     }
 }

可能上述方法会比较慢,大致思路是这样的,就是处理脱敏时 需要深拷贝一份,不应该直接操作原始参数。

4、配置

<!-- 管控台日志打印,发布生产需注释 -->
 <appender name="STDOUT" class="com.test.log.SensitiveConsoleAppender">
     <encoder charset="utf-8"> 
         <Pattern>[%date{yyyy-MM-dd HH:mm:ss.SSS}] %level [%X{TRACE_LOG_ID}] [%file:%method:%line] %msg%n</Pattern>
     </encoder>
 </appender>

三、重新定义一个Filter实现

1、重新定一个filter

@Slf4j
public class SampleFilter  extends Filter<ILoggingEvent> {
    @Override
    public FilterReply decide(ILoggingEvent event) {
        try {
            this.maskDeal((LoggingEvent) event);
        }catch (Exception e){
            log.error("log error:{}!", Throwables.getStackTraceAsString(e));
        }finally {
            return FilterReply.ACCEPT;
        }
    }

    private void maskDeal(LoggingEvent event){
        Object[] args =  event.getArgumentArray();
        if(!Objects.isNull(args)){
            for(int i =0; i<args.length; i++){
                Object arg = args[i];
                String s = MaskUtil.deal(arg);
                args[i] = s;
            }
            //event.setArgumentArray(rargs);
        }

    }
}

2、xml定义

这时不用重写Appenderr,只需要原来Appender的添加一个filter即可。

 <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder charset="utf-8"> 
            <Pattern>[%date{yyyy-MM-dd HH:mm:ss.SSS}] %level [%X{TRACE_LOG_ID}] [%file:%method:%line] %msg%n</Pattern>
        </encoder>
        <filter class="com.test.log.filter.SampleFilter"/>
    </appender>

测试方法:

 public static void tests_log(){
        Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
        LoggerContext loggerContext = rootLogger.getLoggerContext();
        // we are not interested in auto-configuration
        loggerContext.reset();

        PatternLayoutEncoder encoder = new PatternLayoutEncoder();
        encoder.setContext(loggerContext);
        encoder.setPattern("%-5level [%thread]: %message%n");
        encoder.start();
        ConsoleAppender<ILoggingEvent> appender = new ConsoleAppender<ILoggingEvent>();
        appender.setContext(loggerContext);
        appender.setEncoder(encoder);
        appender.start();
        rootLogger.addAppender(appender);
        SampleFilter sampleFilter = new SampleFilter();
        appender.addFilter(sampleFilter);
 }
  • 2
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,非常感谢您的提问。以下是一个使用SpringBoot集成MongoDB和Logback实现日志存储的示例: 1. 添加依赖 在pom.xml中添加以下依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> </dependency> ``` 2. 配置MongoDB 在application.yml中添加以下内容: ```yaml spring: data: mongodb: uri: mongodb://localhost:27017/mydb ``` 其中,uri是MongoDB的连接URI,mydb是数据库的名称。 3. 配置Logbacklogback.xml中配置MongoDB的Appender: ```xml <appender name="MONGO" class="de.flapdoodle.embed.log.LogCollectorAppender"> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>INFO</level> </filter> <encoder class="net.logstash.logback.encoder.LogstashEncoder" /> </appender> ``` 其中,LogstashEncoder用于将日志信息转换为JSON格式,方便存储。 4. 配置Logger 在logback.xml中添加Logger: ```xml <logger name="com.example" additivity="false" level="INFO"> <!-- ConsoleAppender --> <appender-ref ref="STDOUT" /> <!-- MongoDBAppender --> <appender-ref ref="MONGO" /> </logger> ``` 其中,name为包名,level为日志记录的最低级别,可以根据实际情况设置。 5. 测试 使用Logging接口记录日志: ```java @Autowired private Logger logger; @Test public void testLogger() { logger.info("This is a test message."); } ``` 记录的日志信息将同时输出到控制台和MongoDB中。 希望以上内容能够对您有所帮助。如果您有其他问题,可以随时提出。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值