简介

对于Log4j2咱们应该都不是很陌生,听说最多的应该是2021年年末呈现的安全漏洞了,不过最让咱们头痛的应该不只仅是这个安全漏洞的处理,安全漏洞经过升级最新的依靠版别即可快速处理,平常在运用过程中遇到过比较多的问题应该便是日志jar包不知道怎么挑选?日志jar抵触引起的日志不打印问题,日志装备过分杂乱不知道怎么装备只能百度CV张贴一个装备。这些日志装备其实并不杂乱,主要是由于日志组件的开展前史比较充满曲折,导致了许多当地不兼容。接下来就来经过日志组件的开展前史来下手,看看Log4j2是从什么布景下发生的。

前史

Log4j2日志呈现的这些问题多少与它呈现的前史有点联系,接下来就先来了解下Java日志开展史,便利咱们后续知道引进哪个依靠组件。

System.out

对于Java日志打印最开端只需咱们熟悉的以System最初如System.out.println(“hello world”)这样的写法,默许的控制台日志打印方法需要有IO操作,功用极端低效(慎用),功用也过分单一只能简简略单的输出日志。

Log4j

再后来就有了软件开发者Ceki Gulcu设计出了一套日志库也便是log4j并捐献给了Apache帮助简化日志打印。相关的依靠包是log4j和适配log4j2的桥接包log4j-1.2-api。

JUL(Java Util Logging)

Java毕竟仍是sun公司(后被oracle收购)的Java,sun公司并没有采纳Log4j供给的规范库,而是在jdk1.4自立一套日志规范JUL(Java Util Logging) JUL并不算强大也没得到遍及所以现在咱们也很少听说了。相关的依靠是jdk和适配log4j2的桥接包log4j-jul

JCL (Jakarta Commons Logging)

为了便利开发者进行挑选运用,Apache推出了日志门面JCL(Jakarta Commons Logging)能够在运行时绑定日志组件。 相关的依靠是commons-logging和适配log4j2的桥接包log4j-jcl。

Slf4j

前面的竞争促进了日志组件的开展但也逐步导致日志依靠与装备越来越杂乱,2006年Log4j的作者Ceki Gulcu离开了Apache安排后觉得JCL门面不好用,所以自己开发了一版和其功用类似的Slf4j(Simple Logging Facade for Java)这个也是咱们所熟悉的日志门面。相关的依靠是slf4j-api和适配log4j2的桥接包og4j-slf4j-impl或许log4j-slf4j2-impl。

Logback

后来Slf4j作者又写出了Logback日志规范库作为Slf4j接口的默许完成。

Log4j2

到了2012年,Apache或许看到这样下去要被反超了,所以就推出了新项目Log4j2并且不兼容Log4j,全面借鉴Slf4j+Logback。Apache Log4j 2是对Log4j的升级,它比其前身Log4j 1.x供给了明显的改善,并供给了Logback中可用的许多改善,一起修复了Logback体系结构中的一些固有问题。

了解了日志组件的前史,能够看到最终log4j2集众家之长,那应该怎么高雅的运用log4j2日志呢,能够持续往下看。

架构阐明

定位

Log4j 2 旨在用作审计日志记载,被设计为可靠、快速和可扩展,易于了解和运用的结构。简略的来说Log4j2便是一个日志结构,用来管理日志的。

特征

之所以要运用Log4j2 主要仍是由于Log4j2 为咱们供给了足够好用的支撑,下面能够来看下Log4j2的一些特征:

  • API分离: API 与完成是分隔的。

  • 改善的功用: 支撑Disruptor 异步日志记载。

  • 支撑多种API: 供给对 Log4j 1.2、SLF4J、Commons Logging 和 java.util.logging (JUL) API 的支撑。

  • 无侵入性: 经过扩展机制主动加载,无需与代码完全耦合,代码中能够运用SLF4J门面

  • 插件架构: 插件化装备, 主动识别插件并在装备引证它们,极高的可扩展性

  • 特点装备支撑: 能够在装备中引证特点,Log4j 将直接替换它们,特点来自装备文件中界说的值、体系特点、环境变量、ThreadContext Map 和事情中存在的数据。

  • 无垃圾与低垃圾 :稳态日志记载期间,Log4j 2在独立运用程序中是无垃圾的,Web 运用程序中是低垃圾的。

能够看到Log4j2 中心的机制中考虑到了高功用,可扩展,可装备等需求,有效的处理着咱们运用日志的痛点,那接下来就来从全体来了解下Log4j2。

架构

下面能够先全体来了解下UML图,这儿我用文字的形式标明了日志类型的作用,能够简略了解下。

可观测性之Log4j2优雅日志打印

假如对UML不是十分熟悉的同学看起来或许会比较费劲,不过不必忧虑下面就针对比较重要的类型详细来阐明下,一方面便于了解日志装备,一方面便于后边咱们运用。

  • LoggerContext(日志上下文) : 这个就像是Spring的ApplicationContext 充当着容器的上下文环境,Spring能够一起存在运用上下文,Web上下文,Log4j2运用也能够一起有多个 LoggerContext

  • Configuration (装备): 每个 LoggerContext 都有一个活动的 Configuration。Configuration 包括一切 Appenders、context-wide Filters、LoggerConfigs 并包括对 StrSubstitutor 的引证

  • Logger(记载器): 用于让运用者打印日志运用,能够为每个类创立不同的日志记载器,Logger 本身不履行任何直接操作。它只需一个称号并与 LoggerConfig 相相关由日志完成依据装备来进行打印日志。

  • LoggerConfig(记载器装备): LoggerConfig目标是在日志记载装备中声明Logger时创立的。LoggerConfig包括一组筛选器Filter,这些筛选器有必要答应LogEvent在传递给任何Appender之前经过。它包括对运用于处理事情的一组Appender的引证。

  • Log Levels (日志等级): LoggerConfigs 将被分配一个 Log Level 内置等级集包括 ALL、TRACE、DEBUG、INFO、WARN、ERROR、FATAL 和 OFF。Log4j 2 还支撑自界说日志等级 ,下表阐明了等级过滤的工作原理。在表中,垂直标题显现 LogEvent 的等级,而水平标题显现与适当的 LoggerConfig 相关的等级。交集标识是否答应 LogEvent 经过以进行进一步处理 (Yes) 或丢掉 (No)。

  • Filter(筛选器): 除了如上一节所述发生的主动日志等级过滤之外,Log4j 还供给了 Filter,能够在控制权传递给任何 LoggerConfig 之前、在控制权传递给 LoggerConfig 之后但在调用任何 Appender 之前、在控制权被履行之后运用。

  • Appender(追加器): Log4j 答应记载请求打印到多个目的地。在 log4j 中,输出目的地称为 Appender。多个 Appender 能够附加到一个 Logger。目前,存在用于控制台、文件、长途套接字服务器等日志的追加

  • Layout(布局): 通常情况下,用户不只希望自界说输出目标,还希望自界说输出格局。这是经过将 Layout 与 Appender 相相关来完成的。Layout 担任依据用户的意愿格局化 LogEvent,而 appender 担任将格局化的输出发送到其目的地。PatternLayout是规范 log4j 发行版的一部分,它答应用户依据类似于 C 语言printf函数的转化形式指定输出格局。

  • StrSubstitutor 和 StrLookup: StrSubstitutor 类和 StrLookup 接口是从 Apache Commons Lang借来的,然后进行了修改以支撑评估 LogEvents。另外 插值器 类是从 Apache Commons Configuration 借来的,以答应 StrSubstitutor 评估来自多个 StrLookups 的变量。它也被修改为支撑评估 LogEvents。这些一起供给了一种机制,答应装备引证来自体系特点、装备文件、ThreadContext Map、LogEvent 中的 StructuredData 的变量。假如组件能够处理变量,则能够在处理装备时或在处理每个事情时解析变量。

简略的了解了Log4j2的一些概念之后或许并不是很容易了解一些概念的详细意义,运用起来或许还会比较费劲,那接下来就经过一个简略又完好的入门比方来看下.

开发入门

为了添加一点点的难度,也靠近一下平常开发运用的诉求,这儿就以Log4j2绑定Slf4j的事例来阐明,运用Slf4j来作为日志门面,运用Log4j2来完成详细的日志装备与打印。

一起下面的示例会有这样的需求:

  • 过错日志打印: 将error日志等级的日志额定打印到error.log里边便利问题排查。

  • 事务日志打印: 将坐落link.elastic包及其子包下的一切日志打印到logger.log日志里边。

  • 非事务日志打印: 假如不满意link.elastic的包的日志则打印到控制台。

  • 日志归档: 一切的日志文件都要具有归档战略比方按日期每天归档,或许文件超越250MB也要归档。

  • 链路追寻Id打印: 详细的日志打印能够在Java代码中设置链路追寻Id TraceId打印日志的时分能够将其打印出来。

下面就来详细看下满意这样5个需求的日志装备是怎么完成的吧。

依靠引进

能够先经过如下图来看下Log4j2与Slf4之间的适配需要引进哪些依靠包:

可观测性之Log4j2优雅日志打印

能够看到假如要运用Slf4j门面的话,需要引进一个Slf4j门面依靠包slf4j-api和一个与log4j2绑定slf4j的桥接包log4j-slf4j-impl,下面就来看下咱们要引进的依靠:

 <dependencyManagement>    <dependencies>       <!--经过BOM物料清单来引进log4j2的版别-->        <dependency>            <groupId>org.apache.logging.log4j</groupId>            <artifactId>log4j-bom</artifactId>            <version>2.19.0</version>            <scope>import</scope>            <type>pom</type>        </dependency>     </dependencies></dependencyManagement><dependencies>    <!--log4j2 API包-->    <dependency>        <groupId>org.apache.logging.log4j</groupId>        <artifactId>log4j-api</artifactId>    </dependency>    <!--log4j2中心完成包-->    <dependency>        <groupId>org.apache.logging.log4j</groupId>        <artifactId>log4j-core</artifactId>    </dependency>    <!--用于log4j2与slf4j桥接-->    <dependency>        <groupId>org.apache.logging.log4j</groupId>        <artifactId>log4j-slf4j-impl</artifactId>    </dependency>    <!-- slf4j门面包-->    <dependency>        <groupId>org.slf4j</groupId>        <artifactId>slf4j-api</artifactId>        <version>1.7.25</version>    </dependency></dependencies>

入门示例

日志装备log4j2.xml

在Log4j2中日志的装备文件是大部分情况下是经过装备日志的xml文件来收效的,这个装备文件的途径默许是在类的根途径下的log4j2.xml装备文件中,当然也能够经过在JVM参数中指定一个其它方位的日志装备途径,详细参数装备的KEY为log4j.configurationFile,接下来就在maven项目的根目录src/main/resources目录下创立一个log4j2.xml装备文件来让装备默许收效,详细装备内容如下:

<?xml version="1.0" encoding="UTF-8"?><Configuration>    <!--特点装备-->    <Properties>        <!--日志打印格局-->        <Property name="PATTERN">%date{HH:mm:ss.SSS} [%thread] %X{TraceId} %-5level %logger{36} - %msg%n</Property>        <!--日志等级-->        <Property name="LEVEL">INFO</Property>    </Properties>    <!--日志追加器装备-->    <Appenders>        <!--可滚动归档文件的日志追加器,这儿装备的是Error等级的日志能够打印到error.log文件中        一起依据日期(天)和巨细(最大250MB)进行文件归档-->        <RollingFile name="ErrorFile" fileName="error.log"                     filePattern="$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">            <!--日志文件打印格局-->            <PatternLayout>                <Pattern>${PATTERN}</Pattern>            </PatternLayout>            <!--日志文件归档战略-->            <Policies>                <!--依据日期格局按时刻归档-->                <TimeBasedTriggeringPolicy/>                <!--依据文件巨细归档超越250MB就归档-->                <SizeBasedTriggeringPolicy size="250 MB"/>            </Policies>            <!--日志过滤器-->            <Filters>                <!--阈值过滤器,日志等级大于等于ERROR的接纳其他的都回绝-->                <ThresholdFilter level="ERROR" onMatch="ACCEPT" onMi**atch="DENY"/>            </Filters>        </RollingFile>        <!--日志logger文件能够接纳一切等级的日志打印-->        <RollingFile name="LoggerFile" fileName="logger.log"                     filePattern="$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">            <!--日志布局 -->            <PatternLayout>                <Pattern>${PATTERN}</Pattern>            </PatternLayout>            <!--归档战略-->            <Policies>                <!--依据日期格局按时刻归档-->                <TimeBasedTriggeringPolicy/>                <!--依据文件巨细归档超越250MB就归档-->                <SizeBasedTriggeringPolicy size="250 MB"/>            </Policies>        </RollingFile>        <!--打印日志到控制台的日志追加器-->        <Console name="STDOUT" target="SYSTEM_OUT">            <!--日志布局格局-->            <PatternLayout pattern="${PATTERN}"/>        </Console>    </Appenders>    <!--日志记载器装备-->    <Loggers>        <!-- 记载器的日志姓名,这个日志记载器的姓名与咱们每个类里边获取的Logger目标对应,        对应的联系便是经过这个name来匹配的,匹配规矩一般是满意Logger装备的name前缀,        每个logger元素的日志上下文中都存在一个LoggerConfig装备目标来管理装备-->        <Logger name="link.elastic" additivity="false">            <!-- LoggerConfig 也能够装备一个或多个 AppenderRef 元素,            在处理日志记载事情时将调用它们中的每一个-->            <!--logger.log文件日志追加器-->            <AppenderRef ref="LoggerFile"/>            <!--error等级的error.log追加器-->            <AppenderRef ref="ErrorFile"/>        </Logger>        <!--  每个装备都有必要有一个根记载器。前面的Logger日志装备器未匹配到则走默许的根记载器        假如未装备默许根 LoggerConfig,其等级为 ERROR 并附加了控制台附加程序,将被运用。        根记载器和其他记载器之间的主要区别是:        1.根记载器没有称号特点。        2.根记载器不支撑可加性特点,由于它没有父记载器-->        <Root level="${LEVEL}">            <!--控制台追加器-->            <AppenderRef ref="STDOUT"/>        </Root>    </Loggers></Configuration>

为了全面的了解咱们前面介绍了一些日志相关的概念,这儿在引进日志装备的时分尽或许的相关到更多的元素,引进了日志装备之后,下面能够来看Java代码打印日志的示例,一起看下打印作用便利了解。

Java打印日志示例

坐落link.elastic包下包括main函数的发动类源码如下:

package link.elastic.biz;​import com.demo.DemoLog;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.slf4j.MDC;​/** * Hello world! */public class App {    static Logger logger = LoggerFactory.getLogger(App.class);​    public static void main(String[] args) {        //设置日志上下文MDC        MDC.put("TraceId", "123456");        logger.debug("Hello World!");        logger.info("Hello World!");        logger.warn("Hello World!");        logger.error("Hello World!");        new DemoLog().log("Hello World!");        MDC.clear();    }}

坐落其他(非link.elastic)包下的DemoLog日志打印示例:

package com.demo;​import org.slf4j.Logger;import org.slf4j.LoggerFactory;​public class DemoLog {    Logger logger = LoggerFactory.getLogger(DemoLog.class);​    public void log(String s) {        logger.debug(s);        logger.info(s);        logger.warn(s);        logger.error(s);    }}

日志打印作用

控制台日志(非link.elastic包的日志)

可观测性之Log4j2优雅日志打印

logger.log中的日志(link.elastic包下的日志)

可观测性之Log4j2优雅日志打印

error.log中的日志

可观测性之Log4j2优雅日志打印
能够看到这个比方充沛的满意了前面的5大诉求:

  • 过错日志打印: 这儿运用了阈值过滤器ThresholdFilter,日志等级大于等于ERROR的接纳打印其他的都回绝

  • 事务日志打印: 这儿咱们单独装备了日志记载器Logger并将其name特点设置为了link.elastic只需Java代码中的日志记载器满意前缀为link.elastic就会将日志打印到这个文件里边,在Java代码中咱们的日志记载器的姓名为link.elastic.biz.App 是满意link.elastic的前缀的所以会将日志打印到logger.log里边。

  • 非事务日志打印: 对于不满意link.elastic的包比方这儿的包名为com.demo下的日志是无法匹配到前面事务日志打印的日志记载器的就只能走Root这个根日志记载器,这个根日志记载器的追加器装备的是控制台,前面控制台打印的日志便对错link.elastic包下的日志打印。

  • 日志归档: 这儿或许没有很明显的展示由于要满意日期格局或许巨细,日期归档运用的是TimeBasedTriggeringPolicy 这个战略依据filePattern中的日期来进行归档最小的时刻咱们设置的是日会再每天0点之后发生新日志的时分进行归档,巨细归档设置的是SizeBasedTriggeringPolicy。

  • 链路追寻Id打印: 对于链路追寻体系往往不只仅会将链路信息输送到第三方链路追寻体系也会将链路信息打印控制台一份, 这儿咱们运用的是字符串替换器,在日志打印格局中设置获取链路追寻id的获取方法%X{TraceId} ,然后在Java代码中将链路追寻Id放入日志诊断上下文MDC中即可如代码: MDC.put("TraceId", "123456");

总结

日志也是咱们最常用的观测体系健康状况的方法,高雅的日志打印能够在排查问题的时分事半功倍,在Java日志组件中许多当地运用了日志完成主动扫描的扩展机制,假如随意引进不兼容的依靠包之后被扩展机制扫描到,就很容易呈现日志不打印的问题,对于Java 日志依靠的引进,咱们能够先了解其曲折的开展前史,了解其依靠来历,然后再针对性的装备依靠即可。然后便是log4j2日志的装备,关于日志的装备官网有十分详细的文档,在运用的时分CV了百度下来的日志装备之后能够参考官网详细的装备,测验自界说各种特点比方日志追加器append针对日志进行指定方位输出,归档、日志记载器logger针对日志进行分层处理等。假如还有其他问题能够重视微信大众号 《中间件源码》 一起沟通吧。