1.引入log4j2的jar包:log4j-api和log4j-core。
2.自定义脱敏类和方法,重写PatternLayout类,自定义日志格式和脱敏正则表达式。
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.config.plugins.PluginElement; import org.apache.logging.log4j.core.config.plugins.PluginFactory; import org.apache.logging.log4j.core.pattern.RegexReplacement; import org.apache.logging.log4j.status.StatusLogger; /** * 自定义标签replaces, 用于多个正则表达式替换 * * @author zhangyanchun * @date 2017-07-27 */ @Plugin(name = "replaces", category = "Core", printObject = true) public final class CustomRegexReplaces { private static final Logger LOGGER = StatusLogger.getLogger(); // replace标签,复用log4j已有plugin, replaces 下可以0,1,多个replace private final RegexReplacement[] replaces; private CustomRegexReplaces(RegexReplacement[] replaces) { this.replaces = replaces; } /** * 格式化输出日志信息, 此方法会执行多个正则表达式匹配与替换 * * @param msg * @return */ public String format(String msg) { for (RegexReplacement replace : replaces) { msg = replace.format(msg); } return msg; } /** * 实现pluginFactory, 用于生成pugin * * @param replaces * @return */ @PluginFactory public static CustomRegexReplaces createRegexReplacement( @PluginElement("replaces") final RegexReplacement[] replaces) { if (replaces == null) { LOGGER.info("no replaces is defined"); return null; } if (replaces.length == 1) { LOGGER.warn("have the replaces , but no replace is set"); return null; } return new CustomRegexReplaces(replaces); } }
import java.nio.charset.Charset; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.DefaultConfiguration; 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.PluginBuilderAttribute; import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; import org.apache.logging.log4j.core.config.plugins.PluginElement; 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.apache.logging.log4j.core.pattern.LogEventPatternConverter; import org.apache.logging.log4j.core.pattern.PatternFormatter; import org.apache.logging.log4j.core.pattern.PatternParser; /** * 自定义log4j2 layout, 扩展自PatternLayout(拷贝自log4j2, 留待以后扩展使用) * * @author zhangyanchun * @date 2017-07-27 */ @Plugin(name = "CustomPatternLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true) public final class CustomPatternLayout extends AbstractStringLayout { private static final long serialVersionUID = 1L; // 默认格式化 public static final String DEFAULT_CONVERSION_PATTERN = "%m%n"; // ttc 默认格式化 public static final String TTCC_CONVERSION_PATTERN = "%r [%t] %p %c %x - %m%n"; // 简单格式化 public static final String SIMPLE_CONVERSION_PATTERN = "%d [%t] %p %c - %m%n"; public static final String KEY = "Converter"; // 支持多个formater private final List<PatternFormatter> formatters; private final String conversionPattern; private final Configuration config; private final CustomRegexReplaces replace; private final boolean alwaysWriteExceptions; private final boolean noConsoleNoAnsi; /** * 构造自动以patternLayout * * @param config * @param replace * @param pattern * @param charset * @param alwaysWriteExceptions * @param noConsoleNoAnsi * @param header * @param footer */ private CustomPatternLayout(final Configuration config, final CustomRegexReplaces replace, final String pattern, final Charset charset, final boolean alwaysWriteExceptions, final boolean noConsoleNoAnsi, final String header, final String footer) { super(charset, toBytes(header, charset), toBytes(footer, charset)); this.replace = replace; this.conversionPattern = pattern; this.config = config; this.alwaysWriteExceptions = alwaysWriteExceptions; this.noConsoleNoAnsi = noConsoleNoAnsi; final PatternParser parser = createPatternParser(config); this.formatters = parser.parse(pattern == null ? DEFAULT_CONVERSION_PATTERN : pattern, this.alwaysWriteExceptions, this.noConsoleNoAnsi); } private static byte[] toBytes(final String str, final Charset charset) { if (str != null) { return str.getBytes(charset != null ? charset : Charset.defaultCharset()); } return null; } private byte[] strSubstitutorReplace(final byte... b) { if (b != null && config != null) { return getBytes(config.getStrSubstitutor().replace(new String(b, getCharset()))); } return b; } @Override public byte[] getHeader() { return strSubstitutorReplace(super.getHeader()); } @Override public byte[] getFooter() { return strSubstitutorReplace(super.getFooter()); } public String getConversionPattern() { return conversionPattern; } @Override public Map<String, String> getContentFormat() { final Map<String, String> result = new HashMap<String, String>(); result.put("structured", "false"); result.put("formatType", "conversion"); result.put("format", conversionPattern); return result; } @Override public String toSerializable(final LogEvent event) { final StringBuilder buf = new StringBuilder(); for (final PatternFormatter formatter : formatters) { formatter.format(event, buf); } String str = buf.toString(); if (replace != null) { str = replace.format(str); } return str; } /** * pattern parser * @param config * @return */ public static PatternParser createPatternParser(final Configuration config) { if (config == null) { return new PatternParser(config, KEY, LogEventPatternConverter.class); } PatternParser parser = config.getComponent(KEY); if (parser == null) { parser = new PatternParser(config, KEY, LogEventPatternConverter.class); config.addComponent(KEY, parser); parser = (PatternParser) config.getComponent(KEY); } return parser; } @Override public String toString() { return conversionPattern; } /** * log4j2 拷贝代码 * Create a pattern layout. * * @param pattern * The pattern. If not specified, defaults to * DEFAULT_CONVERSION_PATTERN. * @param config * The Configuration. Some Converters require access to the * Interpolator. * @param replace * A Regex replacement String. * @param charset * The character set. * @param alwaysWriteExceptions * If {@code "true"} (default) exceptions are always written even * if the pattern contains no exception tokens. * @param noConsoleNoAnsi * If {@code "true"} (default is false) and * {@link System#console()} is null, do not output ANSI escape * codes * @param header * The footer to place at the top of the document, once. * @param footer * The footer to place at the bottom of the document, once. * @return The PatternLayout. */ @PluginFactory public static CustomPatternLayout createLayout( @PluginAttribute(value = "pattern", defaultString = DEFAULT_CONVERSION_PATTERN) final String pattern, @PluginConfiguration final Configuration config, @PluginElement("Replaces") final CustomRegexReplaces replace, @PluginAttribute(value = "charset", defaultString = "UTF-8") final Charset charset, @PluginAttribute(value = "alwaysWriteExceptions", defaultBoolean = true) final boolean alwaysWriteExceptions, @PluginAttribute(value = "noConsoleNoAnsi", defaultBoolean = false) final boolean noConsoleNoAnsi, @PluginAttribute("header") final String header, @PluginAttribute("footer") final String footer) { return newBuilder().withPattern(pattern).withConfiguration(config).withRegexReplacement(replace) .withCharset(charset).withAlwaysWriteExceptions(alwaysWriteExceptions) .withNoConsoleNoAnsi(noConsoleNoAnsi).withHeader(header).withFooter(footer).build(); } /** * Creates a PatternLayout using the default options. These options include * using UTF-8, the default conversion pattern, exceptions being written, * and with ANSI escape codes. * * @return the PatternLayout. * @see #DEFAULT_CONVERSION_PATTERN Default conversion pattern */ public static CustomPatternLayout createDefaultLayout() { return newBuilder().build(); } /** * Creates a builder for a custom PatternLayout. * * @return a PatternLayout builder. */ @PluginBuilderFactory public static Builder newBuilder() { return new Builder(); } /** * Custom PatternLayout builder. Use the {@link PatternLayout#newBuilder() * builder factory method} to create this. */ public static class Builder implements org.apache.logging.log4j.core.util.Builder<CustomPatternLayout> { // FIXME: it seems rather redundant to repeat default values (same goes // for field names) // perhaps introduce a @PluginBuilderAttribute that has no values of its // own and uses reflection? @PluginBuilderAttribute private String pattern = CustomPatternLayout.DEFAULT_CONVERSION_PATTERN; @PluginConfiguration private Configuration configuration = null; @PluginElement("Replaces") private CustomRegexReplaces regexReplacement = null; // LOG4J2-783 use platform default by default @PluginBuilderAttribute private Charset charset = Charset.defaultCharset(); @PluginBuilderAttribute private boolean alwaysWriteExceptions = true; @PluginBuilderAttribute private boolean noConsoleNoAnsi = false; @PluginBuilderAttribute private String header = null; @PluginBuilderAttribute private String footer = null; private Builder() { } // TODO: move javadocs from PluginFactory to here public Builder withPattern(final String pattern) { this.pattern = pattern; return this; } public Builder withConfiguration(final Configuration configuration) { this.configuration = configuration; return this; } public Builder withRegexReplacement(final CustomRegexReplaces regexReplacement) { this.regexReplacement = regexReplacement; return this; } public Builder withCharset(final Charset charset) { this.charset = charset; return this; } public Builder withAlwaysWriteExceptions(final boolean alwaysWriteExceptions) { this.alwaysWriteExceptions = alwaysWriteExceptions; return this; } public Builder withNoConsoleNoAnsi(final boolean noConsoleNoAnsi) { this.noConsoleNoAnsi = noConsoleNoAnsi; return this; } public Builder withHeader(final String header) { this.header = header; return this; } public Builder withFooter(final String footer) { this.footer = footer; return this; } @Override public CustomPatternLayout build() { // fall back to DefaultConfiguration if (configuration == null) { configuration = new DefaultConfiguration(); } return new CustomPatternLayout(configuration, regexReplacement, pattern, charset, alwaysWriteExceptions, noConsoleNoAnsi, header, footer); } } }
3.本地文件显示和控制台显示脱敏日志,需要了解root和logger的区别,configuration中packages的功能,另外理解日志文件大小分包,理解日志格式编写。以下正则表达式包括了对手机号、身份证、姓名、银行卡进行脱敏,包含xml格式和json格式,其中json脱敏对多/"也进行了匹配。
<?xml version="1.0" encoding="UTF-8"?> <configuration status="OFF" packages="com.vc.strong"> <appenders> <Console name="Console" target="SYSTEM_OUT"> <CustomPatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%traceId] [%t] %-5level %logger{1.} - %msg%n "> <replaces> <replace regex="(<IdNo>|<CertId>|<CertID>)(\d{3})\d{11,14}(\w{1}</)" replacement="$1$2**************$3" /> <replace regex="(<UserId>|<FullName>|<UserName>|<AcName>|<CifName>)([\u4E00-\u9FA5]{1})[\u4E00-\u9FA5]{1,}(</)" replacement="$1$2**$3" /> <replace regex="(<MobilePhone>|<BankBindPhone>|<MobileTelephone>|<FamilyTel>)(\d{3})\d{4}(\d{4}</)" replacement="$1$2****$3" /> <replace regex="(<AcNo>|<MyBankAccount>|<LoanAccountNo>|<BackAccountno>|<EAcNo>)(\d{3})\d{10,13}(\d{3}</)" replacement="$1$2*************$3" /> <replace regex="(Phone|mobilePhone|phone|familyTel|holderMobile|mobileTelephone|bankBindPhone|holdermobile)(\\*":\\*")(\d{3})\d{4}(\d{4}\\*")" replacement="$1$2$3****$4" /> <replace regex="(id_card_no|idCardNo|holderIdNo|holder_id_no|idNo|certId|idCard|holderidno|certID)(\\*":\\*")(\d{3})\d{11,14}(\w{1}\\*")" replacement="$1$2$3**************$4" /> <replace regex="(name_pingyin|namePingyin|accountName|account_name|fullName|userId|realName)(\\*":\\*")([\u4E00-\u9FA5]{1})([\u4E00-\u9FA5]{1,})(\\*")" replacement="$1$2$3**$5" /> <replace regex="(card_no|cardNo|acNo)(\\*":\\*")(\d{3})\d{10,13}(\d{3}\\*")" replacement="$1$2$3*************$4" /> </replaces> </CustomPatternLayout> </Console> <RollingFile name="RollingFile" fileName="logs/vcstrong/vcstrong.log" filePattern="logs/vcstrong/vcstrong.log.%i"> <CustomPatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}][%p] [%traceId] [%t|%logger{1.}] - %msg%n"> <replaces> <replace regex="(<IdNo>|<CertId>|<CertID>)(\d{3})\d{11,14}(\w{1}</)" replacement="$1$2**************$3" /> <replace regex="(<UserId>|<FullName>|<UserName>|<AcName>|<CifName>)([\u4E00-\u9FA5]{1})[\u4E00-\u9FA5]{1,}(</)" replacement="$1$2**$3" /> <replace regex="(<MobilePhone>|<BankBindPhone>|<MobileTelephone>|<FamilyTel>)(\d{3})\d{4}(\d{4}</)" replacement="$1$2****$3" /> <replace regex="(<AcNo>|<MyBankAccount>|<LoanAccountNo>|<BackAccountno>|<EAcNo>)(\d{3})\d{10,13}(\d{3}</)" replacement="$1$2*************$3" /> <replace regex="(Phone|mobilePhone|phone|familyTel|holderMobile|mobileTelephone|bankBindPhone|holdermobile)(\\*":\\*")(\d{3})\d{4}(\d{4}\\*")" replacement="$1$2$3****$4" /> <replace regex="(id_card_no|idCardNo|holderIdNo|holder_id_no|idNo|certId|idCard|holderidno|certID)(\\*":\\*")(\d{3})\d{11,14}(\w{1}\\*")" replacement="$1$2$3**************$4" /> <replace regex="(name_pingyin|namePingyin|accountName|account_name|fullName|userId|realName)(\\*":\\*")([\u4E00-\u9FA5]{1})([\u4E00-\u9FA5]{1,})(\\*")" replacement="$1$2$3**$5" /> <replace regex="(card_no|cardNo|acNo)(\\*":\\*")(\d{3})\d{10,13}(\d{3}\\*")" replacement="$1$2$3*************$4" /> </replaces> </CustomPatternLayout> <Policies> <SizeBasedTriggeringPolicy size="512 MB"/> </Policies> <DefaultRolloverStrategy max="20"/> </RollingFile> </appenders> <loggers> <logger name="com.vc.strong" level="DEBUG" additivity="false"> <appender-ref ref="Console" /> <appender-ref ref="RollingFile" /> </logger> <root level="DEBUG"> <appender-ref ref="Console" /> </root> </loggers> </configuration>