前言
最近Apache Log4j出现了“核弹级”漏洞,趁着这个机会参考各类博客,梳理清楚java中主流的日志框架和之间的关系
log发展历史
最开始,大家都是使用System.out.println控制台打印来检查程序输出是否符合自己的预期,该方式比较原始,无法自动区分日志的类型,很难应用到生产系统中;
从JDK1.4开始提供java.util.logging日志框架来打印日志,但大佬觉得JUL太难用了,于是手撸了个log4j,后来log4j发现了安全漏洞,加上代码结构问题难以维护,于是从1.2就停止更新log4j,并重新撸了个log4j2,再后来,大佬又撸了一个性能更高、功能更全的logback。
大佬构建了log的世界,创造了最常见的日志框架:log4j、log4j2、logback
常见日志框架的使用方式
log4j
- 测试代码
import org.apache.log4j.Logger;
public class Log4jDemo {
public static void main(String[] args) {
// 使用log4j,依赖log4j.jar
// log4j不提供默认配置,需要在适当位置添加log4j.properties或log4j.xml,否则会出现“No appenders could be found for logger”告警信息
// 参考:https://blog.csdn.net/qq_37502106/article/details/86659947
Logger logger_log4j = Logger.getLogger(Log4jDemo.class);
logger_log4j.info("Hello World");
}
}
- log4j.properties配置
log4j.rootLogger=DEBUG, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
log4j2
- 测试代码
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Log4j2Demo {
public static void main(String[] args) {
// 使用log4j2,需要log4j-api.jar、log4j-core.jar
// log4j2存在默认的配置,ERROR级别,输出到console上
// https://www.jianshu.com/p/a344409cf08a
// log4j2可以使用log4j2.xml、yml、jsn等多中配置文件格式
Logger logger_log4j2 = LogManager.getLogger(Log4j2Demo.class);
logger_log4j2.error("Hello World");
}
}
- log4j2.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" monitorInterval="5">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
</Console>
</Appenders>
<Loggers>
<Logger name="org.apache.logging.log4j" level="ERROR" additivity="true">
<AppenderRef ref="File" />
</Logger>
<Root level="INFO">
<AppenderRef ref="Console" />
</Root>
</Loggers>
</Configuration>
logback
- 测试代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LogBackDemo {
public static void main(String[] args) {
// https://blog.csdn.net/u011493218/article/details/82997000
// 按照相关博客内容,可以不用配置logback.xml,会使用默认配置打印在console中,实际情况是
// 只有使用slf4j的方式,才能进行自动配置,否则不会
// 具体类调用为:StaticLoggerBinder --> ContextInitializer.autoConfig --> BasicConfigurator
// 下列方式即使配置了logback.xml也不能打印出日志;
// Logger logger_logback = new LoggerContext().getLogger(LogBackDemo.class);
// logger_logback.info("Hello World!");
// logback必须和slf4j一起使用
Logger logger_logback = LoggerFactory.getLogger(LogBackDemo.class);
logger_logback.info("Hello World");
}
}
- logback.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
<!-- 日志输出级别 -->
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
SLF4J(Simple Logging Facade for Java)
根据上述案例,发现使用不同的日志框架,就要引入不同的jar包,使用不同的代码获取Logger,如果项目后期需要升级,会导致大量的class需要修改,因此引入Slf4j
门面系统的作用
早起Apache Commons Logging出现,Common-logging提供一个日志入口,称为”门面日志“,即它不负责写日志,而是提供一个统一的接口,通过jar来决定使用的日志框架。log4j的大佬嫌弃Common-logging难用,开发了门面日志框架slf4j
slf4j使用方式
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Logger logger = LoggerFactory.getLogger(Test.class);
logger.info("Hello World!")
slf4j如何决定使用哪个框架日志呢,并且引入哪些jar包呢?
如图就是slf4j和日志框架的组合依赖结构图,使用slf4j需要首先导入「slf4j-api.jar」
- 和log4j配合,需要导入「log4j.jar」,以及桥接包「slf4j-log412.jar」。
- log4j2配合需要导入log4j2的「log4j-api.jar」、「log4j-core.jar」和桥接包「log4j-slf4j-impl.jar」
- logback只需要导入「logback-classic.jar」和「logback-core.jar」即可,不需要桥接包
什么是桥接包,为什么logback没有
先让来让我们看看slf4j从LoggerFactory.getLogger()开始,到底干了什么。
流程如下:
原理就是就是让ClassLoader从classpath(依赖的jar)中找到「StaticLoggerBinder」这个类,然后利用他来返回log4j、logback中的Logger,然后打印日志。
所谓的桥接包,就是实现StaticLoggerBinder类,用来连接slf4j和日志框架。因为log4j和log4j2刚开始没有StaticLoggerBinder这个类,为了不改变程序结构,只能重新写一个新的jar来实现StaticLoggerBinder。而logback出现slf4j之后,于是在logback本身的jar中实现了StaticLoggerBinder,所以就不需要桥接包。
StaticLoggerBinder实现了使用底层日志框架创建Logger的功能,各自的StaticLoggerBinder为slf4j提供的Logger,再提供给用户打印日志。
log4j和log4j2桥接包及logback依赖里,都有StaticLoggerBinder类。
使用相关注意项
“Class path contains multiple SLF4J bindings.”
在使用slf4j的时候会遇到以上的报告信息。我也曾遇到过web服务因为slf4j问题启动失败。究其根本是因为logback-classic、log4j-slf4j-impl、slf4j-log412、slf4j-jdk这些jar不能同时存在。他们都实现了StaticLoggerBinder类而导致冲突,slf4j无法确定到底用哪个日志框架。
参考博客:
https://mp.weixin.qq.com/s/8eVxJoQx7YmiI2o0mOnRQg