Java 加載Properties 配置文件:
ResourceBundle bundle = ResourceBundle.getBundle("log4j_filter"); // 不要寫擴展名 .properties LOG_FILTER_SWITH = bundle.getString("log4j.filter.swith"); LOG_FILTER_KEYS = bundle.getString("log4j.filter.keys");
// 直接在本類中使用main調用時用 Properties.class.getResourceAsStream("/log4j_filter.properties");
//Properties p = new Properties();
//p.load(in);
//LOG_FILTER_SWITH = p.getProperty("log4j.filter.swith");
//LOG_FILTER_KEYS = p.getProperty("log4j.filter.keys");
日志脫敏實現: 因為只修改了一個log4j類的調用,故日志必須是
private final static Logger logger = LogManager.getLogger(SeqConfControl.class);
1 , 配置文件 log4j_filter.properties (一個參數是脫敏日志開關, 一個參數是要脫敏的關鍵字)
2,自定義類 : org.apache.logging.log4j.spi.Log4jFilter.java 是解析處理日志字符串中的敏感信息;
log4j原生類: org.apache.logging.log4j.spi.AbstractLogger.java 負責調用自己寫的類的方法.
3, 規則 支持k:v 和 k=v 兩種形式的數據處理
脫敏時v的長度如果大於8, 采用 XXX****XXX的形式脫敏; 如果小於等於8采用 ******* 形式脫敏
效果:
20:25:02,0197 [INFO ][ ][connect to SFTP . ip:10.0.26.36; port:22;uppath:null;downpath:home/selfftp/selffile/20161209/;username:selfftp;password:********][ :][hulk.frame.sftp.SFtpTools]
21:06:16,0907 [INFO ][ ][接收到一個純文本消息並開始傳入數據庫:{"data":"{\"state\":\"0\",\"hostName\":\"termfrontapp\",\"policyName\":\"check_SSPM_101\",\"stateName\":null,\"program\":null,\"entryId\":null,\"agentId\":null,\"msg\":\"{\\\"account_num\\\":\\\"62294785200****0034\\\",\\\"amount\\\":\\\"2.00\\\",\\\"channel_id\\\":\\\"01I\\\",\\\"p_trans_code\\\":\\\"1017006\\\",\\\"card_type\\\":\\\"\\\",\\\"start_time\\\":\\\"1481634017288\\\",\\\"end_time\\\":\\\"1481634017526\\\",\\\"term_id\\\":\\\"01159206\\\",\\\"ret_code\\\":\\\"ZZ\\\",\\\"ret_msg\\\":\\\"交易失敗,請聯系發卡方|Transaction failed, please contact the issuser\\\",\\\"trans_date_sspm\\\":\\\"20161213210017\\\",\\\"term_install_addr\\\":\\\"10.0.66.8-->10.0.54.198\\\",\\\"term_inst_id\\\":\\\"238\\\",\\\"p_trans_describe\\\":\\\"貸款還息\\\",\\\"account_id2\\\":\\\"\\\"}\",\"ip\":\"10.0.26.71\",\"policyId\":null}","taskClass":"stateAlarm"}][ :][hulk.sspm.trandataMonitor.util.ConsumerMessageListener]
21:06:16,0907 [INFO ][ ][the message to decode: {"account_num":"62294785200****0034","amount":"2.00","channel_id":"01I","p_trans_code":"1017006","card_type":"","start_time":"1481634017288","end_time":"1481634017526","term_id":"01159206","ret_code":"ZZ","ret_msg":"交易失敗,請聯系發卡方|Transaction failed, please contact the issuser","trans_date_sspm":"20161213210017","term_install_addr":"10.0.66.8-->10.0.54.198","term_inst_id":"238","p_trans_describe":"貸款還息","account_id2":""}]
log4j_filter.properties:
# log4j 過濾器配置文件,主要用於系統中敏感字段的脫敏
# true表示打開 false表示關閉
log4j.filter.swith=true
# 要脫敏的關鍵字
log4j.filter.keys=password,passwd,password1,password2,account_num
自定義解析類:org.apache.logging.log4j.spi.Log4jFilter.java
package org.apache.logging.log4j.spi; import java.util.ResourceBundle; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * 自定義處理 日志中的敏感信息 * @author yangw1006@163.com * */ public class Log4jFilter { /** * 日志脫敏 開關 */ private static String LOG_FILTER_SWITH = "false"; /** * 日志脫敏關鍵字 */ private static String LOG_FILTER_KEYS = null; static{ // 加載配置文件 try { // 直接在本類中使用main調用時用 Properties.class.getResourceAsStream("/log4j_filter.properties"); //InputStream in =Properties.class.getClassLoader().getResourceAsStream("log4j_filter.properties"); //Properties p = new Properties(); //p.load(in); //LOG_FILTER_SWITH = p.getProperty("log4j.filter.swith"); //LOG_FILTER_KEYS = p.getProperty("log4j.filter.keys"); ResourceBundle bundle = ResourceBundle.getBundle("log4j_filter"); LOG_FILTER_SWITH = bundle.getString("log4j.filter.swith"); LOG_FILTER_KEYS = bundle.getString("log4j.filter.keys"); } catch (Exception e) { e.printStackTrace(); } } /** * 處理日志字符串,返回脫敏后的字符串 * @param msg * @return */ public static String invokeMsg(final String message){ String msg = new String(message); if("true".equals(LOG_FILTER_SWITH)){ //處理字符串 if(LOG_FILTER_KEYS!=null && LOG_FILTER_KEYS.length()>0){ String[] keyArr = LOG_FILTER_KEYS.split(","); for(String key: keyArr){ // 找key int index= -1; do{ index = msg.indexOf(key, index+1); if(index!=-1){ // 判斷key是否為單詞字符 if(isWordChar(msg,key,index)){ continue; } // 確定是單詞無疑.................................... // 尋找值的開始位置................................. int valueStart = getValueStartIndex(msg,index + key.length()); //查找值的結束位置(逗號,分號)........................ int valueEnd = getValuEndEIndex(msg,valueStart); // 對獲取的值進行脫敏 String subStr = msg.substring(valueStart, valueEnd); subStr = tuomin(subStr); /////////////////////////// msg = msg.substring(0,valueStart) + subStr + msg.substring(valueEnd); } }while(index!=-1); } } } return msg; } private static Pattern pattern = Pattern.compile("[0-9a-zA-Z]"); /** * 判斷從字符串msg獲取的key值是否為單詞 , index為key在msg中的索引值 * @return */ private static 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 static 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 static 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 == ','){ // 去掉前面的 \ 處理這種形式的數據 "account_num\\\":\\\"6230958600001008\\\" while(valueEnd>0 ){ char preCh = msg.charAt(valueEnd-1); if(preCh != '\\'){ break; } valueEnd--; } break; }else{ valueEnd ++; } }else if (ch ==';' || ch == ','){ break; }else{ valueEnd ++; } }while(true); return valueEnd; } private static String tuomin(String submsg){ StringBuffer sbResult = new StringBuffer(); if(submsg!=null && submsg.length()>0){ int len = submsg.length(); if(len > 8){ //8位以上的 隱掉中間4位 for(int i = len-1;i>=0;i--){ if(len-i<5 || len-i>8){ sbResult.insert(0, submsg.charAt(i)); }else{ sbResult.insert(0, '*'); } } }else{ //8位以下的全部使用 * for(int i =0;i<len;i++){ sbResult.append('*'); } } } return sbResult.toString(); } public static void main (String[] args) { //{\\\"account_num\\\":\\\"6230958600001008\\\",\\\"amount\\\":\\\"\\\" String msg = "\\\"account_num\\\":\\\"6230958600001008\\\",\\\"amount\\\":\\\"\\\""; System.out.println(invokeMsg(msg)); } }
log4j原生類 org.apache.logging.log4j.spi.AbstractLogger.java 修改的地方
# 代碼 725行左右的方法 protected void logMessage(final String fqcn, final Level level, final Marker marker, final String message, final Throwable t) { // by yangw String invokeMsg = Log4jFilter.invokeMsg(message); logMessage(fqcn, level, marker, messageFactory.newMessage(invokeMsg), t); }