为什么不使用java.util.logging?最终结论

2022-08-31 04:37:30

这是我人生中第一次发现自己处于这样一个位置,我正在编写一个开源的Java API。希望能被纳入许多其他项目。

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

由于我来自7月,我对此有偏见。我对其余部分的了解并不大。

根据我所做的研究,我提出了人们不喜欢JUL的这些原因:

  1. “早在 Sun 发布 JUL 之前,我就已经开始使用 Java 进行开发,对我来说,继续使用 logging-framework-X 比学习新东西更容易。嗯。我不是在开玩笑,这实际上是人们说的。有了这个论点,我们都可以做COBOL。(然而,我当然可以理解这是一个懒惰的家伙)

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

  3. “我不喜欢 JUL 输出的标准格式”。嗯。这只是配置。您甚至不必在代码方面执行任何操作。(没错,在过去,您可能必须创建自己的格式化程序类才能正确使用)。

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

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

所以真正的大问题是为什么不是JUL?。我错过了什么?日志记录外观(SLF4J,JCL)存在的理由是历史上存在多个日志记录实现,其原因实际上可以追溯到我所看到的JUL之前的时代。如果JUL是完美的,那么伐木立面就不会存在,或者什么?为了使事情更加混乱,JUL在某种程度上本身就是一个门面,允许处理程序,格式化程序甚至LogManager被交换。

与其采用多种方式来做同样的事情(日志记录),难道我们不应该质疑为什么它们首先是必要的吗?(看看这些原因是否仍然存在)

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

  1. 性能。有人说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的表现与竞争对手相比不正常的东西。

  2. 从类路径进行配置。开箱即用的 JUL 无法从类路径装入配置文件。只需几行代码即可实现此目的。我可以看到为什么这可能很烦人,但解决方案简短而简单。

  3. 输出处理程序的可用性。JUL 附带 5 个开箱即用的输出处理程序:控制台、文件流、套接字和内存。这些可以扩展,也可以编写新的。例如,这可能是写入UNIX / Linux系统日志和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,后端日志:1515
  • 执行时间 SLF4J,后端 7 月:12938
  • 执行时间 JUL: 16911

上面的数字是毫秒,所以越少越好。因此,性能差异的10倍实际上是非常接近的。我的第一反应是:那是很多!

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

    for (int i = 0; i < noOfExecutions; i++) {
        for (char x=32; x<88; x++) {
            String someString = Character.toString(x);
            // here we log 
        }
    }

(我希望 log 语句同时具有基元数据类型(在本例中为 int)和更复杂的数据类型(在本例中为 String)。不确定它是否重要,但你有它。

SLF4J 的日志语句:

logger.info("Logging {} and {} ", i, someString);

JUL 的日志语句:

logger.log(Level.INFO, "Logging {0} and {1}", new Object[]{i, someString});

JVM在实际测量完成之前通过执行一次相同的测试进行“预热”。Java 1.7.03在Windows 7上使用。使用了最新版本的 SLF4J (v1.6.6) 和 Logback (v1.0.6)。Stdout 和 stderr 已重定向到空设备。

但是,现在要小心,事实证明 JUL 花费了大部分时间,因为 JUL 默认在输出中打印源类名,而 Logback 则不会。因此,我们正在比较苹果和橙子。我必须再次进行测试,并以类似的方式配置日志记录实现,以便它们实际上输出相同的内容。然而,我确实怀疑SLF4J + Logback仍将名列前茅,但与上面给出的初始数字相去甚远。敬请期待。getSourceClassName()

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

测试性能(第 2 部分)

(nolan600 于 08 年 2012 月 <> 日添加的部分)

事实证明,在 JUL 中如何配置模式对于性能并不重要,即它是否包含源名称。我尝试了一个非常简单的模式:

java.util.logging.SimpleFormatter.format="%4$s: %5$s [%1$tc]%n"

这根本没有改变上述时间。我的分析器显示,记录器仍然花了很多时间进行调用,即使这不是我模式的一部分。模式无关紧要。getSourceClassName()

因此,我在性能问题上得出结论,至少对于经过测试的基于模板的日志语句,JUL(慢速)和SLF4J + Logback(快速)之间的实际性能差异似乎大约为10倍。就像Ceki说的。

我还可以看到另一件事,即SLF4J的调用比JUL的同类产品贵得多。(如果我的分析器准确无误,则为 95 毫秒与 0.3 毫秒)。这是有道理的。SLF4J 必须在底层日志记录实现的绑定上做一些时间。这不会吓到我。这些调用在应用程序的生存期内应该有些罕见。快速度应该在实际的日志调用中。getLogger()

最终结论

(nolan600 于 08 年 2012 月 <> 日添加的部分)

感谢您的所有回答。与我最初的想法相反,我最终决定使用SLF4J作为我的API。这是基于许多事情和您的输入:

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

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

  3. SLF4J肯定要快得多,如上所述,特别是如果您将其与Logback结合使用。即使这只是一个粗略的测试,我也有理由相信,在SLF4J + Logback上进行优化的努力比在JUL上付出了更多的努力。

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

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

  6. 如今,大多数(或许多)Java项目都使用Maven,因此添加依赖项并不是一件大事,特别是如果该依赖项相当稳定,即不会不断更改其API。对于SLF4J来说,这似乎是正确的。此外,SLF4J罐子和朋友的尺寸也很小。

所以奇怪的事情是,在与SLF4J合作过一段时间后,我实际上对JUL感到非常沮丧。我仍然感到遗憾的是,JUL必须以这种方式。JUL远非完美,但有点做这项工作。只是不够好。作为一个例子,也可以这样说,但我们不考虑抽象,这样人们就可以插入他们自己的配置库,你有什么。我认为原因是它出现在酒吧上方,而今天的七月则相反......在过去,它以零进入,因为它不存在。PropertiesProperties


答案 1

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

偏爱SLF4J是有客观原因的。首先,SLF4J允许最终用户自由选择底层日志记录框架。此外,更精明的用户倾向于选择logback,它提供的功能超出了log4j,而j.u.l则落后了。功能方面的j.u.l对于某些用户来说可能就足够了,但对于许多其他用户来说,它却不是。简而言之,如果日志记录对您很重要,您可能希望使用带有日志回溯的 SLF4J 作为基础实现。如果日志记录不重要,j.u.l 是可以的。

但是,作为oss开发人员,您需要考虑用户的偏好,而不仅仅是您自己的偏好。因此,您应该采用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和上帝只知道哪个其他日志记录框架,因为他使用四个使用四个不同日志记录框架的库是繁琐的。通过使用SLF4J,您可以允许他配置他想要的日志记录框架,而不是您选择的框架。请记住,一个典型的项目使用无数的库,而不仅仅是你的库。

如果出于某种原因你讨厌SLF4J API,使用它会扼杀你工作的乐趣,那么一定要选择j.u.l。毕竟,有办法将j.u.l重定向到SLF4J

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


答案 2
  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. 图书馆不能轻易改变。如果以前版本的库曾经使用loging-library-X,它不能轻易地切换到loging-library-Y(例如JUL),即使后者明显更优越。该库的任何用户都需要学习新的日志记录框架,并(至少)重新配置其日志记录。这是一个很大的禁忌,特别是当它没有给大多数人带来明显的收益时。

话虽如此,我认为JUL至少是当今其他日志记录框架的有效替代方案。


推荐