Java日志,我们先按照名词分开来理解,Java 是一门编程语言,日志是指记录一件事情发生的过程,所以Java日志指的就是 Java 程序在执行过程中,用于记录程序的执行过程的工具。当程序执行的时候,你如何追踪程序的执行过程?还像开发的时候 debug ?假如程序执行的过程中出现了错误,你又如何定位分析?如果让你优化一个程序功能,你又该如何判断优化哪一段呢?如果你仔细思考过这个问题,你觉得你会怎么做?我想大家应该都是通过日志去完成,通过日志记录执行程序的时间,参数,还有错误的信息,我们将很方便的进行分析判断。如下图:
Java日志的发展时间线:
Java日志界的大佬Ceki Gülcü
如果说对Java日志贡献最大的人是谁,感觉应该当属于Ceki Gülcü了。
Ceki Gülcü 这位大神专注日志开发五百年。Java 日志领域的“卷王”,在1996年初,E.U.SMPER (欧洲安全电子市场)项目决定编写自己的日志API,最后该API演变为 Log4j,该日志软件包一经推出就备受欢迎,当然这里必须要提到一个人,就是 Log4j 的主要贡献者 Ceki Gülcü,后来 Log4j 成为了 Apache 基金会项目中的一员。
这期间 JUL 的出现,引发了日志界 Log4j 与 JUL “楚汉争霸” 局面。为解决这个问题,2002年8月 Apache 又推出了日志接口 JCL ,也就是日志抽象层。希望达到天下之大,唯我 JCL 独尊的地位,但是好景不长,JCL 的名声并不是很好。同时 Ceki Gülcü 由于一些原因离开了 Apache ,这里大家自行脑部各种宫斗场景,哈哈哈。自己创业成立了 qos 公司,并推出了和 JCL 相同的产品 Slf4j 。
由于 Slf4j 出来的较晚,而且还只是一个日志接口,所以之前已经出现的日志产品,如 JUL 和 Log4j 都是没有实现这个接口的,所以尴尬的是光有一个接口,没有实现的产品也是很憋屈啊,就算开发者想用 Slf4j 也是用不了,这时候,大佬Ceki Gülcü发话了 :别急,我早帮你们想好了,要让 sum 或者 Apache 这两个庞然大物来实现我的接口,太南啦,老铁,但… 我帮你们实现,不就完了么… 卧槽,大佬还是我大佬,于是大佬 Ceki Gülcü 连夜撸出了之前提到的桥接包,也就是类似适配器模式。完美解决了与 JUL ,Log4j 这类日志框架包的兼容问题。
Slf4j 虽然可以用,但是想想本质还是使用的别人家的孩子,之前的日志产品都不是正统的 Slf4j 的实现,大佬 Ceki Gülcü 想了想不行不行呀,我得有一个自己的亲孩子。因此,还是出自 Ceki Gülcü 之手的日志产品 Logback 应运而生了【此时作者已经离开了Apache,这名字你细品,我回归了,哈哈哈,是不是很有意思】。Logback 是完美实现了 Slf4j。大佬 Ceki Gülcü 说这就是我的亲孩子。
就这样大家也都看起来相安无事,但 Slf4j + Logback 的模式,显然很冲击 JCL + Log4j ,并且本身Logback 确实比 Log4j 性能更优,设计更为合理,所以老东家 Apache 可就坐不住了。
在2012年,Apache 直接推出新项目,不是 Log4j.x 升级,而是新项目 Log4j2,因为 Log4j2 是完全不兼容 Log4j.x 的。并且很微妙的,Log4j2 几乎涵盖 Logback 所有的特性,更甚者的 Log4j2 也搞了分离的设计,分化成 log4j-api 和 log4j-core ,这个 log4j-api 也是日志接口,log4j-core 才是日志产品。Apache 为了让大家可以接入自己的 Log4j2 ,那不就是桥嘛,Log4j2 也麻溜的推出了它的桥接包。
至此也就形成了现在 Slf4j 与 Log4j2 南北对持的局面,这也是Java日志界,相爱相杀的历史。
总结一下: Log4j Slf4j Logback 是同一个作者,Log4j 后来捐献给 Apache了,作者后来创业又开发了Slf4j 和 Logback 。 JCL 和 Slf4j 都是日志门面接口,具体的实现是通过桥接模式
主要产品
1. JDK日志【java.util.logging 简称 JUL】
从 jdk1.4起,JDK开始自带一套日志系统。JDK Logger最大的优点就是不需要任何类库的支持,只要有Java的运行环境就可以使用。相对于其他的日志框架,JDK自带的日志可谓是鸡肋,无论易用性,功能还是扩展性都要稍逊一筹,所以在商业系统中很少直接使用。
2. Log4j 和 Log4j2 系列日志 【现在都是 Apache 项目】
Apache的一个开放源代码项目,通过使用 log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件、甚至是套接口服务 器、NT的事件记录器、UNIX Syslog守护进程等。用户也可以控制每一条日志的输出格式,通过定义每一条日志信息的级别,用户能够更加细致地控制日志的生成过程。这些可以通过一个配置文件来灵活地进行配置,而不需要修改程序代码。log4j2 是完全不兼容log4j 的,可以理解为两个不同的项目。
3. common-logging 日志接口【简称 JCL】
Apache的一个开放源代码项目,也就是日志抽象层。主要的实现方式还是 log4j 和 jul
4. Slf4j 日志接口
slf4j 全称为 Simple Logging Facade For JAVA,java 简单日志门面。类似于 jcl ,是对不同日志框架提供的一个门面封装,可以在部署的时候不修改任何配置即可接入一种日志实现方案。但是,他在编译时静态绑定真正的log库。使用SLF4J时,如果你需要使用某一种日志实现,那么你必须选择对应正确的 slf4j 的 jar 包的集合(各种桥接包)。
slf4j-simple、logback 都是 slf4j 的具体实现,log4j 并不直接实现 slf4j,而是有专门的一层桥接slf4j-log4j12 来实现 slf4j。
5. Logback
logback 是由 log4j 创始人设计的又一个开源日记组件。logback 当前分成三个模块:logback-core, logback-classic 和 logback-access 。logback-core 是其它两个模块的基础模块。logback-classic是 log4j 的一个改良版本。此外 logback-classic 完整实现 SLF4J API 使你可以很方便地更换成其它日记系统如 log4j 或 jul 。logback-access 访问模块与 Servlet 容器集成提供通过Http来访问日记的功能。
Java 日志学习
最原始的日志
Java中最原始和最简单的方式自然就是通过 System.out 的方式,以下就是一个简单是事例
public static void mostOriSystemLogger(String message) {
try {
// 创建输出文件流
FileOutputStream fos = new FileOutputStream("output.log", true); // true 表示追加模式
PrintStream ps = new PrintStream(fos);
// 重定向 System.out 到文件
System.setOut(ps);
System.setErr(ps);
// 测试输出 日志输出到文件 output.log
System.out.println(message);
System.err.println(message);
} catch (IOException e) {
e.printStackTrace();
System.err.println("This error message: " + e.getMessage());
}
}
System.out 存在一些明显的限制
- 无法灵活配置:无法轻松将日志输出到文件、网络或其他输出端,只能输出到控制台。
- 级别:System.out.println() 没有日志级别的概念,无法区分不同重要程度的信息。
- 日志格式:System.out.println() 输出格式固定,无法统一日志输出的格式和风格。
- 性能问题:大量使用 System.out.println() 可能会导致性能问题,特别是在高并发的应用中,控制台 I/O 可能成为瓶颈。
也正是因为这些限制,由此诞生了 Log4j
Log4j
Log4解决了System.out的所有限制,还补充支持日志文件的滚动(按大小或时间自动切换日志文件),方便管理和归档大量的日志数据。
简单事例 1. 如果使用 SLF4J + Log4J 需要依赖配置
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
2. ****log4j.properties 配置文件
# 配置根Logger,日志级别为DEBUG,输出到名为console的appender
log4j.rootLogger=DEBUG, console
# 配置console这个appender,类型为ConsoleAppender,使用PatternLayout格式输出
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
3. Java代码
import org.apache.log4j.Logger;
public class Log4jUtils {
// 获取Logger实例
private static Logger logger = Logger.getLogger(Log4jUtils.class);
public static void main(String[] args) {
logger.debug("This is a debug message");
logger.info("This is an info message");
logger.warn("This is a warning message");
logger.error("This is an error message");
logger.fatal("This is a fatal message");
}
}
Log4j 在高并发环境下性能较低,尤其是使用同步输出时,容易成为性能瓶颈和线程安全问题。
有了 Log4j 为什么还需要 JUL ?
很好理解,Log4j 毕竟私生子,不是 sum 正统出生。我 sum 要生当然得生一个亲生的。
JUL
JUL 最大的优点不需要依赖额外的库,JDK中就有了,方便快捷。提供默认配置等。但是 JUL 功能和扩展性远不如 Log4j 完善,自带的 Handlers 有限,性能和可用性上也一般,文档也少。JUL 是在Java1.5以后才有所提升的。简单事例
import java.util.logging.Level;
import java.util.logging.Logger;
public class JULExample {
// 创建Logger实例
private static final Logger logger = Logger.getLogger(JULExample.class.getName());
public static void main(String[] args) {
// 设置日志级别
logger.setLevel(Level.ALL);
// 记录不同级别的日志
logger.severe("This is a SEVERE message");
logger.warning("This is a WARNING message");
logger.info("This is an INFO message");
logger.config("This is a CONFIG message");
logger.fine("This is a FINE message");
logger.finer("This is a FINER message");
logger.finest("This is a FINEST message");
// 使用 try-catch 记录异常信息
try {
int result = 10 / 0; // 会抛出异常
} catch (ArithmeticException ex) {
logger.log(Level.SEVERE, "Exception caught", ex);
}
}
}
如果使用 SLF4J + JDKLog 需要依赖配置
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.7.21</version>
</dependency>
为什么又出现了 JCL?Log4j 和 JUL 其实造成了日志界的混乱,他们两个是完全不同的接口实现,当 JUL 出现后,市面上就存在了两套日志框架,假设我 A 包用的 Log4j 日志,B 包用的 jul 日志,我自己工程引用了 A,B 两个包,那我就必须在项目中配置两套日志,各种输出格式,级别配置等等,是不是想想又很扯淡了,于是JCL诞生【其实就是想统一标准接口】
JCL
Java Common Logging (JCL) 是在 Java 社区中为了解决日志抽象层问题而出现的。在 JCL 出现之前,不同的 Java 项目和框架可能使用不同的日志框架(如 JUL、Log4j 等)。这导致在集成多个库时,如果它们使用不同的日志框架,可能会产生冲突或需要重复配置多个日志框架。JCL 提供了一个统一的日志接口,允许开发者在代码中使用同一个 API,而不必关心底层实际使用的日志框架。这使得项目在不同的环境中可以灵活地切换日志实现,例如在开发时使用 Log4j,在生产环境中使用 JUL。简单事例
- 首先,如果你使用 Maven 进行依赖管理,你需要在 pom.xml 中添加 JCL 的依赖。
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
2. Java代码
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class JCLExample {
// 创建一个日志记录器
private static final Log log = LogFactory.getLog(JCLExample.class);
public static void main(String[] args) {
log.debug("This is a debug message");
log.info("This is an info message");
log.warn("This is a warning message");
log.error("This is an error message");
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
log.fatal("This is a fatal error message", e);
}
}
}
JCL 会在 ClassLoader 中进行查找,如果能找到 Log4j 则默认使用Log4j 实现,如果没有则使用 JUL 实现,再没有则使用 JCL 内部提供的 SimpleLog 实现。
JCL 缺点也很明显
-
性能问题:JCL 的自动探测机制会在应用启动时进行绑定检测,这在某些情况下会导致性能问题。
-
复杂性:JCL 增加了一层抽象,这有时会使问题的排查变得更加复杂。
-
线程安全问题:早期版本的 JCL 在多线程环境中存在一些问题,可很大的可能会引发内存泄露。同时,JCL 的书写存在一个不太优雅的地方
// 假如要输出一条debug日志,而一般情况下,生产环境 log 级别都会设到 info 或者以上,那这条 log 是不会被输出的。于是,在代码里就出现了
logger.debug("this is a debug info , message :" + msg);
// 这个有什么问题呢?虽然生产不会打出日志,但是这其中都会做一个字符串连接操作,然后生成一个新的字符串。如果这条语句在循环或者被调用很多次的函数中,就会多做很多无用的字符串连接,影响性能。
// 所以,JCL 推荐的写法如下
if (logger.isDebugEnabled()) {
logger.debug("this is a debug info , message :" + msg);
}
// 虽然解决了问题,但是这个代码实在看上去不怎么舒服...
为什么又出现了 SLF4J
其实看到这里,大家基本都可以猜到 SLF4J 出现的原因了吧,直白点就是 JCL 不行,那我 SLF4J 来吧。
SLF4J + Logback
SLF4J (Simple Logging Facade for Java) 和 JCL 一样都是定义了接口。SLF4J 是一个日志抽象层或日志门面,它提供了一组统一的日志 API。SLF4J 允许你在应用程序中使用统一的日志接口,而具体的日志实现可以通过绑定来选择。SLF4J 本身并不负责实际的日志记录操作,它只定义了日志记录的接口。Logback 是一个具体的日志实现框架,它实现了 SLF4J 的 API。Logback 提供了实际的日志记录功能,并且是 SLF4J 的一个推荐实现。Logback 由 Log4j 的作者开发,旨在提供更好的性能和更丰富的功能。简单事例下面分别给出使用 SLF4J 和 Logback 的代码示例。注意,Logback 是 SLF4J 的一种具体实现,通常在项目中同时使用 SLF4J API 和 Logback。
- 引入依赖包
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.9</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.11</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.4.11</version>
</dependency>
2. Logback 配置文件(logback.xml)
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
</configuration>
3. Java代码
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.debug("This is a debug message");
logger.info("This is an info message");
logger.warn("This is a warning message");
logger.error("This is an error message");
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
logger.error("An exception occurred", e);
}
}
}
// slf4j 最大的特点就是打日志的时候使用了{}占位符,这样就不会有字符串拼接操作,减少了无用String对象的数量,节省了内存。
// 并且,记住,在生产最终日志信息的字符串之前,这个方法会检查一个特定的日志级别是不是打开了,这不仅降低了内存消耗而且预先降低了CPU去处理字符串连接命令的时间。
logger.info("This is a test message: {}", message);
Logback 作为 SLF4J 的推荐实现,旨在与 SLF4J 接口紧密集成,提供一致的日志记录体验。Logback 的特点
- 异步日志:Logback 支持异步日志记录,利用异步日志可以显著提高日志记录的性能,尤其是在高并发环境中。
- 高级配置:Logback 提供了灵活的配置选项,可以基于日志的级别、条件、时间等进行更精细的日志记录配置。
- 多种日志输出:Logback 支持多种日志输出方式,包括控制台、文件、滚动文件、数据库等,且支持日志的滚动和归档。
- 条件化日志:可以基于运行时的条件动态配置日志的输出和级别。
- 性能优化:Logback 在性能方面做了许多优化,包括减少日志记录时的延迟和系统资源的使用。
至此其实 Logback + SLF4J 可以说是最完美的组合了,为什么又出现了 Log4j2 呢?
Log4j2
Log4j2 是 Log4j 框架的升级版本,旨在克服 Log4j 1.x 的一些限制,同时结合 Logback 和其他日志框架的优点,提供更高的性能、灵活性和可扩展性。他和 Logback + SLF4J 个有千秋,总体来说都不差。
简单事例
- 依赖引用
<dependencies>
<!-- Log4j2 Core -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.20.0</version>
</dependency>
<!-- Log4j2 API -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.20.0</version>
</dependency>
</dependencies>
2. 配置文件 log4j2.xml
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
</Console>
</Appenders>
<Loggers>
<Root level="debug">
<AppenderRef ref="Console" />
</Root>
</Loggers>
</Configuration>
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.debug("This is a debug message");
logger.info("This is an info message");
logger.warn("This is a warning message");
logger.error("This is an error message");
logger.fatal("This is a fatal message");
}
}
Log4j2 的特点
-
高性能:Log4j 2 支持异步日志记录,显著提高了日志性能,特别是在高并发环境中。
-
灵活性:提供丰富的插件和扩展机制,支持动态配置和多种日志格式。
-
线程安全:比 Log4j 1.x 更好地处理了线程安全问题。
-
复杂应用的支持:适合需要高可用性和复杂日志需求的企业应用。
总结
| 特性 | SLF4J | Logback | JUL | JCL | Log4j | Log4j2 |
|---|---|---|---|---|---|---|
| 优点 | 统一日志接口,兼容多种日志框架,支持绑定不同的日志实现 | SLF4J 的原生实现,高性能,丰富的功能,支持异步日志 | JDK 自带,易于使用,无需外部依赖 | 提供统一的日志接口,方便切换底层日志框架 | 经典框架,广泛使用,配置简单 | 支持异步日志,灵活扩展,适合复杂企业应用 |
| 缺点 | 仅提供接口,需与其他日志框架结合使用 | 功能较复杂,配置需要一定学习成本 | 功能简单,性能一般,配置灵活性差 | 动态查找日志实现性能低,JAR 冲突问题 | 性能相对较低,线程安全性问题 | 配置和学习复杂 |
| 使用场景 | 适合需要灵活选择或切换底层日志实现的应用 | 适合需要高性能和丰富日志功能的中大型应用 | 适合简单应用和对日志需求不高的系统 | 适合希望灵活切换底层日志框架的场景 | 适合传统应用和简单日志需求 | 适合高并发环境和复杂的企业应用 |
致谢
更多内容欢迎点击阅读原文,喜欢文章的话,也希望能给小编点个赞或者转发,你们的喜欢与支持是小编最大的鼓励,小巫编程室感谢您的关注与支持。好好学习,天天向上(good good study day day up)。
- OPENBOOT[1]
- log4j官方配置文件说明[2]
- log4j1 源码地址[3]
- log4j2 源码地址[4]
- slf4j 官网[5]
- logback 官网[6]
- csdn[7]
- csdn[8]
参考资料
[1] OPENBOOT: gitee.com/openboot
[2] log4j官方配置文件说明: logging.apache.org/log4j/2.x/m…
[3] log4j1 源码地址: github.com/apache/logg…
[4] log4j2 源码地址: github.com/apache/logg…
[5] slf4j 官网: www.slf4j.org/
[6] logback 官网: logback.qos.ch/
[7] csdn: blog.csdn.net/qq_28165595…
[8] csdn: blog.csdn.net/imjcoder/ar…