我有生以来第一次发现自己处于编写将开源的 Java API 的位置。希望被包含在许多其他项目中。
对于日志记录,我(实际上和我一起工作的人)一直使用 JUL( java.util.logging
)并且从来没有遇到过任何问题。但是现在我需要更详细地了解我应该为我的 API 开发做些什么。我对此做了一些研究,根据我得到的信息,我变得更加困惑。因此这篇文章。
因为我来自 JUL,所以我对此有偏见。我对其余部分的了解并不多。
根据我所做的研究,我得出了人们不喜欢 JUL 的以下原因:
“在 Sun 发布 JUL 之前很久我就开始使用 Java 进行开发,对我来说继续使用 logging-framework-X 比学习新东西更容易” 。唔。我不是在开玩笑,这实际上是人们所说的。有了这个论点,我们都可以做 COBOL。 (但是我当然可以将其与自己是一个懒惰的家伙联系起来)
“我不喜欢 JUL 中日志记录级别的名称” 。好吧,说真的,这还不足以成为引入新依赖项的理由。
“我不喜欢 JUL 输出的标准格式” 。唔。这只是配置。您甚至不必在代码方面做任何事情。 (没错,在过去,您可能必须创建自己的 Formatter 类才能正确使用)。
“我使用其他也使用 logging-framework-X 的库,所以我认为使用那个更容易” 。这是一个循环论证,不是吗?为什么“每个人”都使用 logging-framework-X 而不是 JUL?
“其他人都在使用 logging-framework-X” 。这对我来说只是上述的一个特例。多数并不总是正确的。
所以真正的大问题是 为什么不是 JUL? .我错过了什么?日志外观(SLF4J、JCL)存在的理由是历史上已经存在多个日志实现,而在我看来,其原因实际上可以追溯到 JUL 之前的时代。如果 JUL 是完美的,那么 logging facades 就不会存在,或者什么?更令人困惑的是,JUL 在某种程度上本身就是一个门面,允许交换 Handlers、Formatters 甚至 LogManager。
与其采用多种方式来做同一件事(日志记录),我们难道不应该首先质疑为什么它们是必要的吗? (看看这些原因是否仍然存在)
好的,到目前为止,我的研究导致了一些我可以看到的可能是 JUL 的真正问题:
- 表现。有人说 SLF4J 的性能优于其他。在我看来,这似乎是一个过早优化的案例。如果您需要每秒记录数百兆字节,那么我不确定您是否走在正确的道路上。 JUL 也发生了变化,您在 Java 1.4 上所做的测试可能不再正确。您可以 在此处 阅读相关内容,并且此修复已纳入 Java 7。许多人还讨论了日志记录方法中字符串连接的开销。然而,基于模板的日志记录避免了这种成本,它也存在于 JUL 中。就我个人而言,我从来没有真正编写过基于模板的日志记录。太懒了。例如,如果我用 JUL 这样做:
log.finest("Lookup request from username=" + username
+ ", valueX=" + valueX
+ ", valueY=" + valueY));
我的 IDE 会警告我并请求许可将其更改为:
log.log(Level.FINEST, "Lookup request from username={0}, valueX={1}, valueY={2}",
new Object[]{username, valueX, valueY});
.. 我当然会接受。许可授予 !谢谢您的帮助。
所以我实际上并没有自己写这样的语句,这是由IDE完成的。
总之,关于性能问题,我没有发现任何表明 JUL 的性能与竞争对手相比不佳的迹象。
来自类路径的配置。开箱即用的 JUL 无法从类路径加载配置文件。 只需几行代码 就可以做到这一点。我明白为什么这可能很烦人,但解决方案简短而简单。
输出处理程序的可用性。 JUL 带有 5 个开箱即用的输出处理程序:控制台、文件流、套接字和内存。这些可以扩展或可以编写新的。例如,这可能是写入 UNIX/Linux 系统日志和 Windows 事件日志。我个人从未有过这个要求,也没有看到它被使用过,但我当然可以理解为什么它可能是一个有用的特性。例如,Logback 带有 Syslog 的附加程序。我仍然认为
- 开箱即用的 JUL 涵盖了 99.5% 的输出目的地需求。
- 可以通过在 JUL 之上而不是在其他东西之上的自定义处理程序来满足特殊需求。对我来说,没有什么表明为 JUL 编写 Syslog 输出处理程序比为另一个日志记录框架花费更多的时间。
我真的很担心我忽略了一些事情。 JUL 以外的日志外观和日志实现的使用如此广泛,以至于我不得不得出结论,就是我不明白。恐怕这不是第一次了。 :-)
那么我应该如何处理我的 API?我希望它成功。我当然可以“顺其自然”并实施 SLF4J(这似乎是当今最流行的),但为了我自己,我仍然需要了解今天的 JUL 到底出了什么问题,这足以保证所有的模糊?我会为我的图书馆选择 JUL 来破坏自己吗?
测试性能
(nolan600 在 2012 年 7 月 7 日添加的部分)
下面有来自 Ceki 的参考,关于 SLF4J 的参数化比 JUL 快 10 倍或更多。所以我开始做一些简单的测试。乍一看,这种说法当然是正确的。以下是初步结果(但请继续阅读!):
- 执行时间 SLF4J,后端 Logback:1515
- 执行时间 SLF4J,后端 JUL:12938
- 执行时间 JUL:16911
上面的数字是毫秒,所以越少越好。所以 10 倍的性能差异首先实际上非常接近。我最初的反应是:很多!
这是测试的核心。可以看出,一个整数和一个字符串在一个循环中构造,然后在日志语句中使用:
for (int i = 0; i < noOfExecutions; i++) {
for (char x=32; x<88; x++) {
String someString = Character.toString(x);
// here we log
}
}
(我希望日志语句同时具有原始数据类型(在本例中为 int)和更复杂的数据类型(在本例中为 String)。不确定它是否重要,但你已经知道了。)
SLF4J 的日志语句:
logger.info("Logging {} and {} ", i, someString);
JUL 的日志语句:
logger.log(Level.INFO, "Logging {0} and {1}", new Object[]{i, someString});
在实际测量完成之前,JVM 通过执行一次相同的测试来“预热”。在 Windows 7 上使用 Java 1.7.03。使用了最新版本的 SLF4J (v1.6.6) 和 Logback (v1.0.6)。 Stdout 和 stderr 被重定向到空设备。
但是,现在要小心,事实证明 JUL 大部分时间都在 getSourceClassName()
因为默认情况下 JUL 在输出中打印源类名称,而 Logback 则不会。所以我们正在比较苹果和橘子。我必须再次进行测试并以类似的方式配置日志记录实现,以便它们实际上输出相同的内容。然而,我确实怀疑 SLF4J+Logback 仍将名列前茅,但与上面给出的初始数字相去甚远。敬请关注。
顺便说一句:该测试是我第一次真正使用 SLF4J 或 Logback。一次愉快的经历。当您刚开始时,JUL 肯定不那么受欢迎。
测试性能(第 2 部分)
(nolan600 在 2012 年 7 月 8 日添加的部分)
事实证明,在 JUL 中如何配置模式对性能并不重要,即它是否包含源名称。我尝试了一个非常简单的模式:
java.util.logging.SimpleFormatter.format="%4$s: %5$s [%1$tc]%n"
这根本没有改变上述时间。我的探查器显示记录器仍然花费大量时间调用 getSourceClassName()
即使这不是我的模式的一部分。模式无关紧要。
因此,我在性能问题上得出结论,至少对于经过测试的基于模板的日志语句,JUL(慢)和 SLF4J+Logback(快)之间的实际性能差异似乎大约有 10 倍。正如策基所说。
我还可以看到另一件事,即 SLF4J 的 getLogger()
调用比 JUL 的同上更昂贵。 (95 毫秒对 0.3 毫秒,如果我的分析器是准确的)。这是有道理的。 SLF4J 必须在底层日志记录实现的绑定上做一些时间。这不会吓到我。这些调用在应用程序的生命周期中应该很少见。牢度应该在实际的日志调用中。
定论
(peterh 在 2012 年 7 月 8 日添加的部分)
谢谢你所有的回答。与我最初的想法相反,我最终决定为我的 API 使用 SLF4J。这是基于许多事情和您的输入:
它提供了在部署时选择日志实现的灵活性。
在应用程序服务器内运行时,JUL 配置缺乏灵活性的问题。
SLF4J 肯定比上面详述的要快得多,特别是如果您将它与 Logback 结合使用。即使这只是一个粗略的测试,我也有理由相信在 SLF4J+Logback 上的优化比在 JUL 上付出了更多的努力。
文档。 SLF4J 的文档更加全面和精确。
模式灵活性。在进行测试时,我开始让 JUL 模仿 Logback 的默认模式。此模式包括线程的名称。事实证明 JUL 不能开箱即用。好吧,到现在我都没有错过,但我不认为这是一个日志框架应该缺少的东西。时期!
现在大多数(或许多)Java 项目都使用 Maven,因此添加依赖关系并不是什么大事,特别是如果该依赖关系相当稳定,即不会经常更改其 API。这似乎适用于 SLF4J。 SLF4J 罐子和朋友的尺寸也很小。
所以发生的奇怪的事情是,在使用了 SLF4J 之后,我实际上对 JUL 感到非常不满。我仍然很遗憾 JUL 必须这样。 JUL 远非完美,但可以胜任。只是不够好。同样可以说 Properties
作为一个例子,但我们不考虑抽象,所以人们可以插入他们自己的配置库和你有什么。我认为原因是 Properties
刚好高于标准,而今天的 JUL 恰恰相反……而在过去它是零,因为它不存在。
最终最终结论(也许)
(peterh 在 2022 年 10 月 2 日添加的部分)
Java 9 引入了 System.Logger ,它旨在作为日志实现的外观。因此,据我所知,它与 SLF4J 竞争,但具有包含在 JDK 中的优势。 那么也许库开发人员应该使用 System.Logger 而不是 SLF4J ?
我发现 Renato Athaydes 的 这篇博 文对其进行了很好的解释。 (顺便说一句:Renato 提到的 Log4j-v2 桥中的错误似乎已在 Log4j v2 的 v2.13.2 中 修复)
原文由 peterh 发布,翻译遵循 CC BY-SA 4.0 许可协议
免责声明:我是 log4j、SLF4J 和 logback 项目的创始人。
偏爱 SLF4J 是有客观原因的。一方面, SLF4J 允许最终用户自由选择底层日志记录框架。此外,精明的用户往往更喜欢 logback,它提供了 log4j 之外的功能,而 jul 则远远落后。功能方面的 jul 对某些用户来说可能就足够了,但对其他许多用户来说就不够了。简而言之,如果日志记录对您很重要,您会希望使用带有 logback 的 SLF4J 作为底层实现。如果日志记录不重要,那么 jul 就可以了。
然而,作为oss开发者,你需要考虑用户的喜好,而不仅仅是你自己的喜好。因此,您应该采用 SLF4J 并不是因为 您 确信 SLF4J 比 jul 更好,而是因为大多数 Java 开发人员目前(2012 年 7 月)更喜欢 SLF4J 作为他们的日志记录 API。如果最终您决定不关心大众意见,请考虑以下事实:
因此,将“确凿的事实”凌驾于舆论之上,虽然看似勇敢,但在这种情况下是一种逻辑谬误。
如果仍然不相信, JB Nizet 提出了一个额外而有力的论点:
如果出于某种原因你讨厌 SLF4J API 并且使用它会扼杀你工作的乐趣,那么一定要选择 jul 毕竟,有一些方法可以 将 jul 重定向到 SLF4J 。
顺便说一句,jul 参数化至少比 SLF4J 慢 10 倍,这最终产生了明显的差异。