import cn.hutool.core.util.DesensitizedUtil;
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 java.nio.charset.Charset;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* log4j1.x重写log4j的PatternLayout,实现日志信息中敏感信息的编码或加密
* log4j2.x重写log4j的AbstractStringLayout,实现日志信息中敏感信息的编码或加密
*
* @author Zyx
* @since 2022/8/24 10:55
*/
@Plugin(name = "Log4jEncodeLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true)
public class Log4jEncodeLayout extends AbstractStringLayout {
/**
* 手机号正则匹配式
* 实测雪花算法生成的Id可能会被手机号正则匹配到,故加了关键正则过滤:
* (?
private final static Pattern PHONE_PATTERN = Pattern.compile("(?);
private PatternLayout patternLayout;
protected Log4jEncodeLayout(Charset charset, String pattern) {
//调用父类设置基本参数
super(charset);
//PatternLayout 是原本的输出对象,用来获取到原本要输出的日志字符串
patternLayout = PatternLayout.newBuilder().withPattern(pattern).build();
}
@Override
public String toSerializable(LogEvent event) {
//调用原本的 toSerializable 方法,获取到原本要输出的日志
String message = patternLayout.toSerializable(event);
//在原本输出的字符串上做正则匹配过滤
Matcher match = PHONE_PATTERN.matcher(message);
StringBuffer sb = new StringBuffer();
while (match.find()) {
match.appendReplacement(sb, DesensitizedUtil.mobilePhone(match.group()));
}
match.appendTail(sb);// 增加
// 将脱敏后的日志输出
return sb.toString();
}
//定义插件传入的参数
@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);
}
}
需要修改两个地方:
Configuration
标签中 packages
参数需指定自定义插件的位置
<Configuration status="INFO" name="XMLConfigTest" packages="org.apache.logging.log4j.test,com.zyx.demo">
<Properties>
<Property name="PATTERN">
%d{yyyy-MM-dd HH:mm:ss SSS} [%p] [c=%c{1}] [%thread] %m%n
Property>
<property name="MODULE_NAME">log4j2-demoproperty>
<property name="LOG_HOME">/dataproperty>
Properties>
<Appenders>
<Console name="STDOUT">
<Log4jEncodeLayout pattern="${PATTERN}" charset="UTF-8"/>
Console>
<RollingFile name="ROLLINGFILE" fileName="${LOG_HOME}/${MODULE_NAME}.log"
filePattern="${LOG_HOME}/log/${MODULE_NAME}-%d{yyyy-MM-dd}-%i.log.gz">
<Log4jEncodeLayout pattern="[${MODULE_NAME}] %d{yyyy-MM-dd HH:mm:ss SSS} [%p] [c=%c{1}] [%thread] %m%n" charset="UTF-8"/>
<Policies>
<TimeBasedTriggeringPolicy modulate="true"
interval="1" />
<SizeBasedTriggeringPolicy size="100MB"/>
<CronTriggeringPolicy schedule="0 0 * * * ?"/>
Policies>
<DefaultRolloverStrategy max="100">
<Delete basePath="${LOG_HOME}" maxDepth="3">
<IfFileName glob="*/${MODULE_NAME}-*.log.gz"/>
<IfLastModified age="30d"/>
Delete>
DefaultRolloverStrategy>
RollingFile>
Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="STDOUT"/>
<AppenderRef ref="ROLLINGFILE"/>
Root>
<Logger name="com.snbc.vems" level="INFO" additivity="false">
<AppenderRef ref="STDOUT"/>
<AppenderRef ref="ROLLINGFILE"/>
Logger>
<Logger name="org.apache" level="WARN" additivity="false" includeLocation="false">
<AppenderRef ref="STDOUT"/>
<AppenderRef ref="ROLLINGFILE"/>
Logger>
<Logger name="org.mybatis" level="WARN" additivity="false" includeLocation="false">
<AppenderRef ref="STDOUT"/>
<AppenderRef ref="ROLLINGFILE"/>
Logger>
<Logger name="org.hibernate" level="WARN" additivity="false" includeLocation="false">
<AppenderRef ref="STDOUT"/>
<AppenderRef ref="ROLLINGFILE"/>
Logger>
<Logger name="org.springframework" level="WARN" additivity="false" includeLocation="false">
<AppenderRef ref="STDOUT"/>
<AppenderRef ref="ROLLINGFILE"/>
Logger>
Loggers>
Configuration>
@Plugin(name = "MyAppender", category = "Core", elementType = "layout", printObject = true)
name
:插件的名称。请注意,此名称不区分大小写category
:用于放置插件的类别。类别名称区分大小写elementType
:此插件所属元素的相应类别的名称,当前扩展所属元素的类别是 layout(layout:日志处理重新输出,appender:日志追加处理,如存入数据库中)。printObject
:指示插件类是否实现 Object.toString()
方法,消息中使用。createAppender
方法,用于 log4j2
在扫描到插件之后根据配置文件中的配置生成插件自定义的插件对象。
@PluginAttribute
:是指插件的属性,如
@PluginAttribute("name") String name
对应的 xml 配置是:
<MyAppender name="MyAppenderTest">Realtimeval>
会取标签内name的值
@PluginElement
:是指插件的子元素,如
@PluginElement("AppenderRef") AppenderRef[] appenderRefs
对应的 xml 配置是:
<MyAppender name="MyAppenderTest">
<AppenderRef ref="AsyncMqLog"/>
<AppenderRef ref="AsyncCONSOLE"/>
MyAppender>
会获取标签下AppenderRef 元素的值,如果是多个AppenderRef 子元素,将会获取都一个数组,可以根据业务需要自定义元素或者属性。
再说一个上下文的问题,log4j2在dao层service层初始化结束之前就已经初始化了,如果采用@Resource这种依赖注入的方式构建bean是行不通的,获取到的只能是null,但是ApplicationContext已经加载,可以通过ApplicationContext手动获取bean。
当我们在 Spring 框架中使用 log4j2 框架时,可能你想要让 Appender 接收外部 spring 容器中的 bean。如果使用 @Autowared 注入获取对象为 null 时,则实现 ApplicationContextAware 接口,通过 ApplicationContext 来直接获取容器中的对象。
自定义 Log 对象的实现类,该类中使用 Log4j2 打印日志,同时配置 Mybatis 使用自定义的 Log 对象,即可实现对 Mybatis 打印的 sql 日志脱敏
public class Log4j2Impl implements Log {
private final Log logImpl;
/**
* Instantiates a new Snbc log 4 j 2.
*
* @param clazz the clazz
*/
public Log4j2Impl(String clazz) {
Logger tmplLog = LogManager.getLogger(clazz);
if (tmplLog instanceof AbstractLogger) {
logImpl = new Log4j2AbstractLoggerImpl((AbstractLogger) tmplLog);
} else {
logImpl = new Log4j2LoggerImpl(tmplLog);
}
}
public boolean isDebugEnabled() {
return true;
}
public boolean isTraceEnabled() {
return true;
}
public void error(String s, Throwable e) {
logImpl.error(s, e);
}
public void error(String s) {
logImpl.error(s);
}
public void debug(String s) {
logImpl.debug(s);
}
public void trace(String s) {
logImpl.trace(s);
}
public void warn(String s) {
logImpl.warn(s);
}
}
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.log4j2.Log4j2Impl