认识日志框架
log4j,log4j2,logback,slf4j,他们之间都有什么关系
Log4j、Log4j2、Logback 和 SLF4J 是 Java 日志记录的主要框架和工具,它们之间存在一定的关系和区别。今天一起来聊聊他们。
日志组件是我们平时开发过程中必然会用到的组件。在系统中正确的打印日志至少有下面的这些好处:
- 调试:在程序的开发过程中,必然需要我们不断的调试以达到程序能正确执行的状态 。记录日志可以让开发人员清楚的了解程序的运行状态定位问题;
- 信息收集:在新时代,谁掌握了数据谁就掌握了主动权。现在主流的日志系统可以非常方便的记录用户行为数据,格式化成便于进行大数据分析的格式;
- 记录运行状态:应用程序投产之后,难免会出现生产事故,有了系统日志工程师可以根据日志迅速定位问题。
当然,硬币都具有两面性。引入日志组件也并不是没有缺点。
- 代码冗余:光从实现业务逻辑的角度来讲,在应用程序中插入打印日志的代码打印一大堆日志是完全没必要的,这在一定程度上降低了代码的可读性;
- 降低系统性能:这点很容易理解,因为需要进行日志打印处理,所以系统的运行速度肯定会有所降低。
综合比较日志组件优缺点,我们发现引入日志组件还是非常有必要的。
Log4J、Log4J2和LogBack的历史
使用过Log4J和LogBack的小伙伴肯定能发现,这两个框架的设计理念极为相似,使用方法也如出一辙。
其实这个两个框架的作者都是一个人,Ceki Gülcü,俄罗斯程序员。
Log4J
最初是基于Java开发的日志框架,发展一段时间后,作者Ceki Gülcü将 Log4j 捐献给了Apache软件基金会,使之成为了Apache日志服务的一个子项目。 又由于 Log4J 出色的表现,后续又被孵化出了支持C, C++, C#, Perl, Python, Ruby等语言的子框架。
然而,伟大的程序员好像都比较有个性。Ceki Gülcü由于不满Apache对Log4J的管理,决定不再参加Log4J的开发维护。“出走”后的Ceki Gülcü另起炉灶,开发出了LogBack这个框架(SLF4J
是和LogBack
一起开发出来的)。
LogBack
改进了很Log4的缺点,在性能上有了很大的提升,同时使用方式几乎和Log4J一样,许多用户开始慢慢开始使用 LogBack。
由于受到LogBack的冲击,Log4开始式微。终于,2015年9月,Apache软件基金业宣布,Log4j不在维护,建议所有相关项目升级到Log4j2。
Log4J2
是Apache开发的一个新的日志框架,改进了很多Log4J的缺点,同时也借鉴了LogBac`,号称在性能上也是完胜LogBack。有兴趣的朋友可以测试下两者的性能。
这边顺带提下JUL
这个日志组件。这个日志组件是JDK自带的日志框架。由于在使用便利性和性能上都欠佳,所以存在感一直不高。
Log4j:最早的日志框架
Log4j是Apache软件基金会开发的一个日志框架,也是Java中最早广泛使用的日志框架。Log4j的主要功能包括日志级别控制、格式化输出、日志持久化等,但在并发和高性能场景下可能存在性能瓶颈。Log4j使用了五种日志级别(TRACE、DEBUG、INFO、WARN和ERROR),提供了配置文件的方式(XML、Properties)来实现灵活的日志管理。
对于 Log4J,你需要在项目中引入 Log4J 的库,然后创建一个 Log4J 的配置文件,定义日志级别、输出目标等信息。
放在配置文件中
log4j:
appender:
file: org.apache.log4j.RollingFileAppender
file.File: log4j-application.log
stdout: org.apache.log4j.ConsoleAppender
stdout.Target: System.out
stdout.layout: org.apache.log4j.PatternLayout
stdout.layout.ConversionPattern: '%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n'
rootLogger: INFO, stdout, file
Log4j在长时间的使用中逐渐暴露出其并发性能和灵活性不足的缺点,因此开发团队推出了Log4j的升级版本——Log4j2。
Log4j2:Log4j的增强版本
Log4j2是在Log4j基础上进行大幅改进的日志框架。它引入了异步日志、Lambda表达式支持等功能,极大地提高了性能,尤其适用于高并发和大数据场景。此外,Log4j2还支持插件扩展,提供了比Log4j更加灵活的API。Log4j2支持自动重新加载配置文件(如XML、JSON、YAML格式),实现了更加灵活的日志管理。
Log4j2使用LogManager代替了Logger进行日志初始化:
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Log4j2Example {
private static final Logger logger = LogManager.getLogger(Log4j2Example.class);
public static void main(String[] args) {
logger.info("This is a Log4j2 info message.");
}
}
Logback:一个高性能日志框架
Logback是由Log4j的作者创建的一个全新的日志框架,其设计目的是成为Log4j的继任者。Logback的优点在于性能、内存占用和可靠性。它提供了AsyncAppender用于异步日志输出,提高了系统性能。Logback也有多个版本,其中Logback Classic直接实现了SLF4J API,使其成为许多开发人员的首选。
Logback配置通常通过XML文件进行:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} - %msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
</configuration>
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LogbackExample {
private static final Logger logger = LoggerFactory.getLogger(LogbackExample.class);
public static void main(String[] args) {
logger.info("This is a Logback info message.");
}
}
当然Logback 的使用和 Log4J 类似,不过 Logback 提供了更多的功能,例如条件处理、日志归档等。其最常见就是在我们的项目上放在配置文件中。
<?xml version="1.0" encoding="UTF-8"?>
<configuration >
<jmxConfigurator/>
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
<!-- 应用名称 -->
<property scope="context" name="appName" value="codehub" />
<!-- 自定义日志输出路径,以及日志名称前缀 -->
<property name="LOG_FILE" value="/Users/leo/Leo 工作区/IdeaProjects/ProjectCode/codehub-springboot/app/codehub/log/${appName}.%d{yyyy-MM-dd}"/>
<!-- <property name="FILE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"/> -->
<property name="FILE_LOG_PATTERN" value="[TraceId: %X{traceId}] %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"/>
<!--<property name="CONSOLE_LOG_PATTERN" value="${FILE_LOG_PATTERN}"/>-->
<!-- 按照每天生成日志文件 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件输出的文件名 -->
<FileNamePattern>${LOG_FILE}-%i.log</FileNamePattern>
<!-- 日志文件保留天数 -->
<MaxHistory>30</MaxHistory>
<!-- 日志文件最大的大小 -->
<TimeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>10MB</maxFileSize>
</TimeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!-- 格式化输出:%d 表示日期,%thread 表示线程名,%-5level:级别从左显示 5 个字符宽度 %errorMessage:日志消息,%n 是换行符-->
<pattern>${FILE_LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- dev 环境(仅输出到控制台) -->
<springProfile name="dev">
<include resource="org/springframework/boot/logging/logback/console-appender.xml" />
<root level="info">
<appender-ref ref="CONSOLE" />
</root>
</springProfile>
<!-- prod 环境(仅输出到文件中) -->
<springProfile name="prod">
<include resource="org/springframework/boot/logging/logback/console-appender.xml" />
<root level="INFO">
<appender-ref ref="FILE" />
</root>
</springProfile>
</configuration>
使用以上配置,我们可以按环境输出:
- 在dev环境,将日志输出到控制台,日志级别为INFO。
- 在prod环境,将日志输出到文件,日志级别同样为INFO。
可以 定义日志文件路径和命名规则:通过变量appName和LOG_FILE指定日志文件的存储路径和文件名前缀,并按日期组织文件。
可以按照 日志文件滚动:日志文件每到一天或达到10MB时自动滚动,最多保留30天的日志。
这个配置文件只是 Logback 功能的冰山一角,Logback 还提供了许多高级特性,例如条件处理、过滤器、触发器等,可以通过阅读 Logback 的官方文档来了解更多信息。https://logback.qos.ch/documentation.html。
当然也可以使用Java代码的方式进行实现:
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.ConsoleAppender;
import ch.qos.logback.core.rolling.RollingFileAppender;
import ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy;
import ch.qos.logback.core.rolling.TimeBasedRollingPolicy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LogbackConfig {
private static final String ENV = "dev"; // 可将此改为 "prod" 切换环境
static {
configureLogback();
}
private static void configureLogback() {
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
// 定义日志输出格式
PatternLayoutEncoder encoder = new PatternLayoutEncoder();
encoder.setContext(loggerContext);
encoder.setPattern("[TraceId: %X{traceId}] %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n");
encoder.start();
// 控制台输出配置
ConsoleAppender<ILoggingEvent> consoleAppender = new ConsoleAppender<>();
consoleAppender.setContext(loggerContext);
consoleAppender.setEncoder(encoder);
consoleAppender.start();
// 文件输出配置
RollingFileAppender<ILoggingEvent> fileAppender = new RollingFileAppender<>();
fileAppender.setContext(loggerContext);
fileAppender.setEncoder(encoder);
fileAppender.setFile("/Users/leo/Leo 工作区/IdeaProjects/ProjectCode/codehub-springboot/app/codehub/log/codehub.log");
// 文件滚动策略
SizeAndTimeBasedRollingPolicy<ILoggingEvent> rollingPolicy = new SizeAndTimeBasedRollingPolicy<>();
rollingPolicy.setContext(loggerContext);
rollingPolicy.setParent(fileAppender);
rollingPolicy.setFileNamePattern("/Users/leo/Leo 工作区/IdeaProjects/ProjectCode/codehub-springboot/app/codehub/log/codehub.%d{yyyy-MM-dd}-%i.log");
rollingPolicy.setMaxHistory(30);
rollingPolicy.setMaxFileSize("10MB");
rollingPolicy.start();
fileAppender.setRollingPolicy(rollingPolicy);
fileAppender.start();
// 根日志记录器配置
ch.qos.logback.classic.Logger rootLogger = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
rootLogger.setLevel(Level.INFO);
if ("dev".equals(ENV)) {
rootLogger.addAppender(consoleAppender);
} else if ("prod".equals(ENV)) {
rootLogger.addAppender(fileAppender);
}
}
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(LogbackConfig.class);
logger.info("Hello Logback");
}
}
SLF4J:日志的抽象层
SLF4J(Simple Logging Facade for Java) 并不是一个具体的日志框架,而是提供了一个日志的抽象层。通过SLF4J,开发人员可以在不依赖于具体实现的情况下,灵活地选择和替换日志框架(如Log4j、Log4j2或Logback)。SLF4J的目标是解耦日志API与具体实现,使得开发人员可以在项目中统一使用SLF4J进行日志操作,而实际的日志实现可以通过依赖管理来配置。
SLF4J采用了 门面模式(Facade Pattern) ,提供了一组抽象的接口,如Logger、LoggerFactory等。这些接口只定义了日志的使用方式,而具体实现并不包含在SLF4J中。用户只需在代码中使用SLF4J的API接口,即可实现日志记录,而无需关心底层的日志实现如何工作。
SLF4J的API简洁易用,主要接口包括:
- Logger接口:SLF4J的核心接口,定义了info、debug、warn、error等常用的日志记录方法。使用Logger接口,可以通过一致的方式记录不同级别的日志。
- LoggerFactory类:用于创建和获取Logger实例,通常通过LoggerFactory.getLogger(Class<?> clazz)方法来获取指定类的Logger对象。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SLF4JExample {
private static final Logger logger = LoggerFactory.getLogger(SLF4JExample.class);
public static void main(String[] args) {
logger.info("This is an SLF4J info message.");
}
}
SLF4J的适配机制使得在运行时可以动态选择日志实现。例如,可以通过添加不同的SLF4J适配器(Adapter)来支持Log4J、Logback、Java Util Logging(JUL)等日志框架。SLF4J通过绑定(Binding)机制来选择日志实现,不同的日志实现由各自的适配器包提供,如:
- slf4j-log4j12.jar:SLF4J适配Log4J。
- slf4j-logback-classic.jar:SLF4J适配Logback。
- slf4j-jdk14.jar:SLF4J适配Java Util Logging。
只需将不同的适配包加入依赖中,SLF4J便会自动绑定到相应的日志实现。
我们最关心的其实还是性能问题,SLF4J由于是门面库,本身不会增加任何运行时性能负担。它采用懒加载方式,在需要的时候才会调用实际的日志实现方法,避免了无用的日志处理过程。同时,SLF4J的日志参数化机制有效减少了不必要的字符串拼接和对象创建,进一步提升了性能。
而且扩展性也很高,SLF4J允许在不修改应用代码的情况下轻松更换日志实现。例如,将Log4J换为Logback时,只需替换适配器依赖包。得益于SLF4J的通用接口设计和绑定机制,系统的可扩展性和维护性都大大提高。
SLF4J通常与Logback一起使用,但也可以通过适配器与Log4j、Log4j2等其他日志框架集成。
SLF4J、Log4J 和 Logback 之间的关系
SLF4J、Log4J 和 Logback 之间有着密切的关系,这三者共同构成了Java应用中广泛使用的日志框架生态系统。
- SLF4J(Simple Logging Facade for Java):SLF4J是一个为多种日志框架(例如Log4J、Logback等)提供的统一接口,起到日志门面作用。SLF4J本身不执行日志记录,而是通过适配不同的日志实现来完成日志功能。通过SLF4J,开发人员可以在代码中统一使用标准化的API,而在运行时根据需要选择不同的日志实现,使得应用更具灵活性和可扩展性。
- Log4J:Log4J是最早的Java日志框架之一,提供了丰富的日志配置选项和扩展能力。它广泛应用于Java应用开发中,以其灵活性和易用性著称,但在性能和新特性支持方面存在一定局限性。
- Logback:Logback是Log4J的“升级版”,由Log4J的原作者设计并开发。Logback在继承Log4J优点的基础上,进行了多方面的性能优化与功能增强,包括更高效的日志输出、更丰富的日志滚动策略以及更灵活的配置方式。Logback作为SLF4J的默认实现,与SLF4J集成更加紧密。
SLF4J负责提供统一接口,而Log4J和Logback则是实际的日志实现,用户可以通过SLF4J API灵活选择或切换日志框架。Logback相较于Log4J在性能、功能和配置上都有改进,被视为更现代化的日志解决方案。
最后
笔者认为SLF4J的设计模式非常出色,对于那些不太理解“面向接口编程”的人,看完之后应该会有所启发。SLF4J的这种设计被称为“门面模式”,客户端代码无需任何修改,只需使用SLF4J的API即可。至于底层使用的具体日志实现(如Log4J或Logback),客户端完全无需关心。这种设计方式极大地简化了框架更替的过程,如果系统需要升级或更换日志框架,只需替换相应的实现包即可,开发变得更加灵活和高效。
以上便是本文的全部内容,本人才疏学浅,文章有什么错误的地方,欢迎大佬们批评指正!我是Leo,一个在互联网行业的小白,立志成为更好的自己。
如果你想了解更多关于Leo,可以关注公众号-程序员Leo,后面文章会首先同步至公众号。