java日志框架梳理

前言

市面上的java日志框架,有JUL、JCL、Jboss-logging、logback、log4j、log4j2、slf4j;看着是不有一丝凌乱,其实分类后就会清晰很多,主要分两类:

日志门面日志实现
JCL(Jakarta Commons Logging)、SLF4j(Simple Logging Facade for Java)、jboss-loggingLog4j、JUL(java.util.logging)、Log4j2、Logback

日志门面:门面模式即外观模式,面向接口编程,只提供API定义,没有提供具体实现。目的是为应用层提供标准化的使用方式。
日志实现:具体的日志实现类,提供对日志的收集/管理功能。

为什么需要日志接口,直接使用具体的实现不就行了吗?

接口用于定制规范,可以有多个实现,使用时是面向接口的(导入的包都是slf4j的包而不是具体某个日志框架中的包),即直接和接口交互,不直接使用实现,所以可以任意的更换实现而不用更改代码中的日志相关代码。

比如:slf4j定义了一套日志接口,项目中使用的日志框架是logback,开发中调用的所有接口都是slf4j的,不直接使用logback,调用是 自己的工程调用slf4j的接口,slf4j的接口去调用logback的实现,可以看到整个过程应用程序并没有直接使用logback,当项目需要更换更加优秀的日志框架时(如log4j2)只需要引入Log4j2的jar和Log4j2对应的配置文件即可,完全不用更改Java代码中的日志相关的代码logger.info(“xxx”),也不用修改日志相关的类的导入的包(import org.slf4j.Logger; import org.slf4j.LoggerFactory;)

使用日志接口便于更换为其他日志框架

日志框架发展历程

log4j,1999年,Apache基金会最早实现的一套日志框架,从此成为日志的标准并广泛为java程序员所使用,在Java1.4之前只有这一种选择。

JUC,2002年Java1.4发布,Sun公司也在JDK1.4内置了Logging机制(java.util.logging,以下简称JUL),但基本上是模仿Log4j的实现。有点儿鸡肋,但最起码解决了有无的问题。从此开发者有了两种选择。

JCL:随后Apache推出了J.C.L(commons-logging),因为前边有了两种选择,使用有些混乱,而JCL的出现,终结了这种混乱,它定义了一套日志接口,支持运行时动态加载日志组件。应用层编写代码时,只需要使用J.C.L提供的统一接口来记录日志,在程序运行时会优先找系统是否集成Log4j,如果集成则使用Log4j做为日志实现,如果没找到则使用J.U.L做为日志实现

Slf4j,JCL+log4j,风靡了一段时候后,JCL之后就没有了更新。2006年,Log4j的作者Ceki Gülcü离开Apache后,又搞出来一套类似J.C.L的接口类,就是Slf4j。原因是作者觉得J.C.L这套接口设计的不好,容易让开发者写出有性能问题的代码。Slf4j做为一套标准接口,可以实现无缝与多种实现框架进行对接。它也是现在比较常用的日志集成方式。

logback,在搞出来Slf4j之后,Ceki Gülcü又顺带开发了Logback,做为Slf4j的默认实现。在功能完整度和性能上,Logback超越了所有已有的日志实现框架。

log4j2,是log4j 1.x和logback的改进版,据说采用了一些新技术(无锁异步、等等),使得日志的吞吐量、性能比log4j 1.x提高10倍,并解决了一些死锁的bug,而且配置更加简单灵活。

日志框架选择
我们常使用日志框架是SLF4j、Log4j、Logback等,而这些也正是被各大框架所钦定的,比如spring框架默认使用JCL,springBoot底层是Spring,但在springBoot包装的时候,spring-boot-starter-logging采用了SLF4J和Logback。

如果是在一个新的项目中建议使用Slf4j与Logback组合,Slf4j实现机制决定Slf4j限制较少,使用范围更广。Logback拥有更好的性能。

SLF4J在系统中的使用方式
在这里插入图片描述
SLF4j官方给出的图示如上:
正确的用法是第二种及以后的使用,应用程序面向 slf4j编程,调用他的方法进行日志记录。并且程序也导入了日志实现的jar包,虽然我们调用的是slf4j的接口,但是logback等日志实现会为我们实现日志记录,将日志记录到控制台或日志文件

第三种,slf4j绑定log4g,应用程序依然面向统一抽象层slf4j,然后使用log4j的时候,再引入一个中间适配层slf4j-log412.jar,适配层实现了抽象层的日志记录方法,在方法里边,进行真正日志记录的时候,又调了log4j的api,所以使用log4j,系统需要导入三个jar包

如果要从java.util.logging切换到log4j,只需将slf4j-jdk14-1.7.28.jar替换为slf4j-log4j12-1.7.28.jar。SLF4J不依赖任何特殊类的装载机,不会直接访问任何类加载器。实际上,每个SLF4J绑定在编译时都进行了硬连线,以使用一个且仅一个特定的日志记录框架。

桥接类jar包说明
logback-classic-1.0.13.jar(requires logback-core-1.0.13.jar)Slf4j的原生实现,Logback直接实现了Slf4j的接口,因此使用Slf4j与Logback的结合使用也意味更小的内存与计算开销
slf4j-log4j12-1.7.13.jaLog4j1.2版本的桥接器
slf4j-jdk14-1.7.13.jarjava.util.logging的桥接器,Jdk原生日志框架。
slf4j-nop-1.7.13.jarNOP桥接器,默默丢弃一切日志。
slf4j-jcl-1.7.13.jarJakarta Commons Logging 的桥接器. 这个桥接器将Slf4j所有日志委派给Jcl
slf4j-simple-1.7.13.jar一个简单实现的桥接器,该实现输出所有事件到System.err. 只有Info以及高于该级别的消息被打印,在小型应用中它也许是有用的。

如何让系统中所有框架的日志统一到slf4j?

场景说明, 比如系统A要使用slf4j+logback

  • 系统A依赖于spring框架,spring框架依赖commons-logging
  • 系统A依赖于Hebernate框架,Hebernate框架依赖jboss-logging
  • 系统A依赖于其他框架,其他框架依赖其他日志实现

那么如何统一日志记录,让其他框架和系统A一起统一使用slf4j进行输出?

  • 将系统中其他日志框架先排除出去—排除出去,该框架会因为找不到排除出去的日志实现jar报错
  • 用中间包来替换原有的日志框架
  • 然后导入系统选择用的slf4j其他的实现

slf4j给出的实现方式
在这里插入图片描述
比如第一种,slf4j+logback,但是依赖的其他框架依赖于其他日志实现(commons-logging等),我们可以使用中间桥接类jar包jcl-over-slf4j-version.jar,将commons-loggingjar替换,这样就可以实现统一

桥接类jar包说明
log4j-over-slf4j-version.jar将Log4j重定向到Slf4j
jcl-over-slf4j-version.jar将Commons Logging里的Simple Logger重定向到
slf4jjul-to-slf4j-version.jar

切换日志框架

springboot默认使用slf4j+logback spring模式使用log4j logback比log4j更强大,所以springboot切换日志框架可能会将logback切换为log4j2。
logback切换为log4j2:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring‐boot‐starter‐web</artifactId>
	排除spring‐boot‐starter‐logging
	<exclusions>
		<exclusion>
			<artifactId>spring‐boot‐starter‐logging</artifactId>
			<groupId>org.springframework.boot</groupId>
		</exclusion>
	</exclusions>
</dependency>
引入spring‐boot‐starter‐log4j2
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring‐boot‐starter‐log4j2</artifactId>
</dependency> 

logback.xml

		<?xml version="1.0" encoding="UTF-8" ?>
		<!--scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。-->
		<!--scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。-->
		<!--debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false-->
		<configuration scan="true" scanPeriod="60 seconds" debug="false">
		
		    <!--输出格式-->
		     <--name="consoleLog":输出到控制台-->
		    <appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender">
		        <layout class="ch.qos.logback.classic.PatternLayout">
		            <pattern>
		        <!--%d:时间   %level:级别   %thread:哪个线程打出来的    %caller:哪个类多少行   msg:消息   %n:换行-->
		                %d{yyyy/MM/dd-HH:mm:ss} %level [%thread] %caller{1} - %msg%n
		            </pattern>
		        </layout>
		    </appender>
		        <!--p6spy:控制台打印sql-->
		    <appender name="p6spyConsoleAppender" class="ch.qos.logback.core.ConsoleAppender">
		        <layout class="ch.qos.logback.classic.PatternLayout">
		            <pattern>
		                 %msg%n
		            </pattern>
		        </layout>
		    </appender>
		        <!--p6spy:打印sql到log文件    RollingFileAppender:按天或时间滚动-->
		    <appender name="p6spyFileAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
		        <encoder>
		            <pattern>
		                %msg%n
		            </pattern>
		        </encoder>
		        <!--滚动策略-->
		        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
		            <!--路径    %d:按天滚动-->
		            <fileNamePattern>logs/game-p6spy.%d.log</fileNamePattern>
		        </rollingPolicy>
		    </appender>
		
		    <!--输出error文件-->
		    <appender name="fileErrorLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
		        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
		            <level>ERROR</level>
		        </filter>
		        <encoder>
		            <pattern>
		                %d{yyyy/MM/dd-HH:mm:ss} %level [%thread] %caller{1} - %msg%n
		            </pattern>
		        </encoder>
		        <!--滚动策略-->
		        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
		            <!--路径-->
		            <fileNamePattern>logs/game-provider-error.%d.log</fileNamePattern>
		        </rollingPolicy>
		    </appender>
		
		    <!--输出info文件-->
		    <appender name="fileInfoLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
		        <filter class="ch.qos.logback.classic.filter.LevelFilter">
		            <!--过滤error日志-->
		            <level>ERROR</level>
		            <onMatch>DENY</onMatch>
		            <onMismatch>ACCEPT</onMismatch>
		        </filter>
		        <encoder>
		            <pattern>
		                %d{yyyy/MM/dd-HH:mm:ss} %level [%thread] %caller{1} - %msg%n
		            </pattern>
		        </encoder>
		        <!--滚动策略-->
		        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
		            <!--路径-->
		            <fileNamePattern>logs/game-provider-info.%d.log</fileNamePattern>
		        </rollingPolicy>
		    </appender>
		
		    <appender name="addIntegralLogAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
		        <encoder>
		            <pattern>
		                %d{yyyy/MM/dd-HH:mm:ss} %level [%thread] %caller{1} - %msg%n
		            </pattern>
		        </encoder>
		        <!--滚动策略-->
		        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
		            <!--路径-->
		            <fileNamePattern>logs/add-integral.%d.log</fileNamePattern>
		        </rollingPolicy>
		    </appender>
		
		     <!-- name="p6spy" -> 将控制p6spy包下的所有类的日志的打印
	  		 	  level="INFO"  -> 打印级别为info及高于info的日志
 				  additivity="false  -> 默认为true,将此logger的打印信息向上级root传递,false不传递,在本logger配置的appender处理-->
		    <logger name="p6spy" level="INFO" additivity="false">
		        <appender-ref ref="p6spyConsoleAppender"/>
		        <appender-ref ref="p6spyFileAppender"/>
		    </logger>
		    <logger name="com.dmsdbj.integral.game.provider.service.impl.AddIntegralServiceImpl" level="INFO" additivity="false">
		        <appender-ref ref="addIntegralLogAppender"/>
		        <appender-ref ref="consoleLog"/>
		    </logger>
		
		    <logger name="com.xxl.job.core" level="WARN" />
		
		    <!--将root的打印级别设置为“info”,当打印日志时,root将级别为 info及高于info的日志信息已经配置好的appender处理,consoleLog打印到控制台,fileErrorLog打印到error文件,fileInfoLog打印到info文件-->
		    <root level="info">
		        <appender-ref ref="consoleLog"/>
		        <appender-ref ref="fileErrorLog"/>
		        <appender-ref ref="fileInfoLog"/>
		    </root>
		</configuration>

参考文章:
日志发展故事
logback 配置详解
java日志框架

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木子松的猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值