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


351

我有生以来第一次发现自己正在写一个将开源的Java API。希望将其包含在许多其他项目中。

对于日志记录,我(实际上是与我一起工作的人)一直使用JUL(java.util.logging),并且从未遇到过任何问题。但是,现在我需要更详细地了解我应该为API开发做什么。我对此进行了一些研究,并根据所获得的信息感到更加困惑。因此,这个职位。

由于我来自七月,所以我对此有偏见。我对其余的知识并不多。

通过研究,我得出了人们不喜欢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在某种程度上是一种外观,它允许交换Handlers,Formatters甚至LogManager。

而不是采用多种方式做同一件事(记录),我们是否应该首先质疑为什么必须这样做?(并查看这些原因是否仍然存在)

好的,到目前为止,我的研究已经发现了一些可能是JUL 真正问题的东西:

  1. 表现。有人说SLF4J的性能优于其他产品。在我看来,这似乎是过早优化的一种情况。如果您需要每秒记录数百兆字节,那么我不确定您是否走在正确的道路上。JUL也已经发展,您在Java 1.4上进行的测试可能不再正确。您可以在此处阅读有关此内容的信息,此修复程序已将其纳入Java7。许多人还谈到了日志记录方法中字符串连接的开销。但是,基于模板的日志记录避免了这种成本,并且它在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. 从classpath配置。开箱即用的JUL无法从类路径加载配置文件。这样做需要几行代码。我可以理解为什么这可能会令人讨厌,但是解决方案又短又简单。

  3. 输出处理程序的可用性。JUL带有5个开箱即用的输出处理程序:控制台,文件流,套接字和内存。这些可以扩展,也可以编写新的。例如,这可能正在写入UNIX / Linux Syslog和Windows事件日志。我个人从来没有这个要求,也没有看到它使用过,但是我可以肯定地解释为什么它可能是一个有用的功能。例如,Logback附带了Syslog的附加程序。我仍然认为

    1. 开箱即用的JUL可以满足99.5%的输出目的地需求。
    2. 定制处理程序可以在JUL之上而不是其他之上满足特殊需求。对于我而言,没有什么可以比为其他日志记录框架花费更多的时间来为JUL编写Syslog输出处理程序的。

我真的很担心我忽略了某些事情。除了JUL之外,对日志外观和日志实现的使用是如此广泛,以至于我不得不得出结论,就是我自己不了解。恐怕这不是第一次。:-)

那么我应该如何处理我的API?我希望它成功。我当然可以“顺其自然”并实现SLF4J(这是当今最流行的),但是出于我自己的考虑,我仍然需要确切地了解今天的JUL到底有什么毛病?我会为图书馆选择JUL来破坏自己吗?

测试表现

(部分由nolan600在2012年7月7日添加)

以下来自Ceki的参考资料表明SLF4J的参数化速度是JUL的10倍甚至更快。所以我已经开始做一些简单的测试。乍一看,这一主张当然是正确的。这是初步结果(但请继续阅读!):

  • 执行时间SLF4J,后端登录:1515
  • 执行时间SLF4J,后端JUL:12938
  • 执行时间七月:16911

上面的数字是毫秒,所以越少越好。所以实际上十倍的性能差异实际上是非常接近的。我最初的反应:很多!

这是测试的核心。可以看出,整数和字符串在循环中构造,然后在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被重定向到空设备。

但是,现在要小心,事实证明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毫秒vs. 0.3毫秒(如果我的分析器是准确的))。这很有道理。SLF4J必须在底层日志记录实现的绑定上花费一些时间。这不吓我。这些调用在应用程序的生命周期中应该很少见。牢度应在实际的日志调用中。

定论

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

感谢您的所有答复。与我最初的想法相反,我最终决定对我的API使用SLF4J。这基于许多因素和您的输入:

  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恰好在标准之上,而对于今天的7月则恰恰相反……过去,由于它不存在,所以归零。


8
我不会结束发言,因为这个提出的问题很有趣,但是如果您阅读FAQ,这将是临界点:很难找到不基于观点的明确唯一答案。
DenysSéguret12年

您可能会错过的是,许多框架作者都放弃了尝试使用JUL的尝试,因此,如果您不只是制作普通的Java,通常很难使用它。
DenysSéguret2012年

3
当提到流行的日志记录框架之前,使用通用术语“ logging-framework-X”会产生误导。在这种情况下,您应该使用“ log4j”。在jul发布之后,诸如SLF4J和logback之类的其他流行框架也很不错。
Ceki 2012年

1
@Acuariano。Netty项目只是使用反射来测试类路径上可用的日志记录框架。参见此处获取源代码。请参阅InternalLoggerFactory.java
peterh

1
@xenoterracide更为重要的是对Java 9的更新(如它所介绍的)java.lang.System.Logger,它是一个接口,只要该框架能够实现并提供该接口的实现,它就可以重定向到所需的任何实际日志记录框架。结合模块化,您甚至可以部署带有不包含的捆绑JRE的应用程序java.util.logging(如果您喜欢其他框架)。
Holger

Answers:


207

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

有客观原因偏爱SLF4J。首先,SLF4J允许最终用户自由选择底层的日志记录框架。此外,精明的用户往往更喜欢logback,它提供的功能超出log4j,而jul却落后了。对于某些用户而言,按功能选择的jul可能就足够了,但对于其他许多用户而言,还不够。简而言之,如果日志记录对您很重要,则需要将SLF4J与logback一起用作基础实现。如果日志记录不重要,则可以使用jul。

但是,作为oss开发人员,您需要考虑用户的偏好,而不仅仅是您自己的偏好。因此,您应该采用SLF4J并不是因为确信SLF4J比jul更好,而是因为当前(2012年7月)大多数Java开发人员更喜欢SLF4J作为其日志记录API。如果最终您决定不关心民意,请考虑以下事实:

  1. 那些喜欢jul的人这样做是出于方便,因为jul与JDK捆绑在一起。据我所知,没有其他客观论据支持jul
  2. 您自己的喜好了七月,只是,一个偏好

因此,在这种情况下,将“硬事实”置于舆论之上虽然看上去很勇敢,但在逻辑上是谬误。

如果仍然不能说服,JB Nizet会提出另一个有效的论点:

除非最终用户可能已经为自己的代码或使用log4j或logback的其他库完成了此自定义操作。jul是可扩展的,但是必须扩展logback,jul,log4j,而God只知道哪个其他日志记录框架,因为他使用了四个使用了四个不同日志记录框架的库。通过使用SLF4J,您可以允许他配置他想要的日志记录框架,而不是您选择的框架。请记住,一个典型的项目会使用无数的库,而不仅仅是您的库

如果您出于某种原因而讨厌SLF4J API,并且使用它会扼杀工作中的乐趣,那么请务必使用jul。毕竟,可以将jul重定向到SLF4J

顺便说一下,jul参数化比SLF4J慢至少10倍,这最终产生了显着差异。


2
@Ceki您可能需要详细说明免责声明,以便它提及您在log4j,slf4j和logback项目中的当前角色。原因自然是为了解释您的偏见。
托尔比约恩Ravn的安徒生

2
大多数Java开发人员更喜欢SLF4J作为日志记录API的说法是否得到支持?
奥利维尔·卡柳克斯

3
我的文章的实质是不同的开发人员有不同的偏好,这似乎是无可争议的。是?
Ceki 2016年

1
老实说,我很乐意看到Java 11(或最终结果)以及异步模式下的log4j2的2018年基准测试。
xenoterracide

5
在这里,我正在使用SLF4J,但我仍然必须处理其他库使用的所有其他日志记录框架。使用SLF4J不能解决异构记录器的问题,只会使情况变得更糟。xkcd.com/927
查理(Charlie)

34
  1. java.util.logging是Java 1.4中引入的。在此之前有日志记录的用途,这就是为什么存在许多其他日志记录API的原因。这些API在Java 1.4之前就大量使用,因此占有很大的市场份额,而在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 至少可以替代其他日志记录框架。


1
感谢Joachim,感谢您的帖子。您的(1)和(2)对我来说只是历史。很久以前。您的(4)是其结果,然后成为我所谓的循环参数。您的(3)确实很有趣。也许您正在做某事?但这只会影响那些正在构建应用程序容器的人,这些应用程序容器最终只有很少的人。要不然是啥?
peterh 2012年

3
好吧,无视历史的人注定要重复它;-)历史与软件开发非常相关。人们并不会太快,并且只有在标准API的运行效果至少与第三方库一样好的情况下,才能使用标准API替换现有的第三方库。而且他们最初并没有(而且在某些情况下可以说仍然没有)。
约阿希姆·绍尔

约阿希姆(Joachim),我对您提到的“在某些情况下仍然可以忽略”感兴趣。那是必须的肉。在现有代码中替换记录器库非常简单,如今可以自动化。SLF4J有一个工具可以证明我的观点。因此,我认为2002年用log4j编写的大型库可以通过自动化工具在数分钟内转换为JUL。(不过,我不知道是否存在)。那为什么不发生呢?
peterh 2012年

3
@ nolan6000:我对这些短语的细节了解不多,这并不是我要讲的重点。即使JUL现在可以与第三方框架相提并论,惯性和现有的基础结构仍然是不切换的强烈理由。例如,如果库X在版本1.1中使用了slf4j,则对于许多用户(他们已经正确配置了旧系统,并且不得不重做而没有明显收获),在1.2(甚至2.0)中切换到JUL将是一个主要问题。 。
约阿希姆·绍尔

@ nolan6000即使不关心历史,您在应用程序中使用的也绝对可以。仅仅因为使用与您不同的日志记录框架而不得不放弃一个库就没有意思了。
托尔比约恩Ravn的安徒生

29

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

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


谢谢JB。我的问题是,我是否真的通过强加JUL来强加给我的库用户/实现者?例如,如果他对JUL的标准输出处理程序不满意,则可以在部署时将它们换成自己的,如我所见。我并不认为JUL是一件紧身衣。在我看来,它与其他方法一样灵活和可扩展。
peterh 2012年

12
除非最终用户可能已经为自己的代码或使用log4j或LogBack的另一个库进行了此自定义。jul是可扩展的,但是必须扩展LogBack,jul,log4j,上帝只知道哪个其他日志记录框架,因为他使用了4个使用4个不同日志记录框架的库。通过使用slf4j,您允许他配置他想要的日志记录框架。不是您选择的那个。请记住,典型的项目会使用大量的库,而不仅仅是您的库。
JB Nizet

6

我像您怀疑的那样开始使用JUL,因为它是立即开始使用的最简单的方法。然而,多年来,我开始希望自己花更多的时间进行选择。

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

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

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

我们使用slf4j来构建外观。


1
有什么原因不只是在slf4j发行版中使用了“将java.util.logging重定向到slf4j”包?
托尔比约恩Ravn的安徒生

2
我们做到了,但收效甚微,因为迁移到slf4j的主要好处是有效的参数化日志记录。如果我们从一开始就使用它,那么我们现在就没有任何工作要做。
OldCurmudgeon 2012年

1
我同意这是slf4j的低挂水果。
托尔比约恩Ravn的安徒生

3

我在logback-1.1.7上针对slf4j-1.7.21运行了jul,输出到SSD,Java 1.8,Win64

jul运行了48449毫秒,对于1M循环,登录27185毫秒。

不过,对我来说,更快一点和更好一点的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);
    }

}

2
您不是在按赞比较。为什么要为jul显式创建一个数组?我猜这是因为slf4j缺少一个参数的重载logger.info()。因此,您故意削弱了jul的性能,以弥补slf4j界面的缺点。您应该改用惯用的方式对这两种方法进行编码。
Klitos Kyriacou

2
你误会了 您不必额外使用800K。共识是,非常薄的 SLF4J api值得使用,因为这样您(或其他可能重复使用一天代码的人!)可以在JUL,Logback,Log4j等之间自由切换。SLF4J仅为〜28K。SLF4J到JUL桥(slf4j-jdk ... jar)仅为〜9K。
riskop
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.