简介
SLF4J,Log4J,和 Logback 是 Java 世界中最流行的日志框架。SLF4J 是 Simple Logging Facade for Java 的缩写,提供了一个 Java 日志框架的简单外观。Log4J 是 Apache 的一个开源项目,是 Java 的优秀日志框架。Logback 是 log4j 的作者开发的新产品,它被认为是 log4j 的成功者。后续内容会以Logback为主要内容,进行介绍。
SLF4J、Log4J 和 Logback 之间的关系
这三个框架之间有着密切的关系。SLF4J 是一个为各种日志框架(如 Log4J 和 Logback)提供统一接口的库。SLF4J 本身并不进行日志记录,而是依赖于其他的日志框架。Log4J 和 Logback 都是 SLF4J 的实现,都可以配合 SLF4J 使用。
Logback 可以说是 Log4j 的升级版。Logback 是由 Log4j 的原作者设计并开发的,采用了许多 Log4j 的优点,并在此基础上进行了改进和增强。例如,Logback 提供了更好的性能,以及更丰富的日志策略等。
快速入门
SLF4J:要使用 SLF4J,你需要在项目中引入 SLF4J 的 API 包,然后选择一个 SLF4J 的实现,如 Log4J 或 Logback,引入相应的库。然后你可以像使用标准的日志 API 一样,使用 SLF4J 的 API 进行日志记录。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* SLF4J
*
* @author xiehui1956@gmail.com on
* @version 1.0.0
* @date 2023/6/11
*/
public class HelloLog {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(HelloLog.class);
logger.info("Hello Log");
}
}Log4J:对于 Log4J,你需要在项目中引入 Log4J 的库,然后创建一个 Log4J 的配置文件,定义日志级别、输出目标等信息。
放在配置文件中1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16# 设置全局的日志级别为 INFO
log4j.rootLogger=INFO, stdout, file
# 配置控制台输出 (stdout)
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
# 配置文件输出 (file)
log4j.appender.file=org.apache.log4j.RollingFileAppender
log4j.appender.file.File=log4j-application.log
log4j.appender.file.MaxFileSize=5MB
log4j.appender.file.MaxBackupIndex=10
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
也可以放在java代码中,但是在配置文件中更便于维护和独立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
30
31
32
33
34
35
36
37
38
39
40
41
42
43import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;
import org.apache.log4j.RollingFileAppender;
/**
* Log4J配置实例
*
* @author xiehui1956@gmail.com on
* @version 1.0.0
* @date 2023/6/11
*/
public class Log4JConfig {
static{
configureLog4j();
}
private static void configureLog4j() {
Logger rootLogger = Logger.getRootLogger();
rootLogger.setLevel(Level.INFO);
ConsoleAppender consoleAppender = new ConsoleAppender();
consoleAppender.setTarget("System.out");
consoleAppender.setLayout(new PatternLayout("%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n"));
consoleAppender.activateOptions();
rootLogger.addAppender(consoleAppender);
RollingFileAppender fileAppender = new RollingFileAppender();
fileAppender.setFile("log4j-application.log");
fileAppender.setMaxFileSize("5MB");
fileAppender.setMaxBackupIndex(10);
fileAppender.setLayout(new PatternLayout("%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n"));
fileAppender.activateOptions();
rootLogger.addAppender(fileAppender);
}
public static void main(String[] args) {
Logger logger = Logger.getLogger(Log4JConfig.class);
logger.info("Hello Log4J");
}
}
定义了两个 Appender(stdout 和 file),一个将日志输出到控制台,另一个将日志输出到文件。我们为每个 Appender 定义了一个 PatternLayout,指定了日志的输出格式。
我们还设置了全局的日志级别为 INFO,这意味着级别低于 INFO 的日志(如 DEBUG 和 TRACE)将不会被记录。
- Logback:Logback 的使用和 Log4J 类似,不过 Logback 提供了更多的功能,例如条件处理、日志归档等。
放在配置文件中1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logback.log</file>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logback.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
</appender>
<root level="info">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
</root>
</configuration>
定义了两个 Appender,STDOUT 和 FILE,分别用于向控制台和文件输出日志。我们设置了日志的输出格式,定义了日志文件的滚动策略(每天创建一个新的日志文件,最多保留 30 天的日志文件),并且设置了 root Logger 的日志级别为 INFO。
这个配置文件只是 Logback 功能的冰山一角,Logback 还提供了许多高级特性,例如条件处理、过滤器、触发器等,可以通过阅读 Logback 的官方文档来了解更多信息。https://logback.qos.ch/documentation.html。
java代码中实现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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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.TimeBasedRollingPolicy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Logback 配置实例
*
* @author xiehui1956@gmail.com on
* @version 1.0.0
* @date 2023/6/11
*/
public class LogbackConfig {
static {
configureLogback();
}
private static void configureLogback() {
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
PatternLayoutEncoder encoder = new PatternLayoutEncoder();
encoder.setContext(loggerContext);
encoder.setPattern("%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %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("logback.log");
TimeBasedRollingPolicy<ILoggingEvent> rollingPolicy = new TimeBasedRollingPolicy<>();
rollingPolicy.setContext(loggerContext);
rollingPolicy.setParent(fileAppender);
rollingPolicy.setFileNamePattern("logback.%d{yyyy-MM-dd}.log");
rollingPolicy.setMaxHistory(30);
rollingPolicy.start();
fileAppender.setRollingPolicy(rollingPolicy);
fileAppender.start();
ch.qos.logback.classic.Logger rootLogger = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
rootLogger.setLevel(Level.INFO);
rootLogger.addAppender(consoleAppender);
rootLogger.addAppender(fileAppender);
}
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(LogbackConfig.class);
logger.info("Hello Logback");
}
}
扩展入口
- SLF4J:你可以编写自己的日志框架实现,只需要实现 SLF4J 的接口并提供相应的适配器即可。
- Log4J:你可以通过编写自定义的 Appender(输出源)、Layout(布局)、Filter(过滤器)等进行扩展。例如,你可以编写一个 Appender,将日志记录到数据库或者发送到远程服务器。
- Logback:和 Log4J 类似,Logback 也提供了 Appender、Layout、Filter 等接口供用户进行扩展。除此之外,Logback 还提供了更为丰富的功能,例如提供了在配置文件中使用条件语句的能力,使得用户可以更灵活地配置日志。
下面主要介绍下比较常用的Logback组件提供的扩展接口
Appender接口
Appender 是负责处理日志事件并将其输出到目标的组件。所有的 Appender 都需要实现 ch.qos.logback.core.Appender 接口。你可以创建自定义的 Appender 来输出日志到特定的目标,如数据库、远程服务器等。我在上一篇回答中已经给出了一个简单的自定义 Appender 的例子。
java实例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
30
31
32
33
34
35
36
37
38
39
40
41
import ch.qos.logback.core.UnsynchronizedAppenderBase;
import ch.qos.logback.classic.spi.ILoggingEvent;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
/**
* 通过Appender 将日志事件发送到一个远程的服务
*
* @author xiehui1956@gmail.com on
* @version 1.0.0
* @date 2023/6/11
*/
public class RemoteAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {
/**
* 远程地址
*/
private String endpointUrl;
public void setEndpointUrl(String endpointUrl) {
this.endpointUrl = endpointUrl;
}
protected void append(ILoggingEvent eventObject) {
try {
URL url = new URL(endpointUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
byte[] input = eventObject.getFormattedMessage().getBytes(StandardCharsets.UTF_8);
conn.getOutputStream().write(input, 0, input.length);
conn.getResponseCode();
conn.disconnect();
} catch (Exception e) {
addError("Error sending log event", e);
}
}
}
这个示例中,我们将创建一个 Appender,该 Appender 将日志事件发送到一个远程 服务,RemoteAppender 接受一个 endpointUrl 参数,该参数指定了接收日志事件的 REST 服务的 URL。在 append 方法中,我们创建一个 HTTP POST 请求,将格式化后的日志事件作为请求体发送到 REST 服务。
要使用这个自定义 Appender,需要在 Logback 的配置文件中添加对应的配置:1
2
3
4
5
6
7
8
9
10
11
12<configuration>
<appender name="REMOTE" class="com.demo.log.RemoteAppender">
<endpointUrl>http://my-logging-service.com/logs</endpointUrl>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="REMOTE" />
</root>
</configuration>
Converter接口
Converter 在 Logback 中扮演的角色是对 Layout 进行更为细粒度的控制,它的主要任务是将日志事件中的某个部分转换为字符串。
当你在 PatternLayout 的 pattern 中使用 % 开头的占位符时,Logback 实际上是使用 Converter 来将日志事件中的某个部分转换为字符串。例如,%date 对应的 Converter 将日志事件的时间戳转换为字符串,%level 对应的 Converter 将日志事件的级别转换为字符串。
当然,你也可以创建自定义的 Converter。一个自定义 Converter 必须继承 ch.qos.logback.classic.pattern.ClassicConverter 类,然后实现 convert 方法。convert 方法接收一个 ILoggingEvent 对象,并返回一个字符串。这个字符串是日志事件的某个部分的字符串表示。
Converter结合Layout的示例:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import ch.qos.logback.classic.pattern.ClassicConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;
/**
* Logback大小写转换Converter
*
* @author xiehui1956@gmail.com on
* @version 1.0.0
* @date 2023/6/11
*/
public class UppercaseLevelConverter extends ClassicConverter {
public String convert(ILoggingEvent event) {
return event.getLevel().toString().toUpperCase();
}
}
1 |
|
Logback 的配置文件中添加配置1
2
3
4
5
6
7
8
9
10
11<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<layout class="com.demo.log.JsonLayout" />
</encoder>
</appender>
<root level="info">
<appender-ref ref="STDOUT" />
</root>
</configuration>
Layout接口
Layout 是负责格式化日志事件的组件。所有的 Layout 都需要实现 ch.qos.logback.core.Layout 接口。你可以创建自定义的 Layout 来改变日志的输出格式。下面是一个简单的自定义 Layout 的例子:
java实例1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.LayoutBase;
/**
* 自定义Layout
*
* @author xiehui1956@gmail.com on
* @version 1.0.0
* @date 2023/6/11
*/
public class CustomLayout extends LayoutBase<ILoggingEvent> {
public String doLayout(ILoggingEvent event) {
return "Custom Layout: " + event.getFormattedMessage();
}
}
CustomLayout 的类,它继承了 LayoutBase,并重写了 doLayout 方法。doLayout 方法是 Layout 接口的核心方法,它接收一个日志事件并返回格式化后的字符串。
Filter接口
Filter 是负责筛选日志事件的组件。所有的 Filter 都需要实现 ch.qos.logback.core.filter.Filter 接口。你可以创建自定义的 Filter 来筛选出你感兴趣的日志事件。
java实例1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.filter.Filter;
import ch.qos.logback.core.spi.FilterReply;
/**
* 自定义Filter
*
* @author xiehui1956@gmail.com on
* @version 1.0.0
* @date 2023/6/11
*/
public class CustomFilter extends Filter<ILoggingEvent> {
public FilterReply decide(ILoggingEvent event) {
if (event.getMessage().contains("error")) {
return FilterReply.ACCEPT;
} else {
return FilterReply.DENY;
}
}
}
实现原理
SLF4J
SLF4J 作为一个日志抽象层,它的主要目标是抽象出日志操作的公共接口,然后代理给具体的日志框架来执行实际的日志操作。在 SLF4J 中,核心的类是 LoggerFactory 和 Logger,LoggerFactory 用于创建 Logger,Logger 则提供了日志操作的接口。
SLF4J 通过 Java 的动态绑定机制来连接具体的日志框架。在运行时,SLF4J 会查找并加载实现了 SLF4J API 的具体日志框架。如果在类路径中存在多个具体的日志框架,SLF4J 将选择一个并忽略其他的。SLF4J 的这种设计使得开发者可以在不改变代码的情况下更换日志框架,只需替换类路径中的日志框架库就可以了。
Log4J
Log4j 的设计基于三个核心的概念:Logger,Appender 和 Layout。
Logger 负责捕获日志事件并将其发送到 Appender。每个 Logger 都有一个名称,并可以设置日志级别。Logger 的名称通常与类或包的名称相对应,这使得开发者可以控制应用的各个部分的日志行为。
Appender 负责接收日志事件并将其输出到某个目标,例如控制台、文件或远程服务器。Log4j 提供了多种 Appender,开发者也可以创建自定义的 Appender。
Layout 负责格式化日志事件,将其转换为字符串。Log4j 提供了多种 Layout,例如 SimpleLayout、PatternLayout 和 HTMLLayout。开发者也可以创建自定义的 Layout。
Logback
Logback 是 Log4j 的一个改进版,它同样基于 Logger,Appender 和 Layout 的概念,但添加了许多新的功能,例如条件处理、日志归档和自动重新加载配置。
Logback 的 Logger 有一个额外的日志级别,叫做 OFF,它用于关闭日志。此外,Logback 的 Logger 支持多个 Appender,这使得开发者可以将同一个日志事件输出到多个目标。
Logback 的 Appender 提供了一些高级的功能,例如异步输出、缓冲和故障恢复。此外,Logback 提供了许多用于各种输出目标的 Appender,例如控制台、文件、数据库、套接字和邮件。
Logback 的 Layout 提供了多种格式化选项,包括 PatternLayout、HTMLLayout 和 XMLLayout。此外,Logback 还支持自定义的 Converter,开发者可以使用 Converter 来创建复杂的日志格式。
性能对比
SLF4J
因为 SLF4J 是一个日志抽象层,它本身的性能主要取决于它背后的日志实现。SLF4J 的优点是,如果你觉得某个日志实现的性能不佳,你可以轻松地更换为其他实现。
Log4J
Log4J 是一个成熟的日志框架,它的性能在绝大多数情况下都足够好。然而,如果你的应用需要高频率的日志记录,Log4J 可能会成为瓶颈。
Logback
Logback 是 Log4J 的一个改进版本,提供了更好的性能。Logback 采用了一种新的并发模型,可以更有效地处理多线程环境下的日志记录。此外,Logback 还提供了一种基于事件的日志记录方式,可以进一步提高日志记录的性能。
Logback 在设计之初就高度关注性能和资源占用。以下列举了 Logback 的一些特性和优化方法:
异步日志
日志操作可能会涉及磁盘 I/O,网络 I/O 等,可能会对性能产生较大影响。Logback 提供了 AsyncAppender,它可以将日志事件放入一个队列,然后由一个独立的线程来处理这些事件。这使得日志操作不会阻塞主程序的执行。例如,可以使用如下配置来创建一个异步的 FileAppender:1
2
3
4
5
6
7
8
9
10
11<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<!-- configuration for FileAppender -->
</appender>
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="FILE" />
</appender>
<root level="info">
<appender-ref ref="ASYNC" />
</root>
配置中,日志事件首先会被发送到 ASYNC,ASYNC 会将事件放入队列,然后由一个独立的线程来处理这些事件,并将它们发送到 FILE。
缓冲
Logback 的许多 Appender 支持缓冲。当启用缓冲时,Appender 会将日志事件收集到一个缓冲区,然后一次性将缓冲区的内容写入目标。这可以减少 I/O 操作的次数,从而提高性能。
例如,FileAppender 支持设置缓冲区的大小,你可以使用 immediateFlush 参数来关闭即时刷新,使得日志事件可以在缓冲区满时再写入文件:1
2
3
4
5
6
7
8
9
10
11<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>myApp.log</file>
<encoder>
<pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>
</encoder>
<immediateFlush>false</immediateFlush>
</appender>
<root level="info">
<appender-ref ref="FILE" />
</root>
日志级别
通过精细地设置日志级别,你可以控制哪些日志事件会被记录。在性能关键的部分,你可以将日志级别设置为 WARN 或 ERROR,从而只记录重要的事件。此外,你还可以使用 Marker 或 MDC(Mapped Diagnostic Context)来过滤日志事件。例如,可以为某个包设置一个更高的日志级别:1
<logger name="com.example.package" level="WARN" />
配置中,com.example.package 包下的日志事件只有在级别为 WARN 或更高时才会被记录。
避免过渡日志
有时候,开发者可能会过度使用日志记录功能,如在循环或高频调用的函数中记录日志。这会产生大量的日志信息,不仅可能拖慢系统性能,还可能导致重要信息在海量日志中丧失。因此,应谨慎使用日志记录,尽可能在关键位置(如错误处理,重要业务流程等)进行日志记录。
例如1
2
3for(int i=0; i<1000000; i++){
logger.debug("This is number: " + i); //Avoid doing this
}
使用参数化日志消息
当需要在日志消息中包含变量信息时,推荐使用 SLF4J 的参数化日志消息而不是字符串拼接。参数化日志消息可以避免在日志级别禁用的情况下进行无用的字符串操作,从而提升性能。
例如:1
logger.debug("The new entry is " + entry + ".");
应该为:1
logger.debug("The new entry is {}.", entry);
日志归档
随着时间的推移,日志文件可能会变得非常大,这可能会影响日志文件的写入性能,也会增加存储压力。Logback 提供了日志文件归档功能,可以将日志文件按照时间或大小进行切割,保持每个日志文件的大小适中。
配置将每天创建一个新的日志文件,并将旧的日志文件归档:1
2
3
4
5
6
7
8
9
10
11
12
13<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>myApp.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>myApp.%d{yyyy-MM-dd}.log</fileNamePattern>
</rollingPolicy>
<encoder>
<pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="FILE" />
</root>
总结
SLF4J, Log4J 和 Logback 每一个都有其特有的功能和优点。SLF4J 提供的是一种统一的接口,方便我们在不同的日志实现之间切换。Log4J 提供了强大的日志功能和广泛的用户基础。而 Logback 则提供了 Log4J 的所有功能,并在其基础上进行了一些优化,包括提供了更好的性能,更丰富的日志策略等。
因此,我们在选择日志框架时,需要根据项目的实际需求,以及各个日志框架的特性来决定使用哪一个。同时,我们还需要深入理解这些日志框架的实现原理和使用方式,才能充分发挥它们的优点,提高我们项目的日志管理能力。