Java常用日志框架总结

你好,我是猿java。

前言

作为java程序员,在工作开发中遇到最多的一个问题就是打日志(log),好的日志方式可以帮助你事半功倍的监控线上程序运行的链路,出现bug时可以快速定位,但是,面对现如今众多的日志框架中,如何去选择哪个日志框架,成为困扰很多程序员的一个问题,他们的性能怎们样,他们有什么关系,今天我们就来把一把log的那些事…

log框架及发展史

  1. Log4j: apache基于java的日志框架

  2. Log4j2: apache基于log4j的升级版本

  3. JUL: 2002年,java 1.4发布,定义了java标准的日志格式,用java.until.logging日志框架来打印日志,但是貌似不太好用(模仿了log4j)

  4. Apache Commons Logging: apache旗下的门面日志

  5. Slf4j: (simple log facade for java 简单的java日志门面),即它不负责写日志,而是「提供用一个统一的接口,通过jar来决定使用的日志框架」

  6. Logback: 相对于Slf4j,性能更高、功能更全,logback 分为三个模块:logback-core,logback-classic和logback-access。

  • logback-core:模块为其他两个模块的基础。

  • logback-classic:模块可以被看做是log4j的改进版本。此外,logback-classic 本身实现了 SLF4J API,因此可以在 logback 和其他日志框架(如 log4j 或 java.util.logging(JUL))之间来回切换。

  • logback-access:模块与 Servlet 容器(如 Tomcat 和 Jetty)集成,以提供 HTTP 访问日志功能。

日志框架的使用

下面代码展示了几个日志框架使用的大概雏形:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 使用log4j,需要log4j.jar
import org.apache.log4j.Logger;
Logger logger_log4j = Logger.getLogger(Test.class);
logger_log4j.info("Hello World!");

// 使用log4j2,需要log4j-api.jar、log4j-core.jar
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
Logger logger_log4j2 = LogManager.getLogger(Test.class);
logger_log4j2.info("Hello World!");

// logback,需要logback-classic.jar、logback-core.jar
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
Logger logger_logback = new LoggerContext().getLogger(Test.class);
logger_logback.info("Hello World!");

// java.until.logging,简称jul
import java.util.logging.Logger;
Logger logger_jul = Logger.getLogger("java.Test");

如上方式是直接使用某种日志框架,一但需要变更日志框架,改动的点比较大,因此有没有一种方法可以让用户在很少改动代码的情况下灵活的变更日志框架,答案是:有!

现如今,java的两大log阵营:Common logging 阵营(jakarta common logiing,JCL) 和 slf4j阵营,JCL和slf4j属于日志接口,提供统一的日志操作规范,大体使用方式如下:

img_1.png

下面我们就以slf4j为例来讲解下日志门面的使用:

slf4j日志门面

slf4j官网: http://www.slf4j.org/manual.ht
img_1.png

官方说明(官网是最好的学习资料),特别建议大家详读slf4j官方文档

Declaring project dependencies for logging

Given Maven’s transitive dependency rules, for “regular” projects (not libraries or frameworks) declaring logging dependencies can be accomplished with a single dependency declaration.

LOGBACK-CLASSIC If you wish to use logback-classic as the underlying logging framework, all you need to do is to declare “ch.qos.logback:logback-classic” as a dependency in your pom.xml file as shown below. In addition to logback-classic-1.2.3.jar, this will pull slf4j-api-1.7.28.jar as well as logback-core-1.2.3.jar into your project. Note that explicitly declaring a dependency on logback-core-1.2.3 or slf4j-api-1.7.28.jar is not wrong and may be necessary to impose the correct version of said artifacts by virtue of Maven’s “nearest definition” dependency mediation rule.

1
2
3
4
5
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>

解释:使用logback作为日志框架,只要maven中加入logback-classic依赖,就会把slf4j-api-1.7.28.jarlogback-core-1.2.3.jar同时拉取下来,因为logback-classic的pom里面依赖了2个jar

LOG4J If you wish to use log4j as the underlying logging framework, all you need to do is to declare “org.slf4j:slf4j-log4j12” as a dependency in your pom.xml file as shown below. In addition to slf4j-log4j12-1.7.28.jar, this will pull slf4j-api-1.7.28.jar as well as log4j-1.2.17.jar into your project. Note that explicitly declaring a dependency on log4j-1.2.17.jar or slf4j-api-1.7.28.jar is not wrong and may be necessary to impose the correct version of said artifacts by virtue of Maven’s “nearest definition” dependency mediation rule.

1
2
3
4
5
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.28</version>
</dependency>

解释:使用log4j作为日志框架,只要maven中加入slf4j-log4j12依赖,同时会拉取slf4j-api-1.7.28.jar 和 log4j-1.2.17.jar两个jar包,当然在maven中显示的增加这两个jar的依赖也是没有问题的

JAVA.UTIL.LOGGING If you wish to use java.util.logging as the underlying logging framework, all you need to do is to declare “org.slf4j:slf4j-jdk14” as a dependency in your pom.xml file as shown below. In addition to slf4j-jdk14-1.7.28.jar, this will pull slf4j-api-1.7.28.jar into your project. Note that explicitly declaring a dependency on slf4j-api-1.7.28.jar is not wrong and may be necessary to impose the correct version of said artifact by virtue of Maven’s “nearest definition” dependency mediation rule.

1
2
3
4
5
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.7.28</version>
</dependency>

解释:使用JUL作为日志框架,只要maven中加入slf4j-jdk14依赖,同时会拉取*slf4j-api-1.7.28.jar包,当然在maven中显示的增加这两个jar的依赖也是没有问题的

对于slf4j这种日志门面的使用,我们可以在代码中配置如下的通用模式,然后具体下面使用何种日志框架取决于用户的日志跨框架依赖

1
2
3
4
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Logger logger = LoggerFactory.getLogger(Test.class);
logger.info("Hello World!")

通过上面官方的说明,我们可以知道只要在业务代码里面接入一套统一的日志模板,然后引入日志框架以及框架和slf4j的桥接依赖,这样就能灵活的变更日志框架,那么什么是桥接包?先让来让我们看看slf4j从LoggerFactory.getLogger()开始,到底干了什么。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public static Logger getLogger(Class<?> clazz) {
Logger logger = getLogger(clazz.getName());
if (DETECT_LOGGER_NAME_MISMATCH) {
Class<?> autoComputedCallingClass = Util.getCallingClass();
if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) {
Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(),
autoComputedCallingClass.getName()));
Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation");
}
}
return logger;
}

private final static void bind() {
try {
Set<URL> staticLoggerBinderPathSet = null;
// skip check under android, see also
// http://jira.qos.ch/browse/SLF4J-328
if (!isAndroid()) {
staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
}
// the next line does the binding
StaticLoggerBinder.getSingleton();
INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
reportActualBinding(staticLoggerBinderPathSet);
} catch (NoClassDefFoundError ncde) {
}

通过查看源码,我们可以看出,slf4j的原理就是就是让ClassLoader从classpath(依赖的jar)中找到「StaticLoggerBinder」这个类,然后利用他来返回log4j、logback中的Logger,然后打印日志。所谓的桥接包,就是实现StaticLoggerBinder类,用来连接slf4j和日志框架

img_1.png

门面日志和设计模式中的外观模式如出一辙,本身不提供服务,为子系统提供统一的入口,封装子系统的复杂性,便于客户端调用。slf4j就像是菜鸟驿站,本身没有快递服务,但是提供顺丰、中通等快递服务,至于你想用顺丰还是用中通,完全取决于你的想法。

学习交流

如果你觉得文章有帮助,请帮忙转发给更多的好友,或关注公众号:猿java,持续输出硬核文章。

drawing