首页 文章

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

提问于
浏览
288

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

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

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

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

  • "I started developing in Java long before Sun released JUL and it was just easier for me to continue with logging-framework-X rather than to learn something new" . 嗯 . 我不是在开玩笑,这实际上就是人们所说的 . 有了这个论点,我们都可以做COBOL . (但我当然可以说这是一个懒惰的家伙)

  • "I don't like the names of the logging levels in JUL" . 好吧,说真的,这还不足以成为引入新依赖的理由 .

  • "I don't like the standard format of the output from JUL" . 嗯 . 这只是配置 . 你甚至不需要做任何代码方面的事情 . (确实,过去你可能不得不创建自己的Formatter类来实现它) .

  • "I use other libraries that also use logging-framework-X so I thought it easier just to use that one" . 这是一个循环论证,isn 't ? Why does '每个人都使用logging-framework-X而不是JUL?

  • "Everybody else is using logging-framework-X" . 这对我来说只是上面的一个特例 . 多数并不总是正确的 .

所以真正重要的问题是 why not JUL? . 我错过了什么?存在存在的理由,或者是什么?我们不应该接受它们,而不应该首先质疑为什么它们是必要的? (看看这些原因是否仍然存在)

好吧,到目前为止,我的研究已经导致了一些我可以看到的与JUL有关的事情:

  • Performance . 有人说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的表现与竞争对手相比并不好 .

  • Configuration from classpath . 开箱即用的JUL无法从类路径加载配置文件 . 这是一个few lines of code . 我可以看出为什么这可能很烦人,但解决方案简短而简单 .

  • Availability of output handlers . JUL带有5个开箱即用的输出处理程序:控制台,文件流,套接字和内存 . 这些可以扩展,也可以写新的 . 例如,这可能是写入UNIX / Linux Syslog和Windows事件日志 . 我个人从未有过这个要求,也没有看过它,但我当然可以说明为什么它可能是一个有用的功能 . 例如,Logback附带了Syslog的附加程序 . 我仍然会争辩

  • 99.5%的输出目的地需求由JUL开箱即用的内容涵盖 .

  • 特殊需求可以通过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"

并没有改变上述时间 . 我的探查器显示,即使这不是我的模式的一部分, Logger 仍然花了很多时间来调用 getSourceClassName() . 这种模式并不重要 .

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

我还可以看到另一件事,即SLF4J的 getLogger() 电话比JUL更加昂贵's ditto. (95 ms vs 0.3 ms if my profiler is accurate). This makes sense. SLF4J has to do some time on the binding of the underlying logging implementation. This doesn' t吓唬我 . 这些调用在应用程序的生命周期中应该是少见的 . 牢度应该在实际的日志调用中 .

最后的结论

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

谢谢你的所有答案 . 与我最初的想法相反,我最终决定将SLF4J用于我的API . 这是基于许多事情和您的意见:

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

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

  • SLF4J肯定要快得多,如上所述,特别是如果你把它与Logback结合起来 . 即使这只是一个粗略的测试,我也有理由相信在SLF4J Logback上的优化要比JUL更多 .

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

  • 图案灵活性 . 当我进行测试时,我开始让JUL模仿Logback中的默认模式 . 此模式包含线程的名称 . 事实证明,JUL无法开箱即用 . 好吧,我到目前为止还没有错过它,但我认为这不应该是日志框架中遗漏的东西 . 期!

  • 今天大多数(或许多)Java项目使用Maven,因此添加依赖项并不是那么重要,特别是如果该依赖项相当稳定,即不会不断更改其API . SLF4J似乎也是如此 . SLF4J jar 和朋友也很小 .

所以发生的奇怪事情是我在使用SLF4J后对JUL感到非常不满 . 我仍然感到遗憾的是,JUL必须采用这种方式 . JUL远非完美,但有点像这样的工作 . 只是不太好 . 关于 Properties 也可以这样说,但是我们不考虑抽象,所以人们可以插入他们自己的配置库以及你有什么 . 我认为原因是 Properties 恰好位于栏杆上方,而今天的JUL却恰恰相反......而在过去,由于它不存在,所以它在零上方 .

5 回答

  • 172

    Disclaimer :我是log4j,SLF4J和logback项目的创始人 .

    偏爱SLF4J有客观原因 . 一个, SLF4J allows the end-user the liberty to choose the underlying logging framework . 此外,更精明的用户往往更喜欢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 . 如果您最终决定不关心民意,请考虑以下事实:

    • 喜欢j.u.l的人这样做是出于方便,因为j.u.l与JDK捆绑在一起 . 据我所知,没有其他客观论据支持j.u.l.

    • 你自己对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倍,最终产生明显的差异 .

  • 27

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

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

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

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

    我们使用slf4j来构建外观 .

  • 1
    • java.util.logging 是在Java 1.4中引入的 . 在此之前有记录用途,当释放1.4时,'s why many other logging APIs exist. Those APIs where used heavily before Java 1.4 and thus had a great marketshare that didn't只会降到0 .

    • JUL没有开始那么好,很多你提到的东西在1.4中差很多而且只在1.5中变得更好(我猜在6中也是如此,但我不太确定) .

    • JUL不适 Contract 一JVM中具有不同配置的多个应用程序(想想不应该交互的多个Web应用程序) . Tomcat需要跳过一些箍来实现这一点(如果我理解正确的话,有效地重新实现JUL) .

    • 您无法始终影响库使用的日志记录框架 . 因此,使用SLF4J(实际上只是一个非常薄的API层,高于其他库)有助于保持整个日志世界的一致性(因此您可以决定底层日志框架,同时仍在同一系统中进行库日志记录) .

    • 图书馆不能轻易改变 . 如果以前版本的库曾经使用过logging-library-X,它就不能轻易切换到logging-library-Y(例如JUL),即使后者显然是优等的:该库的任何用户都需要学习新的日志记录框架和(至少)重新配置他们的日志记录 . 这是一个很大的禁忌,特别是当它给大多数人带来明显的收益时 .

    尽管如此,我认为JUL至少是目前其他日志框架的有效替代品 .

  • 26

    我通过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);
        }
    
    }
    
  • 4

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

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

相关问题