log4j2 java日志脱敏

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>



猜你喜欢

转载自blog.csdn.net/vcstrong/article/details/80527455