对于Log4j2大家应该都不是很陌生,听说最多的应该是2021年年底出现的安全漏洞了,不过最让大家头痛的应该不仅仅是这个安全漏洞的处理,安全漏洞通过升级最新的依赖版本即可快速解决,平时在使用过程中遇到过比较多的问题应该就是日志jar包不知道如何选择?日志jar冲突引起的日志不打印问题,日志配置太过复杂不知道如何配置只能百度CV粘贴一个配置。这些日志配置其实并不复杂,主要是因为日志组件的发展历史比较充满曲折,导致了很多地方不兼容。接下来就来通过日志组件的发展历史来入手,看看Log4j2是从什么背景下产生的。
Log4j2日志出现的这些问题多少与它出现的历史有点关系,接下来就先来了解下Java日志发展史,方便我们后续知道引入哪个依赖组件。
对于Java日志打印最开始只有大家熟悉的以System开头如System.out.println("hello world")这样的写法,默认的控制台日志打印方式需要有IO操作,性能极其低效(慎用),功能也太过单一只能简简单单的输出日志。
再后来就有了软件开发者Ceki Gulcu设计出了一套日志库也就是log4j并捐献给了Apache帮助简化日志打印。相关的依赖包是log4j和适配log4j2的桥接包log4j-1.2-api。
Java毕竟还是sun公司(后被oracle收购)的Java,sun公司并没有采纳Log4j提供的标准库,而是在jdk1.4自立一套日志标准JUL(Java Util Logging) JUL并不算强大也没得到普及所以现在大家也很少听说了。相关的依赖是jdk和适配log4j2的桥接包log4j-jul
为了方便开发者进行选择使用,Apache推出了日志门面JCL(Jakarta Commons Logging)可以在运行时绑定日志组件。 相关的依赖是commons-logging和适配log4j2的桥接包log4j-jcl。
前面的竞争促进了日志组件的发展但也逐渐导致日志依赖与配置越来越复杂,2006年Log4j的作者Ceki Gulcu离开了Apache组织后觉得JCL门面不好用,于是自己开发了一版和其功能相似的Slf4j(Simple Logging Facade for Java)这个也是大家所熟悉的日志门面。相关的依赖是slf4j-api和适配log4j2的桥接包og4j-slf4j-impl或者log4j-slf4j2-impl。
后来Slf4j作者又写出了Logback日志标准库作为Slf4j接口的默认实现。
到了2012年,Apache可能看到这样下去要被反超了,于是就推出了新项目Log4j2并且不兼容Log4j,全面借鉴Slf4j+Logback。Apache Log4j 2是对Log4j的升级,它比其前身Log4j 1.x提供了显著的改进,并提供了Logback中可用的许多改进,同时修复了Logback体系结构中的一些固有问题。
了解了日志组件的历史,可以看到最后log4j2集众家之长,那应该如何优雅的使用log4j2日志呢,可以继续往下看。
Log4j 2 旨在用作审计日志记录,被设计为可靠、快速和可扩展,易于理解和使用的框架。简单的来说Log4j2就是一个日志框架,用来管理日志的。
之所以要使用Log4j2 主要还是因为Log4j2 为我们提供了足够好用的支持,下面可以来看下Log4j2的一些特征:
可以看到Log4j2 核心的机制中考虑到了高性能,可扩展,可配置等需求,有效的解决着我们使用日志的痛点,那接下来就来从整体来了解下Log4j2。
下面可以先整体来了解下UML图,这里我用文字的形式标明了日志类型的作用,可以简单了解下。
如果对UML不是非常熟悉的同学看起来可能会比较费劲,不过不用担心下面就针对比较重要的类型详细来说明下,一方面便于了解日志配置,一方面便于后面我们使用。
简单的了解了Log4j2的一些概念之后可能并不是很容易理解一些概念的具体含义,使用起来可能还会比较费劲,那接下来就通过一个简单又完整的入门例子来看下.
为了增加一点点的难度,也贴近一下平时开发使用的诉求,这里就以Log4j2绑定Slf4j的案例来说明,使用Slf4j来作为日志门面,使用Log4j2来实现具体的日志配置与打印。
同时下面的示例会有这样的需求:
下面就来详细看下满足这样5个需求的日志配置是如何实现的吧。
可以先通过如下图来看下Log4j2与Slf4之间的适配需要引入哪些依赖包:
可以看到如果要使用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配置文件中,当然也可以通过在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代码打印日志的示例,同时看下打印效果方便理解。
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();
}
}
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);
}
}
可以看到这个例子充分的满足了前面的5大诉求:
MDC.put("TraceId", "123456");
日志也是我们最常用的观测系统健康状况的方式,优雅的日志打印可以在排查问题的时候事半功倍,在Java日志组件中很多地方使用了日志实现自动扫描的扩展机制,如果随意引入不兼容的依赖包之后被扩展机制扫描到,就很容易出现日志不打印的问题,对于Java 日志依赖的引入,我们可以先了解其曲折的发展历史,了解其依赖来源,然后再针对性的配置依赖即可。然后就是log4j2日志的配置,关于日志的配置官网有非常详细的文档,在使用的时候CV了百度下来的日志配置之后可以参考官网详细的配置,尝试自定义各种属性比如日志追加器append针对日志进行指定位置输出,归档、日志记录器logger针对日志进行分层处理等。如果还有其他问题可以关注微信公众号 《中间件源码》 一起交流吧。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。