Logback、Log4J及slf4J日志框架分析对比及在Spring Boot中的使用

Logback、Log4J及SLF4J日志框架分析对比

一、SLF4J介绍

SLF4J,即简单日志门面(Simple Logging Facade for Java)。从设计模式的角度考虑,它是用来在log和代码层之间起到门面的作用。配置SLF4J是非常简单的一件事,只要将你打算使用的日志系统对应的jar包加入到项目中,SLF4J就会自动选择使用你加入的日志系统。使用slf4j提供的接口,可隐藏日志的具体实现。这与jdbc相似,使用jdbc也就避免了不同的具体数据库。

SLF4J所提供的核心API是一些接口以及一个LoggerFactory的工厂类,SLF4J提供了统一的记录日志的接口,只要按照其提供的方法记录即可,最终日志的格式、记录级别、输出方式等通过具体日志系统的配置来实现,因为在代码实现中用的是接口,因此可以在应用中灵活切换日志系统。

目前来说,Logback是SLF4J的最佳实现。

SLF4J如何做到自动选择日志框架?以log4j为例:

《Logback、Log4J及slf4J日志框架分析对比及在Spring Boot中的使用》

1、首先,通过slf4j-api.jar的Logger log = LoggerFactory.getLogger(xxx);获取日志对象,作为日志的入口,可以通过log.info()等写日志;

2、LoggerFactory类中调用了org.slf4j.impl.StaticLoggerBinder类的getLoggerFactory()方法获取日志工厂ILoggerFactory(slf4j提供的接口)的实现,然后通过日志工厂的实现类的getLogger()方法返回Logger的实现实例,这里的关键就是org.slf4j.impl.StaticLoggerBinder(该类是接口LoggerFactoryBinder的实现类),虽然slf4j-api.jar中的LoggerFactory类使用了这个类,但这个类并不存在于slf4j-api.jar中,所以,只导入了slf4j-api.jar时,编译是会报错的,因为org.slf4j.impl.StaticLoggerBinder根本就不存在;

3、slf4j-log4j12.jar作为适配器,提供了org.slf4j.impl.StaticLoggerBinder类,所以,当我们导入了slf4j-log4j12.jar后,slf4j-api.jar会直接调用slf4j-log4j12.jar中的org.slf4j.impl.StaticLoggerBinder类。logback日志框架实现了slf4j,所以不需要额外的适配器就能与slf4j结合,logback-classic.jar里有org.slf4j.impl.StaticLoggerBinder类,所以,如果同时导入logback-classic.jar和slf4j-log4j12.jar会报错,因为不能确定使用哪个org.slf4j.impl.StaticLoggerBinder类;

4、具体的写日志操作,由适配器调用log4j.jar来完成

因为Logback自身就实现了slf4j,所以不需要像slf4j-log4j12.jar这种适配器,其它日志框架也一样,如果本身就实现了slf4j,则可直接被slf4j使用,如果还没实现slf4j,则需要额外的适配器包,如slf4j-jdk14.jar用于适配java.util.logging。

当然,我们可以不结合slf4j而单独使用log4j,这样只需要导入log4j.jar即可,这样做的坏处有:1、当想替换成其它日志框架时,整个系统的写日志代码都要修改,因为不同的日志框架的实现不同,包括类名、方法名等;2、语法设计角度,slf4j有{}占位符,而log4j需要用“+”来连接字符串,既不利于阅读,同时消耗了内存。

 

二、logback与log4j

logback介绍

Logback是由log4j创始人设计的另一个开源日志组件,意在成为log4j的继承者,Logback比现有的所有日志系统更快,占用内存更小,提供独有且很有用的特性(翻译自官网),官方网站:http://logback.qos.ch。分为下面3个模块:

logback-core:其它两个模块的基础模块,对应logback-core-1.1.11.jar;
logback-classic:它是log4j的一个改良版本,同时它完整实现了slf4j API,使你可以很方便地更换成其它日志系统如log4j(log4j并不实现slf4j,需要通过适配器slf4j-log4j12.jar,所以,在把logback框架换成log4j时,除了要引入log4j.jar还要引入slf4j-log4j12.jar,java代码不用做任何修改)或JDK14 Logging,对应logback-classic-1.1.11.jar;
logback-access:访问模块与Servlet容器集成提供通过Http来访问日志的功能。(不理解)

logback取代log4j的理由:

1、更快的实现:Logback的内核重写了,在一些关键执行路径上性能提升10倍以上,而且初始化内存加载也更小了;

2、自动重新加载配置文件,当配置文件修改了,Logback-classic能自动重新加载配置文件,扫描过程快且安全,它并不需要另外创建一个扫描线程;

3、配置文件可以处理不同的情况,开发人员经常需要判断不同的Logback配置文件在不同的环境下(开发,测试,生产),而这些配置文件仅仅只有一些很小的不同,为了避免重复,logback支持配置文件中的条件处理,只需使用<if>,<then>和<else>,那么同一个配置文件就可以在不同的环境中使用了;

4、Filters(过滤器):有时候,需要诊断一个问题,需要打出日志。在log4j,只有降低日志级别,不过这样会打出大量的日志,会影响应用性能。在Logback,你可以继续保持日志级别而除掉某种特殊情况,如alice这个用户登录,她的日志将打在DEBUG级别而其他用户可以继续打在WARN级别,要实现这个功能只需加4行XML配置(怎样配?);

5、占位符(place holder):logger.debug("Processing trade with id: {} and symbol : {} ",id, symbol);

6、自动清除旧的日志归档文件;

7、自动压缩归档日志文件;

8、Logback-access模块,提供了通过HTTP访问日志的能力,logback-access模块可与Jetty或者Tomcat进行集成,提供了非常丰富而强大的通过HTTP访问日志的功能。

 

LogBack与Log4J性能测试:

LogBack(1.1.11版本)测试结果:

15:22:41.191 [main] INFO com.LogDemo.TestLogBack – 10000条耗时:[482, 387, 496, 382, 496, 424, 496, 420, 533, 427]ms
15:22:41.191 [main] INFO com.LogDemo.TestLogBack – 平均每10000条耗时:454ms

 

Log4J(1.2.17版本)测试结果:

16:03:42.962 [main] INFO  Log4JTest.Log4JTest.TestLog4J – 10000条耗时:[626, 483, 437, 540, 557, 566, 520, 564, 536, 538]ms
16:03:42.962 [main] INFO  Log4JTest.Log4JTest.TestLog4J – 平均每10000条耗时:536ms

看不出有明显的性能差别。

 

Logback源码分析请看“Logback日志框架”中的文章

Logback在Spring Boot中的使用

因为spring-boot-starter包含了spring-boot-starter-logging,故无需要在maven中添加依赖,Spring Boot默认用Logback来记录日志,并用INFO级别输出到控制台,可以直接使用Spring Boot的默认日志配置。

更多配置及使用请看分类“Logback日志框架”中的文章。

一、日志配置

默认日志打印的格式如下:

2017-11-22 10:31:04.283  INFO 6504 — [nio-8888-exec-1] com.LogDemo.controller.UserController    : 用户信息查询,参数:name:活动结束, age:39

• Date and Time—Millisecond precision and easily sortable.
• Log Level—ERROR, WARN, INFO, DEBUG or TRACE.
• Process ID.
• A — separator to distinguish the start of actual log messages.
• Thread name—Enclosed in square brackets (may be truncated for console output).
• Logger name—This is usually the source class name (often abbreviated).
• The log message.

1、通过.properties配置文件配置,如:application.properties

可以配置的属性有:

1)debug=true#以debug模式启动应用

2)通过logging.level.*做级别控制:

        logging.level.root=INFO#所有类日志都以INFO级别输出

        logging.level.com.mypackage=DEBUG#com.mypackage包下所有class以DEBUG级别输出

 

3)文件输出:

        logging.path=D:\\log#在D:\log文件夹生成一个日志文件为 spring.log

        logging.file=my.log#在项目的当前路径下生成一个my.log日志文件

logging.path和logging.file都可以是相对路径或者绝对路径,如若同时使用,则只有logging.file生效。

文件命名规则???

4)日志格式:

???

2、自定义配置

根据不同的日志系统,你可以按如下规则组织配置文件名,就能被正确加载:

  • Logback:logback-spring.xml, logback-spring.groovy, logback.xml, logback.groovy

  • Log4j:log4j-spring.properties, log4j-spring.xml, log4j.properties, log4j.xml

  • Log4j2:log4j2-spring.xml, log4j2.xml

  • JDK (Java Util Logging):logging.properties

Spring Boot官方推荐优先使用带有-spring的文件名作为你的日志配置(如使用logback-spring.xml,而不是logback.xml),命名为logback-spring.xml的日志配置文件,spring boot可以为它添加一些spring boot特有的配置项。上面是默认的命名规则,并且放在src/main/resources下面即可。如果你即想完全掌控日志配置,但又不想用logback.xml作为Logback配置的名字,可以通过logging.config属性指定自定义的名字:

logging.config=classpath:logging-config.xml

虽然一般并不需要改变配置文件的名字,但是如果你想针对不同运行时Profile使用不同的日志配置,这个功能会很有用,有两种实现方案:

1、logging.config属性在application.properties配置文件中,application.properties配有spring.profiles.active=dev(prod或test),可以通过如下方式获取当前环境对应的配置文件:

logging.config=classpath:${spring.profiles.active}/logging-config.xml#不同环境的配置文件放在不同的文件夹下,如dev/logging-config.xml

也可以通过拼接文件名的方式:logging.config=classpath:logging-${spring.profiles.active}-config.xml

2、有多个application.properties文件,如application-dev.properties、application-prod.properties,每个配置文件都有一个logging.config属性,分别指向该环境对应的日志配置文件,名字为application.properties的配置文件中有一个属性spring.profiles.active=dev(prod或test),该方式,spring boot会根据spring.profiles.active的值选择不同的application.properties文件,如application-dev.properties,从而能确定使用哪个日志配置文件。

3、配置文件举例:

1、application.properties:

#配置应用启动环境,这里为开发环境(dev)

spring.profiles.active=dev

2、logback-spring.xml

 

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
	<contextName>logback</contextName>
    <springProperty scope="context" name="app_name" source="spring.application.name"/>
	<springProfile name="test"><!-- 如果不同环境日志存放路径相同,无需套上springProfile节点 -->
		<property name="log.path" value="D:\\test\\" /><!-- 可以放在.properties文件里配置 -->
	</springProfile>
	<springProfile name="prod">
		<property name="log.path" value="D:\\prod\\" />
	</springProfile>
	<!-- 控制台输出 -->
	<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
		<encoder>
			<pattern>%d{HH:mm:ss.SSS} %contextName [${app_name:-}] [%thread] %-5level %logger{36} - %msg%n </pattern>
		</encoder>
	</appender>
	
	<!-- 不带过滤器,能记录所有级别的日志 -->
	<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
		<File>${log.path}/info.log</File>
		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
			<!-- 策略在每次往日志中添加新内容时触发,如果满足条件(每分钟对应一个日志文件),就将
				info.log复制到${log.path}目录并更名为info-2017-11-22_13-15.1.log,并删除原info.log,
				另一种生成新文件的条件是,info.log大小大于maxFileSize时,如果当前这一分钟已经有一个文件了,
				则i加1。通常情况下,日志按天分割,如:${log.path}/info-%d{yyyyMMdd}.%i.log -->
			<fileNamePattern>${log.path}/info-%d{yyyy-MM-dd_HH-mm}.%i.log</fileNamePattern>
			<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">  
				<maxFileSize>10MB</maxFileSize>  
			</timeBasedFileNamingAndTriggeringPolicy>
			<maxHistory>60</maxHistory>
			<totalSizeCap>20GB</totalSizeCap>
		</rollingPolicy>
		<layout class="ch.qos.logback.classic.PatternLayout">
			<pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n </pattern>
		</layout>
	</appender>

	<!-- error级别的文件输出 -->
	<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
		<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
			<level>ERROR</level>
		</filter>
		<File>${log.path}/error.log</File>
		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
			<fileNamePattern>${log.path}/error-%d{yyyy-MM-dd_HH-mm}.%i.log</fileNamePattern>
			<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">  
				<maxFileSize>10MB</maxFileSize>  
			</timeBasedFileNamingAndTriggeringPolicy>
			<maxHistory>60</maxHistory>
			<totalSizeCap>20GB</totalSizeCap>
		</rollingPolicy>
		<layout class="ch.qos.logback.classic.PatternLayout">
			<pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n </pattern>
		</layout>
	</appender>
	<root level="info">
		<appender-ref ref="STDOUT" />
		<appender-ref ref="INFO_FILE" />
		<appender-ref ref="ERROR_FILE" />
	</root>
	
	<springProfile name="dev">
		<logger name="com" level="DEBUG" additivity="false">
			<appender-ref ref="STDOUT" />
<!-- 			<appender-ref ref="INFO_FILE" />
			<appender-ref ref="ERROR_FILE" /> -->
		</logger>
	</springProfile>
	
	<springProfile name="test,prod">
		<logger name="com" level="INFO" additivity="false">
			<appender-ref ref="INFO_FILE" />
			<appender-ref ref="ERROR_FILE" />
		</logger>
	</springProfile>

</configuration>

 

 

说明:

1、<configuration>是配置文件的根节点:

属性contextName用于区分不同应用程序的记录,默认是default,可以通过%contextName来打印日志上下文名称。

属性property用来定义变量值。

2、springProperty 用于获取spring的配置,如上面声明的app_name属性,它的值来自spring.application.name这个配置项,这个配置项可以在配置文件中找到,即应用的名字,然后通过${app_name:-}在日志中输出。

3、子节点appender用来格式化日志输出,有两个属性name和class,class用来指定哪种输出策略,常用就是控制台输出策略和文件输出策略。

4、节点root

root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性,用来设置打印级别,大小写无关。

<root>可以包含0个或多个<appender-ref>元素,表示该logger用哪些appender来输出日志,可以把root看做是一个特殊的logger。

5、节点logger

用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>。只有3个属性:

name:用来指定受此loger约束的某一个包或者具体的某一个类

Level:用来设置打印级别,如果未设置此属性,那么当前loger将会继承上级的级别

Addtivity:是否向上级loger传递打印信息,默认是true

6、节点springProfile,根据不同环境,选择不同的logger节点,同理,也可以套在其它节点的外面

 

从实际使用来讲,只需要两种级别的日志配置:

生产环境:除了debug级别,其他全开,如果生产环境发生了问题,日志应该能够指明原因。

开发环境:编写新代码或是尝试复现问题时,打开全部级别。

测试环境:与生产环境相同。

二、代码中如何使用

更多用法请看分类“Logback日志框架”中的文章

用哪种日志级别记录信息:

debug:任何觉得有利于在调试时更详细的了解系统运行状态的东东,比如变量的值等等,都输出来看看也无妨。

info:这个应该用来反馈系统的当前状态给最终用户的,所以,在这里输出的信息,应该对最终用户具有实际意义,也就是最终用户要能够看得明白是什么意思才行,从某种角度上说,Info 输出的信息可以看作是软件产品的一部分(就像那些交互界面上的文字一样)。

error:捕获到异常或确定执行存在问题时。

使用:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Logger log = LoggerFactory.getLogger(this.getClass());

Logger log = LoggerFactory.getLogger(Xxx.class);

记录普通日志信息:

log.info("用户信息查询,参数:name:{}, age:{}", name,age);

记录异常日志信息:

 

		try {
			Integer x = null;
			++x;
		} catch (Exception e) {
			log.error(e); // A
			log.error(e, e); // B
			log.error("xxxx" + e); // C
			log.error(e.toString()); // D
			log.error(e.getMessage()); // E
			log.error(null, e); // F
			log.error("xxxx", e); // G
			log.error("xxxx{}", e); // H
			log.error("xxxx{}", e.getMessage()); // I
			log.error("Error reading configuration file: " + e.getMessage()); // K
		}

上面的代码,正确输出异常信息的只有G, A和B甚至不能在SLF4J中编译通过,其他的都会丢失异常堆栈信息或者打印了不恰当的信息。这里只要记住一条,在日志中输出异常信息,第一个参数一定是一个字符串,一般都是对问题的描述信息,而不能是异常message(因为堆栈里面会有),第二个参数才是具体的异常实例。

如果结合zipkin,希望获取zipkin的追踪id可以通过以下方式获取traceid和spanid

%X{X-B3-TraceId:-} %X{X-B3-SpanId:-}

 

重要参考资料:

http://www.cnblogs.com/zheting/category/966890.html

 

官方文档:

https://logback.qos.ch/manual/index.html

 

参考资料:

 

https://www.cnblogs.com/warking/p/5710303.html

 

https://www.cnblogs.com/rollenholt/p/3525822.html

 

https://www.cnblogs.com/Sinte-Beuve/p/5758971.html

 

https://www.cnblogs.com/huayu0815/p/5341712.html

 

http://blog.csdn.net/mutouyihao/article/details/45723233

 

http://blog.csdn.net/zhuyouzhi123/article/details/48714079

 

https://www.cnblogs.com/davidwang456/p/4448011.html

 

http://blog.csdn.net/zhuyucheng123/article/details/21524549

 

http://blog.csdn.net/GeekSnow/article/details/53405062

 

 

 









































    原文作者:Spring Boot
    原文地址: https://blog.csdn.net/wudiyong22/article/details/78584128
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞