大家好,我是tin,这是我的第5篇原创文章

本文讲述在思考对业务零碎代码入侵最小的状况下实现日志脱敏的计划原理。文章很长,包含了日志脱敏起由、编码实现、log4j2.xml文件加载原理、log4j2的插件机制等,最初还抖出注解编译处理器AbstractProcessor,实现编译期动静生成代码!有点像捡到宝,毕竟以前没关注过注解编译处理器,先上一个目录:

  • 一、为什么做日志脱敏
  • 二、log4j2日志脱敏编码实现
  • 三、源码摸索log4j2日志脱敏实现原理
    1、什么是slf4j?
    2、log4j2又是什么?
    3、slf4j和log4j2是如何实现绑定的?
    4、log4j.xml配置文件是如何加载的?
    5、咱们定义log4j2的Plugin插件又是如何加载注册的?
    6、AbstractProcessor注解处理器
  • 四、敌人请留步

一、为什么做日志脱敏

日志打印十分常见且重要,这毋庸置疑,但有这样一种状况:咱们打印的日志蕴含了用户的隐衷信息,比方做登录领取的打印用户账号和明码,做金融的打印用户的卡号等,这些日志先不说放在磁盘上治理不当可能造成用户隐衷泄露,再者就算是合规查看,它也是不过关的,必须要做解决整治。

咱们打日志是怎么打的?先上一个图(日志中打印我的用户名和账号),看看咱们本人就是这么用的:

没做非凡解决,不出意外,日志输入是这样的:

卡号打印进去了,随后这行日志就安详地躺在咱们服务器磁盘上。

二、log4j2日志脱敏编码实现

如何借助日志框架实现对账号打码脱敏,而不入侵业务代码?废话不多说,先看看我已实现的成果:

本文基于slf4j+log4j2实现,咱们代码日志输入处没有任何改变,打打印进去的日志对卡号做了打码脱敏。

本文实现日志打码脱敏的计划波及开发的中央有两个:

一是实现log4j2的RewritePolicy接口,重写logEvent;

二是批改log4j2.xml文件。

看看我写的RewritePolicy实现类:

log4j2.xml批改,上面是log4j2配置和rewrite配置:

这个文件也十分具体地把log4j2.xml配置解释了一遍,不是很分明log4j配置的可留图保留啦。

为了不便复制,把log4j2.xml配置的源码粘贴一份进去:

<?xml version="1.0" encoding="UTF-8"?><configuration monitorInterval="5"><!--变量配置--><Properties>    <!-- 格式化输入:%date示意日期,HH:mm:ss.SSS示意日期格局,%thread示意线程名,%-5level示意级别从左显示5个字符宽度,    %C{1.}示意类全限定名输出精度,%-4L输入日志所在行行号,%msg代表日志音讯,%n是换行符-->    <property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %C{1.} %-4L : %msg%n"/>    <!-- 定义日志存储的门路.${web:rootDir}示意以后工程目录, -->    <property name="FILE_PATH" value="../log/tin-example"/>    <property name="FILE_NAME" value="tin-example"/></Properties><appenders>    <!--控制台输入-->    <console name="Console" target="SYSTEM_OUT">        <!--输入日志的格局-->        <PatternLayout pattern="${LOG_PATTERN}"/>        <!--示意输入level=debug级别及以上日志(onMatch),debug级别以下不输入(onMismatch)-->        <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>    </console>    <Rewrite name="rewrite">        <DataMaskingRewritePolicy/>        <AppenderRef ref="Console"/>    </Rewrite>    <!-- 打印出所有级别的日志信息,并主动滚动存档-->    <RollingFile name="AllLevelRollingFile" fileName="${FILE_PATH}/${FILE_NAME}.log"                 filePattern="${FILE_PATH}/${FILE_NAME}-ALL-%d{yyyy-MM-dd}_%i.log.gz">        <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="ACCEPT"/>        <PatternLayout pattern="${LOG_PATTERN}"/>        <Policies>            <!--interval属性用来指定多久滚动一次,interval=1示意1小时滚动一次-->            <TimeBasedTriggeringPolicy interval="1"/>            <!--size=20示意文件大于20M滚动一次-->            <SizeBasedTriggeringPolicy size="20MB"/>        </Policies>        <!-- max=15示意同文件夹下最多10个文件,再多则会笼罩,DefaultRolloverStrategy如不设置,则默认为7个-->        <DefaultRolloverStrategy max="10"/>    </RollingFile>    <!-- 打印出所有error及以上级别的信息,并主动滚动存档-->    <RollingFile name="ErrorRollingFile" fileName="${FILE_PATH}/error.log"                 filePattern="${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz">        <!--输入level及以上级别的信息(onMatch),level以下间接回绝(onMismatch)-->        <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>        <PatternLayout pattern="${LOG_PATTERN}"/>        <Policies>            <!--interval属性用来指定多久滚动一次,interval=1示意1小时滚动一次-->            <TimeBasedTriggeringPolicy interval="1"/>            <!--size=20示意文件大于20M滚动一次-->            <SizeBasedTriggeringPolicy size="20MB"/>        </Policies>        <!-- max=15示意同文件夹下最多10个文件,再多则会笼罩,DefaultRolloverStrategy如不设置,则默认为7个-->        <DefaultRolloverStrategy max="10"/>    </RollingFile></appenders><!--Logger节点用来独自指定日志的模式,能够给不同包配置不同的日志打印策略。--><loggers>    <logger name="com.tin.example.spring.log4j2" level="info" additivity="false">        <AppenderRef ref="rewrite"/>    </logger>    <root level="debug">        <appender-ref ref="Console"/>        <appender-ref ref="AllLevelRollingFile"/>        <appender-ref ref="ErrorRollingFile"/>    </root></loggers></configuration>

三、源码摸索log4j2日志脱敏原理

为何上文这么做就能实现日志打码脱敏?是有什么变法么?实现的原理是什么?背着一大连串疑难,当初咱们从slf4j和log4j2原理说起,来了,搬好凳子了。

1、什么是slf4j?

slf4j全称simple logging facade for Java。是一个日志接口框架,配合日志输入零碎实现日志输入。slf4j并不是真正输入日志的零碎,只是定义了一套日志标准。相似这样的日志门面还有commons-logging。

private static final Logger LOGGER = LoggerFactory.getLogger(AccountTest.class);

以上的Logger就是slf4j的类。

2、log4j2又是什么?

log4j2才是一个真正的日志零碎,它才是咱们我的项目中打印日志的代码库实现。除了log4j2,咱们常见的日志库还有log4j、logback、jdk-logging。

slf4j作为连贯log和代码层的中间层,咱们只有应用slf4j提供的接口,不必关怀日志的具体实现(想想这样的益处是咱们能够把业务零碎内日志库比方log4j2换为logback也没问题)。说起来有点像jdbc,咱们切换不同的数据库,jdbc帮咱们做好了兼容。

log4j2的依赖包有3个,slf4j和log4j2的几个jar包关系作用如下:

3、slf4j和log4j2是如何实现绑定的?

从下面图都看到了,slf4j-api和log4j相干的包基本不在一起,那么它们之间是通过什么关联的?

答案是:

slf4j指定门路进行类加载,log4j必然有桥接实现类。 还是从这行定义和初始化Logger的代码开始看起:

private static final Logger LOGGER = LoggerFactory.getLogger(AccountTest.class);

从LoggerFactory.getLogger始终进入到LoggerFactory类的bind办法,找到staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet(),这里即是slf4j实现绑定log4j2的中央:

findPossibleStaticLoggerBinderPathSet()通过指定门路加载一个StaticLoggerBinder类:

指定查找org/slf4j/impl/StaticLoggerBinder.class进行加载。

那么StaticLoggerBinder应该在哪里?

当然是在log4j2包内了!

通过StaticLoggerBinder这个类即实现了slf4j和log4j的绑定,看下图。

绑定完之后即通过getLoggerFactory办法获取到Log4jLoggerFactory:

log4j2和slf4j实现了绑定,那么,和本文所说的脱敏原理有什么关系?

脱敏的实现原理真正在于log4j2,以上只是开展阐明日志零碎的根本关联原理,为接下来讲述log4j的插件机制打个铺垫。

log4j2 通过应用插件机制加载各种组件,比方appender, logger等,咱们的脱敏计划编码定义了一个类:

实现了log4j的rewrite策略类,这其实就是一种插件!

要讲清楚Plugin原理得分两局部讲。

一是log4j.xml配置文件是如何加载的;

二是咱们定义的Plugin插件又是如何加载注册的。

4、log4j.xml配置文件是如何加载的?

咱们仍然是通过断点看源码,毕竟,源码底下无谎话! 还是从上面这行代码开始看起:

上文曾经提到过Log4jLoggerFactory,它继承了AbstractLoggerAdapter这个抽象类,咱们间接进入到getContext办法获取Logger的中央:

anchor中文译为"锚",这里是通过Java反射失去日志类,anchor不为null,因而进入到前面的语句。

进入getContext,咱们的Log4jContextFactory又呈现了,它在LogManager中的动态代码块中已初始化好。

咱们持续到Log4jContextFactory内看getContext:

已初始化好的selector,外部具体如何获取context有趣味可自行debug,咱们进入到ctx.start办法内:

看到reconfigure()办法,就晓得log4j筹备开始加载配置了!!!再从reconfigure始终往下看:

687行获取失去一个XmlConfiguration,这是因为咱们应用的是xml配置文件!!!失常来说配置文件除了xml,还有properties,yaml,json等。

此处既然已取得配置文件的内容, 那么咱们退回去看ConfigurationFactory.getInstance().getConfiguration(this,contextName,configURI,cl)。

看看XmlConfigurationFactory类

指定了xml后缀,getConfiguration理论返回XmlConfiguration

依据configSource的log4jx.xml文件,进行配置内容加载。

到这里xml配置就算是加载实现啦。xml外面定义的<DataMaskingRewritePolicy/>标签也会被加载。

接下来,自然而然的咱们就会问,这个标签和代码@Plugin注解定义的插件是如何关联起来的?或者又说Plugin插件是如何加载的?

5、咱们定义的Plugin插件又是如何加载注册的?

log4j中的Plugin注解提供了一种便捷的办法将一个类申明成 log4j2 的插件,比方我单测用到的案例:

在log4j2加载上下文的时候会加载Plugin,log4j对立用PluginRegistry注册核心加载和注册插件,并由PluginManager来治理。

进入到PluginManager:

正文都写得很分明,从指定的指定文件Log4j2Plugin.dat加载插件,持续进入loadFromMainClassLoader办法

不同模块不同jar包都有可能存在Log4j2Plugins.dat文件,比方log4j-core包存在

咱们本人编写代码定义的插件则被编译到target目录下(因为我的是mac,在控制台的看得,win零碎也一样找到编译产生的target文件夹即可)

咱们编译生成的Log4j2Plugins.dat外面的内容又是什么呢?

文件记录了插件分类、全限定类名等信息。

说到这里,产生新的一个疑难,咱们本人的Log4j2Plugins.dat 文件到底是如何被生成到target目录下的?

6、AbstractProcessor注解处理器

这不得不说咱们的注解编译处理器咯!注解分为两种类型,一种是运行时注解,另一种是编译时注解。编译时注解的外围要依赖APT(Annotation Processing Tools)实现,基本原理就是在类、办法、字段等上增加注解,在编译时,编译器通过扫描AbstractProcessor的子类,把对应适合的注解传入process函数,而后咱们开发人员能够在编译期进行相应的逻辑解决了。看看log4j实现的注解编译处理器:

咱们平时编码很少会用到注解编译处理器,有趣味可自行写单元测试试一试,这种没玩过的代码写起来还挺乏味的。不过自行写的话须要申明好javax.annotation.processing.Processor文件,再补一张log4j申明的文件:

四、敌人请留步

我是tin,一个在致力让本人变得更优良的一般攻城狮。经历无限、学识肤浅,如你有发现文章不妥之处,十分欢送加我提出,我肯定仔细斟酌加以批改。

看到这里请安顿个激励再走吧,保持原创不容易,你的正反馈是我保持输入的最弱小能源,谢谢啦。

总结、晋升
做一个高兴的攻城狮
构筑属于本人的一方天地