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

问题描述 投票:303回答:5

这是我生命中的第一次,我发现自己处于一个可以开源的Java API的位置。希望被包括在许多其他项目中。

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

由于我来自JUL,我对此持偏见态度。我对其余的知识并不是那么大。

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

  1. “在Sun发布JUL之前,我开始使用Java进行开发,而且我更容易继续使用logging-framework-X而不是学习新东西”。嗯。我不是在开玩笑,这实际上就是人们所说的。有了这个论点,我们都可以做COBOL。 (但我当然可以说这是一个懒惰的家伙)
  2. “我不喜欢JUL中日志记录级别的名称”。好吧,说真的,这还不足以成为引入新依赖的理由。
  3. “我不喜欢JUL输出的标准格式”。嗯。这只是配置。你甚至不需要做任何代码方面的事情。 (的确,过去你可能不得不创建自己的Formatter类来实现它)。
  4. “我使用其他也使用logging-framework-X的库,所以我觉得使用那个更容易”。这是一个循环论证,不是吗?为什么'每个人'都使用logging-framework-X而不是JUL?
  5. “其他人都在使用logging-framework-X”。这对我来说只是上面的一个特例。多数并不总是正确的。

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

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

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

  1. 性能。有人说SLF4J的表现优于其他表现。在我看来,这是一个过早优化的案例。如果你需要每秒记录数百兆字节,那么无论如何我都不确定你是否在正确的路径上。 JUL也在不断发展,你在Java 1.4上做的测试可能不再适用。你可以阅读它here,这个修复已经成为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无法从类路径加载配置文件。这是一个few lines of code。我可以看出为什么这可能很烦人,但解决方案简短而简单。
  3. 输出处理程序的可用性。 JUL带有5个开箱即用的输出处理程序:控制台,文件流,套接字和内存。这些可以扩展或可以编写新的。例如,这可能是写入UNIX / Linux Syslog和Windows事件日志。我个人从来没有这个要求,也没有看过它,但我当然可以说它为什么它可能是一个有用的功能。例如,Logback附带了Syslog的附加程序。我仍然会争辩 JUL开箱即用的产品涵盖99.5%的输出目的地需求。 特殊需求可以通过JUL之上的自定义处理程序来满足,而不是在其他方面。对我而言,没有任何迹象表明为JUL编写Syslog输出处理程序需要花费更多时间,而不是为另一个日志框架编写。

我真的很担心我忽略了一些东西。除了JUL之外,使用伐木外墙和伐木实施是如此普遍,我必须得出结论,我只是不明白。那恐怕不是第一次。 :-)

那我该怎么办?我希望它成功。我当然可以“顺其自然”并实施SLF4J(这些日子似乎最受欢迎)但是为了我自己的缘故,我仍然需要了解今天的JUL究竟有什么问题需要保证所有的模糊?我会为我的图书馆选择JUL来破坏自己吗?

测试性能

(nolan600于2012年7月7日添加的部分)

下面有一篇来自Ceki的参考文献,其中提到SLF4J的参数化比JUL快10倍或更快。所以我开始做一些简单的测试。乍一看,这种说法肯定是正确的。以下是初步结果(但请继续阅读!):

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

上面的数字是msecs,所以越少越好。因此,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在实际测量完成之前执行了相同的测试,“预热”了一次。在Windows 7上使用Java 1.7.03。使用了最新版本的SLF4J(v1.6.6)和Logback(v1.0.6)。 Stdout和stderr被重定向到null设备。

但是,现在小心,事实证明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 ms vs 0.3 ms)。这是有道理的。 SLF4J必须在底层日志记录实现的绑定上做一些时间。这不会吓到我。这些调用在应用程序的生命周期中应该是少见的。牢度应该在实际的日志调用中。

Final conclusion

(nolan600于2012年7月8日添加的部分)

谢谢你的所有答案。与我最初的想法相反,我最终决定将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远非完美,但有点像这样的工作。只是不太好。关于Properties的例子也可以这样说,但我们不考虑抽象,所以人们可以插入他们自己的配置库,你有什么。我认为原因在于Properties恰好位于栏杆上方,而今天的JUL却恰恰相反......而在过去它因为它不存在而在零位置上升。

java logging slf4j logback
5个回答
181
投票

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

偏爱SLF4J有客观原因。首先,SLF4J允许最终用户自由选择底层日志框架。此外,精明的用户往往更喜欢logback which offers capabilities beyond log4j,j.u.l落后。对于某些用户来说,功能方面的j.u.l可能就足够了,但对于其他许多用户来说,它可能就足够了。简而言之,如果日志记录对您很重要,您可能希望将SLF4J与logback一起用作底层实现。如果日志记录不重要,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.毕竟,有redirect j.u.l to SLF4J的手段。

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


29
投票
  1. java.util.logging是在Java 1.4中引入的。之前有用于记录的原因,这就是为什么存在许多其他日志记录API的原因。那些在Java 1.4之前大量使用的API因此具有很好的市场份额,当1.4发布时它不会降到0。
  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至少是目前其他日志框架的有效替代品。


27
投票

恕我直言,使用像slf4j这样的日志外观的主要优点是你让库的最终用户选择他想要的具体日志记录实现,而不是将你的选择强加给最终用户。

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


6
投票

我开始像我怀疑的那样,使用JUL因为它是最容易立即开始的。然而,多年来,我已经希望我花了一点时间选择。

我现在的主要问题是我们有大量的“库”代码在许多应用程序中使用,它们都使用JUL。每当我在Web服务类型的应用程序中使用这些工具时,日志记录就会消失或变得无法预测或奇怪。

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

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

我们使用slf4j来构建外观。


2
投票

我通过logback-1.1.7对抗slf4j-1.7.21,输出到SSD,Java 1.8,Win64

jul运行48449 ms,对于1M循环,回溯27185 ms。

不过,更快的速度和更好的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);
    }

}
© www.soinside.com 2019 - 2024. All rights reserved.