logback高级特性一
自定义Pattern模板
package com.cj.log; import ch.qos.logback.classic.pattern.ClassicConverter; import ch.qos.logback.classic.spi.ILoggingEvent; public class IpConvert extends ClassicConverter { @Override public String convert(ILoggingEvent event) { return "10.10.10.10"; } }
<conversionRule conversionWord="ip" converterClass="com.cj.log.IpConvert" />
<Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS}%ip [%thread] %-5level %logger{36} -% msg%n</Pattern>
public class PatternLayout extends PatternLayoutBase<ILoggingEvent> { public static final Map<String, String> defaultConverterMap = new HashMap<String, String>(); static { defaultConverterMap.putAll(Parser.DEFAULT_COMPOSITE_CONVERTER_MAP); defaultConverterMap.put("d",DateConverter.class.getName()); defaultConverterMap.put("date",DateConverter.class.getName()); defaultConverterMap.put("r",RelativeTimeConverter.class.getName()); defaultConverterMap.put("relative",RelativeTimeConverter.class.getName()); ...
defaultConverterMap.put("ip",IpConvert.class.getName());
即可。"ip"是转换的字符,IpConvert是上面定义的转换器类。但如何添加进去呢?下面便是一种实现方案:
package com.cj.log; import ch.qos.logback.classic.PatternLayout; public class MyPatternLayout extends PatternLayout { static { defaultConverterMap.put("ip",IpConvert.class.getName()); } }
<!-- 日志输出格式 -->
<layout class="com.cj.log.MyPatternLayout">
<Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %ip [%thread] %-5level %logger{36} -%msg%n</Pattern>
</layout>
通过上述两种方案,便可实现自定义模板的功能。这种功能使用的一种场景便是在集群的环境下进行日志的分析,通常分析异常日志的时候,并不能准确定位到底是哪台主机上的哪个server出了错,如果添加了ip地址信息到日志中去,那么日志分析工作讲会变得更加准确高效。如果有类似于监控平台这样的系统,那么便可将所有的异常日志统一进行分析,只需在输出中定义一些类似于主机ip、系统应用id之类的区别的变量,这样处理的好处自然不言而喻。
经典应用-Java中关于使用logback做日志脱敏
本文使用最简单的脱敏方式进行数据脱敏打印,规则如下:
| 参数 | 脱敏前 | 脱敏后 |
| ——– | ——– | ——– |
| 姓名 | 李丽丽 | 李** |
| 手机号 | 13898701234 | 138****1234 |
| 身份证号 | 111111111111115762 | **************5762 |
| 银行卡号 | 6222600890987671234 | 6222600********1234 |
具体代码
首先定义类:SensitiveDataConverter 继承父类:MessageConverter
import ch.qos.logback.classic.pattern.MessageConverter; import ch.qos.logback.classic.spi.ILoggingEvent; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * 敏感信息脱敏处理 * @author AAA */ public class SensitiveDataConverter extends MessageConverter { @Override public String convert(ILoggingEvent event){ // 获取原始日志 String oriLogMsg = event.getFormattedMessage(); // 获取脱敏后的日志 String afterLogMsg = invokeMsg(oriLogMsg); return afterLogMsg; } /** * 日志脱敏开关 */ private static String converterCanRun = "true"; /** * 日志脱敏关键字 */ private static String sensitiveDataKeys = "idcard,realname,bankcard,mobile"; /** * 处理日志字符串,返回脱敏后的字符串 * @param msg * @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 static Pattern pattern = Pattern.compile("[0-9a-zA-Z]"); /** * 判断从字符串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; } public static void main(String[] args) { String tempMsg = "{sign=f88898b2677e62f1ad54b9e330c0a27e, idcard=130333198901192762, realname=%E5%BE%90%E5%BD%A6%E5%A8%9C, key=c5d34d4c3c71cc45c88f32b4f13da887, mobile=13210141605, bankcard=6226430106137525}"; String tempMsg1 = "{\"reason\":\"成功 \",\"result\":{\"jobid\":\"JH2131171027170837443588J6\",\"realname\":\"李哪娜\",\"bankcard\":\"6226430106137525\",\"idcard\":\"130333198901192762\",\"mobile\":\"13210141605\",\"res\":\"1\",\"message\":\"验证成功\"},\"error_code\":0}"; SensitiveDataConverter sc = new SensitiveDataConverter(); System.out.println(sc.invokeMsg(tempMsg)); System.out.println(sc.invokeMsg(tempMsg1)); } /** * 获取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; } private String tuomin(String submsg, String key){ // idcard:身份证号, realname:姓名, bankcard:银行卡号, mobile:手机号 if("idcard".equals(key)){ return SensitiveInfoUtils.idCardNum(submsg); } if("realname".equals(key)){ return SensitiveInfoUtils.chineseName(submsg); } if("bankcard".equals(key)){ return SensitiveInfoUtils.bankCard(submsg); } if("mobile".equals(key)){ return SensitiveInfoUtils.mobilePhone(submsg); } return ""; } }
ss
import org.apache.commons.lang.StringUtils; public class SensitiveInfoUtils { /** * [姓名] 只显示第一个汉字,其他隐藏为星号<例子:李**> * * @param fullName * @return */ public static String chineseName(String fullName) { if (StringUtils.isBlank(fullName)) { return ""; } String name = StringUtils.left(fullName, 1); return StringUtils.rightPad(name, StringUtils.length(fullName), "*"); } /** * [身份证号] 显示最后四位,其他隐藏。共计18位或者15位。<例子:*************5762> * * @param idCardNum * @return */ public static String idCardNum(String idCardNum) { if (StringUtils.isBlank(idCardNum)) { return ""; } String num = StringUtils.right(idCardNum, 4); return StringUtils.leftPad(num, StringUtils.length(idCardNum), "*"); } /** * [手机号码] 前三位,后四位,其他隐藏<例子:138******1234> * * @param num * @return */ public static String mobilePhone(String num) { if (StringUtils.isBlank(num)) { return ""; } return StringUtils.left(num, 3).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(num, 4),StringUtils.length(num), "*"), "***")); } /** * [银行卡号] 前六位,后四位,其他用星号隐藏每位1个星号<例子:6222600**********1234> * * @param cardNum * @return */ public static String bankCard(String cardNum) { if (StringUtils.isBlank(cardNum)) { return ""; } return StringUtils.left(cardNum, 6).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(cardNum, 4), StringUtils.length(cardNum), "*"), "******")); } }
配置文件
代码部分完成之后,我们需要在locback.xml配置文件中增加一行配置:
<conversionRule conversionWord="msg" converterClass="com.api.filter.SensitiveDataConverter"> </conversionRule>
详细如下:
<?xml version="1.0" encoding="UTF-8"?> <configuration> <!-- Logback默认配置的采用的步骤 --> <!-- 1. 尝试在 classpath 下查找文件 logback-test.xml; --> <!-- 2. 如果文件不存在,则查找文件 logback.xml; --> <!-- 3. 如果两个文件都不存在,logback 用 BasicConfigurator 自动对自己进行配置,这会导致记录输出到控制台。 --> <!-- 本机环境中只会加载该配置文件,部署服务器时请删除本文件 --> <conversionRule conversionWord="msg" converterClass="com.api.filter.SensitiveDataConverter"> </conversionRule> <!-- 输出控制台 --> <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> <target>System.out</target> <encoder> <Pattern><![CDATA[ [%-5level] [%d{yyyy-MM-dd HH:mm:ss.SSS}] [%t] [%logger.%method:%line] -- %msg%n ]]></Pattern> </encoder> </appender> <!-- 时间滚动输出日志 --> <appender name="file—info" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>D:/logs/aaaa.log</file> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>INFO</level> </filter> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>D:/logs/aaaa.%d{yyyy-MM-dd}.log</fileNamePattern> </rollingPolicy> <encoder> <pattern><![CDATA[ [%-5level] [%d{yyyy-MM-dd HH:mm:ss.SSS}] [%t] [%logger.%method:%line] -- %msg%n ]]></pattern> </encoder> </appender> <logger name="dfpay-auto" additivity="false"> <appender-ref ref="INFO_FILE" /> </logger> <root level="INFO"> <appender-ref ref="console" /> </root> </configuration>
然后就可以运行代码,输出结果:
{sign=f88898b2677e62f1ad54b9e330c0a27e, idcard=*********2762, realname=%*********************, key=c5d34d4c3c71cc45c88f32b4f13da887, mobile=132****1605, bankcard=622643******7525}
{“reason”:”成功 “,”result”:{“jobid”:”JH2131171027170837443588J6”,”realname”:”李**”,”bankcard”:”622643******7525”,”idcard”:”**************2762”,”mobile”:”132****1605”,”res”:”1”,”message”:”验证成功”},”error_code”:0}
转自:
https://blog.csdn.net/fywfengyanwei/article/details/78484590
https://blog.csdn.net/zhuyucheng123/article/details/21524529