Java日志框架
流行的日志框架
- JUL,java.util.logging包下的,是Java原生日志框架
- Log4j,Apache的一个开源项目
- Logback,由Log4j之父做的另一个开源项目,业界中称作log4j后浪,一个可靠、通用且灵活的java日志框架
- Log4j2,Log4j官方的第二个版本,各个方面都是与Logback及其相似,具有插件式结构、配置文件优化等特征,Spring Boot1.4版本以后就不再支持log4j,所以第二个版本应运而生
- 日志门面技术:JCL、SLF4J,用于整合不同日志框架的API,可以更换不同的日志框架而不需要修改代码。
JUL
JUL(Java Util Logging)是java原生的日志框架,使用时不需要另外引用第三方的类库,相对其他的框架使用方便,学习简单,主要是使用在小型应用中。
组成:
- Logger:日志记录器,用于记录日志
- Handler:日志处理器,用于输出日志
- Filter:过滤器,提供了日志级别之外更细粒度的控制
- Formatter:格式化输出
原理:
- 系统启动时,读取默认的logger配置文件,初始化一个单例的LogManager,负责管理所有的Logger,每次用
Logger.getLogger("org.example.xxx")
获取一个logger都会在LogManager里注册。 - 不同的logger之间是有父子关系的,比如
Logger.getLogger("org.example")
是Logger.getLogger("org.example.xxx")
的父logger,那么子logger的日志会发送给父logger的hangler,默认RootLogger的名字是一个空字符串 - logger和hangler都可以设置日志级别,不同时,取高级别的输出,默认RootLogger的级别是INFO,可以在默认的配置文件中看到
- 默认有一个RootLogger,它有一个ConsoleHandler,RootLogger是所有logger的父logger,如果没有
log1.setUseParentHandlers(false)
,日志都会发送给RootLogger的ConsoleHandler
Log4j
Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
组成:
- Loggers:日志记录器,负责收集处理日志记录,实例的命名就是类的全限定名,Logger的名字大小写敏感,其命名有继承机制。Log4J中有一个特殊的logger叫做“root”,他是所有logger的根,可以用Logger.getRootLogger()方法获取。
- Appenders:类似Handler,但提供了更强大的功能,比如允许把日志输出到不同的地方,如控制台(Console)、文件(Files)等,可以根据天数或者文件大小产生新的文件,可以以流的形式发送到其它地方等等。常用Appender:
- ConsoleAppender 将日志输出到控制台
- FileAppender 将日志输出到文件中
- DailyRollingFileAppender 将日志输出到一个日志文件,并且每天输出到一个新的文件
- RollingFileAppender 将日志信息输出到一个日志文件,并且指定文件的尺寸,当文件大小达到指定尺寸时,会自动把文件改名,同时产生一个新的文件
- JDBCAppender 把日志信息保存到数据库中
- Layouts:Log4j可以在Appenders的后面附加Layouts来完成这个功能。Layouts提供四种日志输出样式,如根据HTML样式、自由指定样式、包含日志级别与信息的样式和包含日志时间、线程、类别等信息的样式。常用Layouts:
- HTMLLayout 格式化日志输出为HTML表格形式
- SimpleLayout 简单的日志输出格式化,打印的日志格式如默认INFO级别的消息
- PatternLayout 最强大的格式化组件,可以根据自定义格式输出日志,如果没有指定转换格式, 就是用默认的转换格式
格式说明:
%m 输出代码中指定的日志信息
%p 输出优先级,及 DEBUG、INFO 等
%n 换行符(Windows平台的换行符为 "\n",Unix 平台为 "\n")
%r 输出自应用启动到输出该 log 信息耗费的毫秒数
%c 输出打印语句所属的类的全名
%t 输出产生该日志的线程全名
%d 输出服务器当前时间,默认为 ISO8601,也可以指定格式,如:%d{yyyy年MM月dd日 HH:mm:ss}
%l 输出日志时间发生的位置,包括类名、线程、及在代码中的行数。如:Test.main(Test.java:10)
%F 输出日志消息产生时所在的文件名称
%L 输出代码中的行号
%% 输出一个 "%" 字符
可以在 % 与字符之间加上修饰符来控制最小宽度、最大宽度和文本的对其方式。如:
%5c 输出category名称,最小宽度是5,category<5,默认的情况下右对齐
%-5c 输出category名称,最小宽度是5,category<5,"-"号指定左对齐,会有空格
%.5c 输出category名称,最大宽度是5,category>5,就会将左边多出的字符截掉,<5不
会有空格
%20.30c category名称<20补空格,并且右对齐,>30字符,就从左边交远销出的字符截掉
JCL
JCL(Jakarta Commons Logging)是Apache提供的一个通用日志API。用户可以自由选择第三方的日志组件作为具体实现,像log4j,或者jdk自带的jul, common-logging会通过动态查找的机制,在程序运行时自动找出真正使用的日志库。默认会使用JUL日志框架做日志的记录
使用它的好处就是,代码依赖是common-logging而非log4j的API, 避免了和具体的日志API直接耦合,在有必要时,可以更改日志实现的第三方库。
JCL 有两个基本的抽象类:
- Log:日志记录器
- LogFactory:日志工厂(负责创建Log实例)
SLF4j
-
门面模式
门面模式(Facade Pattern),也称之为外观模式,其核心为:外部与一个子系统的通信必须通过一个统一的外观对象进行,使得子系统更易于使用。
外观模式主要是体现了Java中的一种好的封装性。更简单的说,就是对外提供的接口要尽可能的简单。 -
日志门面
前面介绍的几种日志框架,每一种日志框架都有自己单独的API,要使用对应的框架就要使用其对应的API,这就大大的增加应用程序代码对于日志框架的耦合性。
为了解决这个问题,就是在日志框架和应用程序之间架设一个沟通的桥梁,对于应用程序来说,无论底层的日志框架如何变,都不需要有任何感知。只要门面服务做的足够好,随意换另外一个日志框架,应用程序不需要修改任意一行代码,就可以直接上线。 -
常见日志框架及门面
常见的日志实现:JUL、log4j、logback、log4j2
常见的日志门面 :JCL、slf4j
出现顺序 :log4j -->JUL-->JCL--> slf4j --> logback --> log4j2 -
slf4j
SLF4j(Simple Logging Facade For Java)主要是为了给Java日志访问提供一套标准、规范的API框架,其主要意义在于提供接口,具体的实现可以交由其他日志框架,例如log4j和logback等。 当然slf4j自己也提供了功能较为简单的实现,但是一般很少用到。对于一般的Java项目而言,日志框架会选择slf4j-api作为门面,配上具体的实现框架(log4j、logback等),中间使用桥接器完成桥接。所以我们可以得出SLF4J最重要的两个功能就是对于日志框架的绑定以及日志框架的桥接。
官方网站: https://www.slf4j.org/ -
slf4j桥接技术
通常,我们依赖的某些组件依赖于SLF4J以外的日志API。我们可能还假设这些组件在不久的将来不会切换到SLF4J。为了处理这种情况,SLF4J附带了几个桥接模块,这些模块会将对log4j,JCL和java.util.logging API的调用重定向为行为,就好像是对SLF4J API进行的操作一样。(桥接器是在不改变原有代码的情况使用slf4j) -
适配器
为了让slf4j和它之前的出现的日志框架一起使用,用slf4j作为日志门面,需要使用适配器,针对的是开发中使用slf4j门面,但实际使用旧的实现。如log4j适配slf4j需要使用slf4j-log4j12
。
注意:桥接器与适配器不能同时使用 (桥接器是改写了原来的类 而达到不用修改代码的目的,而适配器是为了适配原来的类 此时肯定会报错。如果放在适配器类的上方报错 下方不生效 没有任何意义 不要同时使用)
使用:
- 加入slf4j核心依赖,再加入其它日志实现框架即可。如slf4j + logback, slf4j + log4j + 适配器slf4j-log4j12
- 如果现有系统使用的是其它日志框架,也可以使用slf4j + 桥接器log4j-over-slf4j实现不改变现有代码就可以使用slf4j
Logback
Logback是由log4j创始人设计的又一个开源日志组件。
Logback当前分成三个模块:logback-core,logback- classic和logback-access。
logback-core是其它两个模块的基础模块。
logback-classic是log4j的一个改良版本。此外logback-classic完整实现SLF4J API。使你可以很方便地更换成其它日志系统如log4j或JDK14 Logging。
logback-access访问模块与Servlet容器集成提供通过Http来访问日志的功能。
组成:
- Logger: 日志的记录器,主要用于存放日志对象,也可以定义日志类型、级别。
- Appender:用于指定日志输出的目的地,目的地可以是控制台、文件、数据库等等。
- Layout: 负责把事件转换成字符串,格式化的日志信息的输出。
- 在Logback中Layout对象被封装在encoder中。也就是说我们未来使用的encoder其实就是Layout
Logback提供了3种配置文件
- logback.groovy
- logback-test.xml
- logback.xml
如果都不存在则采用默认的配置
日志输出格式:
%-10level 级别 案例为设置10个字符,左对齐
%d{yyyy-MM-dd HH:mm:ss.SSS} 日期
%c 当前类全限定名
%M 当前执行日志的方法
%L 行号
%thread 线程名称
%m或者%msg 信息
%n 换行
Log4j2
Apache Log4j 2是对Log4j的升级,它比其前身Log4j 1.x提供了重大改进,并提供了Logback中可用的许多改进,同时修复了Logback架构中的一些问题。被誉为是目前最优秀的Java日志框架。
-
自动重新加载配置
与Logback一样,Log4j2可以在修改时自动重新加载其配置。与Logback不同,它会在重新配置发生时不会丢失日志事件。 -
高级过滤
与Logback一样,Log4j2支持基于Log事件中的上下文数据,标记,正则表达式和其他组件进行过滤。
此外,过滤器还可以与记录器关联。与Logback不同,Log4j2可以在任何这些情况下使用通用的Filter类。 -
插件架构
Log4j使用插件模式配置组件。因此,您无需编写代码来创建和配置Appender,Layout,Pattern Converter等。在配置了的情况下,Log4j自动识别插件并使用它们。 -
无垃圾机制
在稳态日志记录期间,Log4j2 在独立应用程序中是无垃圾的,在Web应用程序中是低垃圾。这减少了垃圾收集器的压力,并且可以提供更好的响应性能。
目前市面上最主流的日志门面就是SLF4J,虽然Log4j2 也是日志门面,因为它的日志实现功能非常强大,性能优越。所以我们一般情况下还是将 Log4j2 看作是日志的实现
SLF4j + Log4j2 的组合,是市场上最强大的日志功能实现方式,绝对是未来的主流趋势。 -
异步日志是log4j2最大的特色,其性能的提升主要也是从异步日志中受益。
Log4j2提供了两种实现日志的方式,一个是通过AsyncAppender,一个是通过AsyncLogger,分别对应前面我们说的Appender组件和Logger组件。注意这是两种不同的实现方式,在设计和源码上都是不同的体现- AsyncAppender方式
是通过引用别的Appender来实现的,当有日志事件到达时,会开启另外一个线程来处理它们。需要注意的是,如果在Appender的时候出现异常,对应用来说是无法感知的。 AsyncAppender应该在它引用的Appender之后配置,默认使用 java.util.concurrent.ArrayBlockingQueue实现而不需要其它外部的类库。 当使用此Appender的时候,在多线程的环境下需要注意,阻塞队列容易受到锁争用的影响,这可能会对性能产生影响。这时候,我们应该考虑使用无锁的异步记录器(AsyncLogger)。 - AsyncLogger方式
AsyncLogger才是log4j2实现异步最重要的功能体现,也是官方推荐的异步方式。它可以使得调用Logger.log返回的更快。你可以有两种选择:全局异步和混合异步。- 全局异步:所有的日志都异步的记录,在配置文件上不用做任何改动,只需要在jvm启动的时候增加一个参数即可实现。
- 混合异步:你可以在应用中同时使用同步日志和异步日志,这使得日志的配置方式更加灵活。虽然Log4j2提供以一套异常处理机制,可以覆盖大部分的状态,但是还是会有一小部分的特殊情况是无法完全处理的,比如我们如果是记录审计日志(特殊情况之一),那么官方就推荐使用同步日志的方式,而对于其他的一些仅仅是记录一个程序日志的地方,使用异步日志将大幅提升性能,减少对应用本身的影响。
混合异步的方式需要通过修改配置文件来实现,使用AsyncLogger标记配置。
- AsyncAppender方式
总结
- 日志框架:jul,log4j,logback, log4j2
- 日志门面:jcl,slf4j
- springboot默认使用slf4j + logback,当前性能最强是slf4j + log4j2
- log4j使用配置文件log4j.properties
- logback使用配置文件logback.xml
- log4j2使用配置文件log4j2.xml、log4j2.component.properties(配置全局异步)
官网: - log4j: https://logging.apache.org/log4j/2.x/
- logback: https://logback.qos.ch/
- slf4j: https://www.slf4j.org/
依赖导入总结
出现顺序:log4j -->JUL-->JCL--> slf4j --> logback --> log4j2
日志门面
- jcl日志门面,如果没有找到日志实现,默认使用jul
<!--apache jcl 日志门面-->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
- sl4j日志门面
<!--slf4j 日志门面 核心依赖-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
日志实现
- jul,jdk自己的实现,不需要导包
- slf4j自己的简单实现,使用时也必须导包才能使用
<!--slf4j 的简单实现-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.25</version>
</dependency>
<!--slf4j nop 不记录日志 要起作用必须放在其他日志依赖的前面 slf4j优先使用先导入的
(slf4j-simple/slf4j-nop/log4j/logback) 如果任何日志实现都没有导入 也是默认NOP (即不记录日志)-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>1.7.25</version>
</dependency>
- logback,直接引入logback-classic接口,直接实现了slf4j,配置文件:logback.xml
<!--logback 核心依赖 不需要适配-->
<!--已经包含logback-core slf4j-api-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
- log4j,一般不单独使用日志框架实现,都要使用门面,配置文件:log4j.properties。reload4j,解决后续的log4j漏洞版本,slf4j+log4j可以直接使用sl4fj-reload4j(原来是名字是slf4j-log4j12)
<!--log4j-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
- log4j2,它有自己的日志门面,相比于log4j,增加了门面(所以artifactId是log4j-api而不是log4j2-api,它俩一家的),核心包是log4j-core版本从2开始,配置文件:log4j2.xml,log4j2.component.properties
<!--log4j2的日志门面-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.17.1</version>
</dependency>
<!--log4j2的日志实现-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.17.1</version>
</dependency>
<!--log4j2异步日志-->
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.3.7</version>
</dependency>
- (log4j适配slf4j)slf4j+log4j,log4j是在slf4j之前出现的,这个适配是slf4自己做的
<!--log4j适配slf4j的依赖 包含了log4j + sl4j 只导这一个就可以用slf4j + log4j-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
- (log4j2适配slf4j)slf4j+log4j2,它是sl4j之后出现的,但是它有自己的门面,也可以适配到slf4j,这个适配是log4j2做的,使用这个依赖就可以直接使用log4j2和sl4j了
<!--log4j2适配slf4j的适配器
包含了log4j-api和log4j-core slf4j-api
= slf4j + log4j2
-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.17.1</version>
</dependency>
- (jul适配sfl4j)slf4j+jul,jul出现在slf4j之前,这个适配是slf4j自己做的
<!--jul适配slf4j的依赖 不用导实现包 jdk自带-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.7.25</version>
</dependency>
- (log4j桥接slf4j)使用桥接器兼容旧的代码(注意不能与适配器一起使用)
<!--log4j 重构为 slf4j + log4j 使用的桥接器 会自动把原来log4j的api换成slf4j的-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>1.7.25</version>
</dependency>
- log4j到log4j2的适配器
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-1.2-api</artifactId>
<version>2.18.0</version>
<scope>test</scope>
</dependency>
- (log4j2日志门面桥接sl4j)log4j-to-slf4j,把log4j2 api日志门面的日志绑定到slf4j门面上,日志实现使用slf4j实际绑定的
- 不能和log4j-slf4j-impl一起使用,它两逻辑是反的,log4j-slf4j-impl是slf4j把日志交给log4j实现,log4j-to-slf4j是把日志交给slf4j门面,循环了,也即是适配器和桥接器不能同时使用,也没有意义
- log4j-over-slf4j,把log4j的日志桥接到slf4j门面上,日志实现使用其他slf4j绑定的
- jcl-over-slf4j,把旧代码中的jcl日志门面的日志桥接到slf4j上,日志实现使用其他slf4j绑定的
- jul-to-slf4j,把旧代码中的jul日志桥接到slf4j上,日志实现使用其他slf4j绑定的
- slf4j-log4j12,log4j对slf4j的适配,log4j对slf4j的一种实现,使用slf4j门面,日志实现使用log4j
- slf4j实现的,因为slf4j出现时帮现有的这些日志都做了适配
- log4j-slf4j-impl,log4j2对slf4j的适配,使用slf4j门面,日志实现使用log4j2
- log4j2实现的,log4j2是在slf4j后出现的,自己实现了slf4j接口
slf4j家的日志模块:
<modules>
<module>slf4j-api</module>
<module>slf4j-simple</module>
<module>slf4j-nop</module>
<module>slf4j-jdk14</module>
<module>slf4j-log4j12</module>
<module>slf4j-jcl</module>
<module>slf4j-android</module>
<module>slf4j-ext</module>
<module>jcl-over-slf4j</module>
<module>log4j-over-slf4j</module>
<module>jul-to-slf4j</module>
<module>osgi-over-slf4j</module>
<module>integration</module>
<module>slf4j-site</module>
<module>slf4j-migrator</module>
</modules>
桥接器、适配器理解
假设代码中实际使用的日志实现是A,使用桥接器就是把A的日志桥接给slf4j(想用slf4j门面及其他实现又不想改代码),然后实际使用slf4j绑定的实现(不是A,否则就不用桥接了),使用适配器就是实际使用日志实现A,但是想用slf4j门面的api而又不用修改代码中使用的日志实现A的代码(想用slf4j门面的api,但是不改实际的日志实现)
桥接器是为了兼容旧的代码,适配器是旧的日志实现为了使用新的门面,如slf4j。
如果使用桥接器,即把具体实现logApi桥接给slf4j,实际实现由slf4j去绑定想要使用的log实现。适配器是把具体实现api适配给slf4j
使用logback + slf4j
- 加入logback-classic依赖,使用logback日志实现,logback天生实现了slf4j
- 如果需要servlet的访问日志,需要加入logback-access
- 其他桥接器,把旧代码都桥接到slf4j上
- jul,jul-to-slf4j
- log4j,log4j-over-slf4j
- log4j2,log4j-to-slf4j
- jcl,jcl-over-slf4j
本文来自博客园,作者:Bingmous,转载请注明原文链接:https://www.cnblogs.com/bingmous/p/15786789.html