为什么不使用java.util.logging呢?

这是我人生中第一次发现自己正在编写一个开源的Java API。希望能被包括在其他项目中。

对于日志,我(以及与我一起工作的人)一直使用JUL (java.util.logging),并且从未遇到过任何问题。然而,现在我需要更详细地了解我应该为我的API开发做什么。我对此做了一些研究,根据我得到的信息,我只是更加困惑。所以才有了这篇文章。

因为我来自JUL,所以我对此有偏见。我对其他的知识就没那么多了。

从我所做的研究中,我得出了人们不喜欢JUL的这些原因:

  1. < p > “早在Sun发布JUL之前,我就开始用Java开发了,对我来说,继续使用logging-framework-X比学习新的东西更容易。”。嗯。我没开玩笑,人们就是这么说的。有了这个参数,我们都可以使用COBOL。(然而,我自己也很懒)

  2. < p > “我不喜欢jull中的日志级别名称”;。好吧,说真的,这还不足以成为引入新依赖项的理由。

  3. < p > “我不喜欢jull输出的标准格式”;。嗯。这只是构型。您甚至不需要在代码方面做任何事情。(确实,在过去,你可能不得不创建自己的Formatter类来正确地使用它)。

  4. < p > “我使用其他也使用logging-framework-X的库,所以我认为只使用那个库更容易”;。这是一个循环论证,不是吗?为什么“每个人”都使用logging-framework-X而不是JUL?

  5. < p > “其他人都在使用logging-framework- x”;。对我来说,这只是上述情况中的一个特例。多数人并不总是正确的。

所以真正的大问题是为什么不是JUL?。我错过了什么?关于日志facade (SLF4J, JCL)的原因是历史上已经存在了多种日志实现,其原因实际上可以追溯到JUL之前的时代。如果JUL是完美的,那么日志facade就不存在了。更让人困惑的是,JUL在某种程度上是一个门面,它允许处理程序、格式化器甚至LogManager交换。

与其采用多种方式来做同一件事(日志),我们难道不应该质疑为什么它们是必需的吗?(看看这些原因是否还存在)

好的,到目前为止,我的研究已经导致了一些事情,我可以看到可能是真正的问题与JUL:

  1. < p > 性能。有人说SLF4J的性能优于其他的。在我看来,这是一个不成熟的优化案例。如果您需要记录每秒数百兆字节的日志,那么我不确定您是否在正确的路径上。JUL也发生了变化,您在Java 1.4上所做的测试可能不再正确。你可以阅读它在这里和这个修复已经使它成为Java 7。许多人还讨论了日志方法中字符串连接的开销。然而,基于模板的日志记录避免了这种成本,而且它也存在于july中。懒得这么做。例如,如果我对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的表现与竞争对手相比不佳。

  2. < p > 来自类路径的配置。开箱即用的JUL不能从类路径加载配置文件。使它这样做的是代码行数很少。我知道为什么这可能很烦人,但解决方案很简单。

  3. < p > 输出处理程序的可用性。JUL提供了5个开箱即用的输出处理程序:控制台、文件流、套接字和内存。可以扩展这些功能,也可以编写新的功能。例如,可以写入UNIX/Linux Syslog和Windows事件日志。我个人从来没有这个需求,也没有看到它被使用过,但我肯定能理解为什么它可能是一个有用的功能。例如,Logback附带了一个用于Syslog的追加器。不过我还是会这么说

    1. 99.5%的输出目的地需求由JUL中的开箱即用的内容来满足。
    2. 特殊需求可以由JUL之上的自定义处理程序来满足,而不是在其他东西之上。对我来说,没有任何迹象表明为JUL编写Syslog输出处理程序比为另一个日志框架编写Syslog输出处理程序需要更多的时间。

我很担心我忽略了什么。除了JUL之外,日志门面和日志实现的使用是如此广泛,以至于我不得不得出结论,是我自己不理解。恐怕这不是第一次了。: -)

那么我应该如何处理我的API呢?我希望它能成功。我当然可以“顺其自然”。并实现SLF4J(这似乎是目前最流行的),但为了我自己的缘故,我仍然需要确切地了解今天的JUL有什么问题,这保证了所有的模糊?我会因为选择JUL作为我的图书馆而破坏自己吗?

测试性能

(nolan600于2012年7月7日新增)

下面有来自Ceki的关于SLF4J参数化比JUL快10倍或更多的参考。所以我开始做一些简单的测试。乍一看,这种说法当然是正确的。以下是初步结果(请继续阅读!):

  • 执行时间SLF4J,后端Logback: 1515
  • 执行时间SLF4J,后端JUL: 12938
  • 执行时间:16911

上面的数字是msec,所以越少越好。所以10倍的性能差异实际上已经非常接近了。我的第一反应是:太多了!

这是测试的核心。可以看到,在循环中构造了一个整数和一个字符串,然后在log语句中使用:

    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)的最新版本。标准输出和标准错误被重定向到空设备。

然而,现在要小心,原来JUL大部分时间都花在getSourceClassName()中,因为JUL默认情况下在输出中打印源类名,而Logback则不会。所以我们在比较苹果和橘子。我必须再次进行测试,并以类似的方式配置日志实现,以便它们实际上输出相同的内容。然而,我确实怀疑SLF4J+Logback仍然会名列榜首,但远低于上面给出的初始数字。请继续关注。

顺便说一句:这个测试是我第一次真正使用SLF4J或Logback。愉快的经历。当你刚开始的时候,JUL当然不那么受欢迎。

测试性能(第二部分)

(由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毫秒vs 0.3毫秒,如果我的分析器是准确的)。这是有道理的。SLF4J必须在底层日志实现的绑定上花费一些时间。这吓不倒我。在应用程序的生命周期中,这些调用应该很少。快速性应该体现在实际的日志调用中。

最终结论

(部分由peter于2012年7月8日添加)

谢谢你的回答。与我最初的想法相反,我最终决定为我的API使用SLF4J。这是基于一些事情和你的输入:

  1. 它提供了在部署时选择日志实现的灵活性。

  2. JUL配置在应用服务器中运行时缺乏灵活性的问题。

  3. SLF4J当然要快得多,特别是如果您将它与Logback结合在一起。即使这只是一个粗略的测试,我也有理由相信在SLF4J+Logback上比在JUL上进行了更多的优化。

  4. < p >文档。SLF4J的文档更加全面和精确。

  5. < p >模式的灵活性。在进行测试时,我开始让JUL模仿Logback中的默认模式。此模式包括线程的名称。事实证明,JUL无法开箱即用。好吧,直到现在我还没有错过它,但我不认为它是日志框架中应该缺少的东西。期!

  6. 现在大多数(或许多)Java项目都使用Maven,所以添加依赖项并不是什么大问题,特别是如果依赖项相当稳定,即不会经常更改其API。对于SLF4J来说,这似乎是正确的。而且SLF4J罐子和朋友的尺寸都很小。

所以发生的奇怪的事情是,在使用SLF4J工作了一段时间后,我实际上对JUL非常不满。我仍然很遗憾,JUL必须以这种方式出现。JUL远非完美,但也能做到这一点。只是还不够好。以Properties为例也可以这样说,但我们不考虑对其进行抽象,以便人们可以插入自己的配置库等。我认为原因是Properties刚好在酒吧之上,而相反的是今天的JUL…在过去,它是0,因为它不存在。

最终的结论(可能)

(部分由peter于02-OCT-2022添加)

Java 9引入了系统。日志记录器,它的目的是作为日志实现的facade。因此,据我所知,它与SLF4J竞争,但它的优势在于它包含在JDK中。因此,也许库开发人员应该使用System。Logger而不是SLF4J吗?

我发现Renato Athaydes的这篇博文很好地解释了它。(顺便说一句:Renato提到的Log4j-v2桥的bug似乎是Log4j v2的v2.13.2的固定)

83269 次浏览
  1. java.util.logging在Java 1.4中引入。在此之前,伐木也有用途。这就是存在许多其他日志api的原因。这些api在Java 1.4之前被大量使用,因此在1.4发布时,它们的市场份额并没有下降到零。

  2. JUL一开始并没有那么好。你提到的很多东西在1.4中都很糟糕,在1.5中才有所改善(我猜在6中也一样,但我不太确定)。

  3. JUL不太适合同一个JVM中具有不同配置的多个应用程序(想想不应该交互的多个web应用程序)。Tomcat需要通过一些步骤才能使其工作(如果我理解正确的话,可以有效地重新实现JUL)。

  4. 你不能总是影响你的库使用什么日志框架。因此,使用SLF4J(它实际上只是一个非常薄的API层,位于其他库之上)有助于保持整个日志记录世界的一致性(因此,您可以决定底层日志记录框架,同时在同一系统中仍然具有库日志记录)。

  5. 库不能轻易更改。如果以前版本的库使用logging-library-X,那么它就不能轻易切换到logging-library-Y(例如JUL),即使后者明显更好。该库的任何用户都需要学习新的日志框架,并且(至少)重新配置他们的日志。这是一个大禁忌,尤其是当它没有给大多数人带来明显的好处时。

说了这么多,我认为JUL是至少,是当今其他日志框架的有效替代品。

以我之见,使用slf4j这样的日志facade的主要优势在于,您可以让库的最终用户选择他想要的具体日志实现,而不是将您的选择强加给最终用户。

也许他已经在Log4j或LogBack(特殊的格式化程序、附加程序等)上投入了时间和金钱,并且更愿意继续使用Log4j或LogBack,而不是配置july .没问题:slf4j允许这样做。使用Log4j而不是jul是明智的选择吗?也许,也许不是。但你不在乎。让最终用户选择他喜欢的。

免责声明:我是log4j, SLF4J和logback项目的创始人。

偏爱SLF4J是有客观原因的。首先,SLF4J允许最终用户自由选择底层日志记录框架。此外,精明的用户往往更喜欢Logback提供了log4j之外的功能,而j.u.l则远远落后。有特色的j.u.l可能对某些用户来说已经足够了,但对其他许多用户来说还不够。简而言之,如果日志记录对您很重要,那么您将希望使用带日志返回的SLF4J作为底层实现。如果日志不重要,那么j.u.l就可以了。

然而,作为一个操作系统开发者,你需要考虑用户的偏好,而不仅仅是你自己的。因此,你应该采用SLF4J,不是因为相信SLF4J比j.u.l更好,而是因为大多数Java开发人员目前(2012年7月)更喜欢SLF4J作为他们的日志API。如果你最终决定不去在意大众的意见,考虑一下以下事实:

  1. 那些更喜欢j.u.l的人这样做是出于方便,因为j.u.l是与JDK捆绑在一起的。据我所知,没有其他客观的论据支持j。u。l。
  2. 你自己对j.u.l的偏好就是的偏好

因此,将“确凿的事实”置于公众舆论之上,虽然看似勇敢,但在这种情况下却是一个逻辑谬误。

如果仍然不相信,JB Nizet会做一个额外的有力的论证:

除非最终用户已经为他的用户做了这个定制 自己的代码,或者使用log4j或logback的另一个库。j.u.l是 可扩展的,但必须扩展logback, j.u.l, log4j和God 知道哪个日志框架,因为他使用了四个库 使用四种不同的日志框架是很麻烦的。通过使用SLF4J,您可以 允许他配置他想要的日志框架,而不是只配置一个 你已经选择了。记住,一个典型的项目使用无数的 图书馆,而不仅仅是你的.

.

.

如果出于某种原因,你讨厌SLF4J API,使用它会让你的工作失去乐趣,那么无论如何都要选择j.u.l。毕竟,有重定向j.u.l到SLF4J的方法。

顺便说一下,j.u.l参数化至少比SLF4J慢10倍,这最终产生了明显的差异。

我开始使用JUL,我怀疑,因为它是最容易立即上手的。然而,这些年来,我开始希望自己当初多花一点时间来做选择。

我现在的主要问题是,我们有大量的“库”代码,在许多应用程序中使用,他们都使用july .每当我在web服务类型的应用程序中使用这些工具时,日志就会消失或到不可预测或奇怪的地方。

我们的解决方案是向库代码添加一个facade,这意味着库日志调用不会改变,而是被动态重定向到任何可用的日志机制。当包含在POJO工具中时,它们被定向到JUL,但当部署为web应用程序时,它们被重定向到LogBack。

当然,我们的遗憾是库代码没有使用参数化日志记录,但现在可以在需要时进行改进。

我们使用slf4j来构建facade。

我通过logback-1.1.7在slf4j-1.7.21上运行jul,输出到SSD, Java 1.8, Win64

jul运行48449毫秒,logback 27185毫秒为1M循环。

尽管如此,对我来说,稍微快一点和更好一点的API还是不值3个库和800K。

package log;


import java.util.logging.Level;
import java.util.logging.Logger;


public class LogJUL
{
final static Logger logger = Logger.getLogger(LogJUL.class.getSimpleName());


public static void main(String[] args)
{
int N = 1024*1024;


long l = System.currentTimeMillis();


for (int i = 0; i < N; i++)
{
Long lc = System.currentTimeMillis();


Object[] o = { lc };


logger.log(Level.INFO,"Epoch time {0}", o);
}


l = System.currentTimeMillis() - l;


System.out.printf("time (ms) %d%n", l);
}
}

而且

package log;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


public class LogSLF
{
static Logger logger = LoggerFactory.getLogger(LogSLF.class);




public static void main(String[] args)
{
int N = 1024*1024;


long l = System.currentTimeMillis();


for (int i = 0; i < N; i++)
{
Long lc = System.currentTimeMillis();


logger.info("Epoch time {}", lc);
}


l = System.currentTimeMillis() - l;


System.out.printf("time (ms) %d%n", l);
}


}

JUL的原因:

  1. 用户不友好的API设计-笨拙的消息参数语法,奇怪的日志级别定义…这样的例子不胜枚举。

  2. 这是一个日志实现,而不是Facade。

正如引用的博客文章所指出的,这不是Java 9系统的目标。Logger定义用于日志记录的通用接口。SLF4J和Apache Commons Logging都尝试过这样的Facade目标,但是在我看来,它们都失败了,因为它们提供了太多的实现,而不是坚持定义良好的日志Facade这一简单目标。例如,一个真正的日志facade永远不会在“性能”方面与任何日志实现进行比较。因为facade本身不需要记录日志。

声明:对于日志facade,我重新发明了轮子,并使用了我自己的https://github.com/elf4j