注:日志脱敏经过大量上线项目测试,脱敏平均时间在30毫秒左右,短数据基本不占用时间;另外大数据超量数据耗损时间请关注自己程序日志打印优化和对象转json耗时,或log4j异步打印,请移步百度。使用前请@Test测试,
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 == 0) {
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 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 getContentFormat() {
final Map result = new HashMap();
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 {
// 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);
}
}
}
注:闲来无事,今天(2018-06-19)看了一下博文,发现csdn的代码插入器,把html/xml的转义字符自动给修改了,我嘞个擦,而且插入的代码还不方便修改,草草草草草,下面replace正则表达式中,请把'<'换成'<',把'"'换成'"',更换完之后如下面插图。
3.本地文件显示和控制台显示脱敏日志,需要了解root和logger的区别,configuration中packages的功能,另外理解日志文件大小分包,理解日志格式编写。以下正则表达式包括了对手机号、身份证、姓名、银行卡进行脱敏,包含xml格式和json格式,其中json脱敏对多/"也进行了匹配。
另附(测试打印):