最近在搭建springCloud項目,正好這個廢了我點時間,也就記錄下來,防止下次使用,也爲了方便別人
首先是pom.xml配置:
刪除springBoot自己的logback,導入log4j2
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
然後創建咱們的log4j2.xml配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<!-- 根節點Configuration有兩個屬性:status和monitorinterval,有兩個子節點:Appenders和Loggers(表明可以定義多個Appender和Logger) -->
<!-- status:這個用於設置log4j2自身內部的信息輸出,可以不設置,當設置成trace時,會看到log4j2內部各種詳細輸出-->
<!-- monitorInterval:用於指定log4j自動重新配置的監測間隔時間,單位是s,最小是5s;Log4j能夠自動檢測修改配置 文件和重新配置本身,設置間隔秒數-->
<Configuration status="info">
<Appenders>
<!-- SYSTEM_OUT是輸出到統一的輸出流,沒有指定日誌文件 -->
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}]%p[%t]%c{1}|%m%n" />
</Console>
<!-- RollingFile節點用來定義超過指定大小自動刪除舊的創建新的的Appender. name:指定Appender的名字. fileName:指定輸出日誌的目的文件帶全路徑的文件名. filePattern:指定新建日誌文件的名稱格式. -->
<!-- 詳細日誌 -->
<RollingRandomAccessFile name="DetailRollingFile" fileName="log/consumer/consumer_detail.log" filePattern="log/consumer/consumer_detail.log.%d{yyyyMMddHH}" immediateFlush="true">
<!--控制檯只輸出level及以上級別的信息(onMatch),其他的直接拒絕(onMismatch)-->
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
<!-- PatternLayout:輸出格式,不設置默認爲:%m%n. -->
<Log4jEncodeLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}]%p[%t]%c{1}|%m%n" charset="UTF-8"/>
<!-- Policies:指定滾動日誌的策略. -->
<Policies>
<!-- TimeBasedTriggeringPolicy:Policies子節點,基於時間的滾動策略,interval屬性用來指定多久滾動一次,默認是1 hour。modulate=true用來調整時間:比如現在是早上3am,interval是4,那麼第一次滾動是在4am,接着是8am,12am...而不是7am. -->
<TimeBasedTriggeringPolicy interval="1"/>
</Policies>
</RollingRandomAccessFile>
<!-- 簡要日誌 -->
<RollingRandomAccessFile name="MpspRollingFile" fileName="log/consumer/consumer_mpsp.log" filePattern="log/consumer/consumer_mpsp.log.%d{yyyyMMddHH}">
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}]%m%n"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1"/>
</Policies>
<!-- DefaultRolloverStrategy屬性如不設置,則默認爲最多同一文件夾下7個文件,用來指定同一個文件夾下最多有幾個日誌文件時開始刪除最舊的,創建新的(通過max屬性) -->
<!--<DefaultRolloverStrategy max="200"/>-->
</RollingRandomAccessFile>
<!-- 性能監控日誌 -->
<RollingRandomAccessFile name="ssRollingLog" fileName="log/consumer/ss.log" filePattern="log/consumer/ss.log.%d{yyyyMMddHH}">
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}]%m%n"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1"/>
</Policies>
</RollingRandomAccessFile>
<!-- 關鍵日誌 -->
<RollingRandomAccessFile name="mpspRollingLog" fileName="log/consumer/mpsp.log" filePattern="log/consumer/mpsp.log.%d{yyyyMMddHH}">
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}]%m%n"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1"/>
</Policies>
</RollingRandomAccessFile>
</Appenders>
<!--定義logger,只有定義了logger並引入的appender,appender纔會生效-->
<Loggers>
<!-- additivity的值如果爲false的話,就不會在控制檯上輸出或者爲該Logger再增加一個輸出源Consloe -->
<Logger name="payGateMpspLog" level="INFO" additivity="false">
<AppenderRef ref="MpspRollingFile" />
</Logger>
<Logger name="mpspLog" level="INFO" additivity="false">
<AppenderRef ref="mpspRollingLog" />
</Logger>
<Logger name="ssLog" level="INFO" additivity="false">
<AppenderRef ref="ssRollingLog" />
</Logger>
<Logger name="payGateBusiLog" level="INFO" additivity="false">
<AppenderRef ref="DetailRollingFile" />
</Logger>
<Logger name="com" level="INFO" additivity="false">
<AppenderRef ref="DetailRollingFile" />
</Logger>
<Logger name="com" level="INFO" additivity="false">
<AppenderRef ref="DetailRollingFile" />
</Logger>
<Root level="info">
<AppenderRef ref="Console" />
</Root>
</Loggers>
</Configuration>
這裏要注意!!!
由於我的是要實現日誌脫敏的,所以,詳細日誌的打印格式,我是自己定義的,並不是 PatternLayout
如果只想要實現集成,則需要改正。
如果對xml配置項不太懂得,請參考這篇文章:https://www.cnblogs.com/hafiz/p/6170702.html 寫的挺不錯的
接下來是實現日誌脫敏,說白了,就是匹配正則表達式,將value值更換爲********
這裏是實現類代碼:
package com.umpay.utils;
import java.nio.charset.Charset;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.umpay.utils.StringUtils;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Node;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.layout.AbstractStringLayout;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* log4j1.x重寫log4j的PatternLayout,實現日誌信息中敏感信息的編碼或加密
* log4j2.x重寫log4j的AbstractStringLayout,實現日誌信息中敏感信息的編碼或加密
*/
@Plugin(name = "Log4jEncodeLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true)
public class Log4jEncodeLayout extends AbstractStringLayout {
private PatternLayout patternLayout;
protected Log4jEncodeLayout(Charset charset, String pattern) {
super(charset);
patternLayout = PatternLayout.newBuilder().withPattern(pattern).build();
init();
}
private static final Logger log = LoggerFactory.getLogger(Log4jEncodeLayout.class);
/** 要匹配的正則表達式(身份證/銀行卡) */
private Pattern patternChar;
/** 要匹配的正則表達式(姓名) */
private Pattern namepatternChar;
private Pattern cvvpatternChar;
private int keys_length_char;// 要匹配關鍵字的數目
private int keys_cvv_length_char;// 要匹配關鍵字的數目
private String[] charKeys;
private String[] cvvcharKeys;
private void init() {
// String str_char_keys = YamlConfigurerUtil.getStrYmlVal("umfWeChatCore.logLayout", "");
String str_char_keys = StringUtils.trim(YmlUtils.getCommonYml("umfWeChatCore.logLayout"));
StringBuffer charSbf = new StringBuffer();
StringBuffer cvvSbf = new StringBuffer();
String cvv_char_keys = "";
if (StringUtils.isEmpty(str_char_keys)) {// 沒有配置log4j.char.keys,不需要替換*
return;
}
// 2、區分脫敏字段配置規則
// 把帶[]的脫敏字段放入到cvvSbf中,其他脫敏字段放入到charSbf中
String[] charKeysStrs = str_char_keys.split(",");// 讀取配置中要加密的key
for (int i = 0; i < charKeysStrs.length; i++) {
if (isOptional(charKeysStrs[i])) {
// 帶[]的脫敏字段
cvvSbf.append(charKeysStrs[i].substring(1, charKeysStrs[i].length() - 1) + ",");
} else {
charSbf.append(charKeysStrs[i] + ",");
}
}
str_char_keys = charSbf.toString();
cvv_char_keys = cvvSbf.toString();
// 去掉拼轉好的字符串最後一個逗號“,”
if (StringUtils.isNotEmpty(str_char_keys) && str_char_keys.length() > 0) {
str_char_keys = str_char_keys.substring(0, str_char_keys.length() - 1);
}
if (StringUtils.isNotEmpty(cvv_char_keys) && cvv_char_keys.length() > 0) {
cvv_char_keys = cvv_char_keys.substring(0, cvv_char_keys.length() - 1);
}
// 3、組織需要脫敏字段的正則表達式
if (str_char_keys.length() > 0) {
charKeys = str_char_keys.split(",");// 讀取配置中要加密的key
StringBuffer sbRegChar = new StringBuffer();
sbRegChar.append("(");
for (int i = 0; i < charKeys.length; i++) {
sbRegChar.append("(");
sbRegChar.append(charKeys[i]);
sbRegChar.append(")");
if (i < charKeys.length - 1) {
sbRegChar.append("|");
}
}
StringBuffer namesbRegChar = new StringBuffer(sbRegChar);
namesbRegChar.append(")(=|:|:|>)([\u4E00-\u9FA5\u00B7]{1,16})(;|,|\\||\\}|]|$|&|<)");
// 由於(\\s*\\w+={0,2}) key:value 中value中結尾包括==(base64導致,所以修改該寫法)
sbRegChar.append(")(=|:|:|>)(\\s*\\w+={0,2})(;|,|\\||\\}|]|$|&|<)");
namepatternChar = Pattern.compile(namesbRegChar.toString());// 編譯正則表達式
patternChar = Pattern.compile(sbRegChar.toString());// 編譯正則表達式
keys_length_char = charKeys.length;
}
if (cvv_char_keys.length() > 0) {
cvvcharKeys = cvv_char_keys.split(",");// 讀取配置中要加密的key
StringBuffer cvvRegChar = new StringBuffer();
cvvRegChar.append("(");
for (int i = 0; i < cvvcharKeys.length; i++) {
cvvRegChar.append("(");
cvvRegChar.append(cvvcharKeys[i]);
cvvRegChar.append(")");
if (i < cvvcharKeys.length - 1) {
cvvRegChar.append("|");
}
}
cvvRegChar.append(")(=|:|:|>)(.*?)(;|,|\\||\\}|]|$|&|<)");
cvvpatternChar = Pattern.compile(cvvRegChar.toString());// 編譯正則表達式
keys_cvv_length_char = cvvcharKeys.length;
}
}
public static void main(String[] args) {
Log4jEncodeLayout log = new Log4jEncodeLayout(null, "");
}
/**
* 對銀行卡以及身份證號脫敏處理 1.判斷配置文件中是否已經配置需要脫敏字段 2.判斷敏感信息內容是否需要處理 2.1 處理銀行卡和身份證敏感信息
* 2.2 處理用戶姓名敏感信息 2.3 處理需要不展示字段(例如:cvv=123展示爲cvv=)
*
* @param @param msg
* @param @return
* @return String
* @throws
*/
private String dimChar(String msg) {
try {
// 1.判斷配置文件中是否已經配置需要脫敏字段
if (keys_length_char <= 0 && keys_cvv_length_char <= 0) {
return msg;
}
boolean nameFlag = false;
Matcher match = null;
StringBuffer sbMsg = new StringBuffer();
if (patternChar != null) {
match = patternChar.matcher(msg);
// 處理要打印的日誌信息
// 2.判斷敏感信息內容是否需要處理
while (match.find()) {
// 2.1 處理銀行卡和身份證敏感信息
nameFlag = true;
// group(1)匹配key,group(keys_length+2)匹配(=|:|:),group(keys_length+3)匹配value,group(keys_length+4)匹配(;|,|\\||]|\\)|$)
match.appendReplacement(sbMsg, match.group(1)
+ match.group(keys_length_char + 2)
+ replaceChar(match.group(keys_length_char + 3))
+ match.group(keys_length_char + 4));
}
match.appendTail(sbMsg);// 增加最後一個匹配項後面的值
}
if (namepatternChar != null) {
// 2.2 處理用戶姓名敏感信息
if (nameFlag) {
match = namepatternChar.matcher(sbMsg);
} else {
match = namepatternChar.matcher(msg);
}
sbMsg = new StringBuffer();
while (match.find()) {
nameFlag = true;
// group(1)匹配key,group(keys_length+2)匹配(=|:|:),group(keys_length+3)匹配value,group(keys_length+4)匹配(;|,|\\||]|\\)|$)
match.appendReplacement(sbMsg,
match.group(1)
+ match.group(keys_length_char + 2)
+ replaceNameChar(match.group(keys_length_char + 3))
+ match.group(keys_length_char + 4));
}
match.appendTail(sbMsg);// 增加最後一個匹配項後面的值
}
if (cvvpatternChar != null) {
if (nameFlag) {
match = cvvpatternChar.matcher(sbMsg);
} else {
match = cvvpatternChar.matcher(msg);
}
sbMsg = new StringBuffer();
// 2.3 處理需要不展示字段(例如:cvv=123展示爲cvv=)
while (match.find()) {
// group(1)匹配key,group(keys_length+2)匹配(=|:|:),group(keys_length+3)匹配value,group(keys_length+4)匹配(;|,|\\||]|\\)|$)
match.appendReplacement(
sbMsg,
match.group(1)
+ match.group(keys_cvv_length_char + 2)
+ replaceCvvChar(match.group(keys_cvv_length_char + 3))
+ match.group(keys_cvv_length_char + 4));
}
match.appendTail(sbMsg);// 增加最後一個匹配項後面的值
}
return sbMsg.toString();
} catch (Exception e) {
// 如果跑出異常爲了不影響流程,直接返回原信息
log.error(e.getMessage(), e);
}
return msg;
}
private boolean isOptional(String key) {
return key.startsWith("[") && key.endsWith("]");
}
/**
* 日誌關鍵字用* 替換 規則: value <= 6 替換爲****** 6 > value <= 12 後四位保留,其他爲* value > 12
* 前6位保留 中間* 後4位保留
*
* @param value
* @return
*/
private static String replaceChar(String value) {
if (StringUtils.isEmpty(value)) {
return "";
}
if (value.length() <= 6) { // value <= 6 替換爲******
return lStr("", '*', value.length());
} else if (value.length() > 6 && value.length() <= 12) { // 後四位保留,其他爲*
int valLen = value.length();
String replaceHeadStr = value.substring(value.length() - 4);
return replaceHeadStr = lStr(replaceHeadStr, '*', valLen);
} else if (value.length() > 12) { // 前6位保留 中間* 後4位保留
String replaceHeadStr = value.substring(0, 6);
String replaceTailStr = value.substring(value.length() - 4);
String dimStr = lStr("", '*', value.length() - 10);
return replaceHeadStr + dimStr + replaceTailStr;
} else {
return value;
}
}
/**
* 日誌姓名關鍵字替換 保留第一個漢字,後邊用*號表示(例如:張三--》張*)
*
* @param @param value
* @param @return
* @return String
* @throws
*/
private static String replaceNameChar(String value) {
if (StringUtils.isEmpty(value)) {
return "";
}
String replaceHeadStr = value.substring(0, 1);
return replaceHeadStr + lStr("", '*', value.length() - 1);
}
/**
* 日誌cvv關鍵字替換 直接輸出空(例如:1608--》)
*
* @param @param value
* @param @return
* @return String
* @throws
*/
private static String replaceCvvChar(String value) {
return "";
}
/*
* 左補長char
*/
public static String lStr(String s, char ch, int width) {
if (s.length() < width) { // 需要前面補'0'
while (s.length() < width)
s = ch + s;
} else { // 需要將高位丟棄
s = s.substring(s.length() - width, s.length());
}
return s;
}
@Override
public String toSerializable(LogEvent event) {
String message = patternLayout.toSerializable(event);
return dimChar(message);
}
@PluginFactory
public static Layout createLayout(
@PluginAttribute(value = "pattern") final String pattern,
// LOG4J2-783 use platform default by default, so do not specify
// defaultString for charset
@PluginAttribute(value = "charset") final Charset charset) {
return new Log4jEncodeLayout(charset, pattern);
}
}
這個類,也就是我上面說過的,那個自定義的,至此,實現完成,但是還差個問題就是如何獲取配置項的值。
package com.umpay.utils;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import java.util.Properties;
/**
* 讀取配置文件地址
*/
public class YmlUtils {
private static String PROPERTY_NAME = "application.yml";
public static Object getCommonYml(Object key){
Resource resource = new ClassPathResource(PROPERTY_NAME);
Properties properties = null;
try {
YamlPropertiesFactoryBean yamlFactory = new YamlPropertiesFactoryBean();
yamlFactory.setResources(resource);
properties = yamlFactory.getObject();
} catch (Exception e) {
e.printStackTrace();
return null;
}
return properties.get(key);
}
}
如果還有哪裏寫的不對,也有請大佬們批評指正。