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

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

对于日志logging,我(和我工作的人)总是使用JUL(java.util.logging),从来没有任何问题。 但是现在我需要更详细地了解我应该为我的API开发做些什么。 我已经做了一些这方面的研究,并用我得到的信息,我只是更加困惑。 因此这篇文章。

因为我来自JUL,所以我对此很有偏见。 我对其余的知识不是那么大。

从我所做的研究中,我已经想出了人们不喜欢JUL的原因:

  1. “在Sun发布JUL之前,我就开始用Java开发了,而且我继续使用logging-framework-X而不是学习新的东西” 。 嗯。 我不是在开玩笑,这其实是人们所说的。 有了这个论点,我们都可以做COBOL。 (但是我当然可以把这当作是一个懒惰的家伙)

  2. “我不喜欢JUL的日志级别的名字” 。 好吧,严重的是,这只是不足以引入新的依赖关系。

  3. “我不喜欢JUL输出的标准格式” 。 嗯。 这只是configuration。 你甚至不需要做任何代码明智的事情。 (事实上​​,在过去,您可能不得不创build自己的Formatter类才能正确使用)。

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

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

所以真正的大问题是为什么不是JUL? 。 我错过了什么? 日志外观(SLF4J,JCL)存在的理由是历史上存在多种日志实现方式,其原因实际上可以追溯到JUL看到的那个时代。 如果JUL是完美的,那么伐木外墙将不存在,或者什么? 而不是拥抱他们,我们不应该问为什么他们是必要的第一位? (看看这些原因是否还存在)

好的,到目前为止,我的研究已经导致了几个我可以看到的JUL可能是真正的问题

  1. 性能 。 有人说SLF4J的性能比其他的要好。 这在我看来是一个过早优化的例子。 如果你需要logging每秒数百兆字节,那么我不知道你是否在正确的道路上。 JUL也发展了,你在Java 1.4上做的testing可能不再是真实的。 你可以在这里阅读它,并且这个修补程序已经把它写入了Java 7中。许多人还谈到了日志logging方法中string串联的开销。 然而,基于模板的日志logging避免了这种成本,它也存在于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的performance与比赛相比并不好。

  2. 从类pathconfiguration 。 开箱即用的JUL无法从类path加载configuration文件。 这是几行代码 ,使它这样做。 我可以看到为什么这可能是烦人的,但解决scheme是简短的。

  3. 输出处理程序的可用性 。 JUL带有5个输出处理程序开箱即用:控制台,文件stream,套接字和内存。 这些可以扩展或者可以写入新的。 例如,这可以写入UNIX / Linux系统日志和Windows事件日志。 我个人从来没有这个要求,也没有看到它的使用,但我可以肯定,为什么它可能是一个有用的function。 例如,Logback带有一个用于Syslog的appender。 我仍然会争辩说

    1. JUL开箱即可满足输出目的地99.5%的需求。
    2. 特殊需求可以由JUL之上的定制处理程序来满足,而不是在其他东西之上。 这对我来说没有任何意义,这意味着为JUL编写Syslog输出处理程序需要比为其他日志框架编写更多的时间。

我真的很担心,我忽略了一些东西。 JUL以外的日志外观和日志实现的使用非常广泛,所以我必须得出这样的结论,那就是我只是不理解。 恐怕这不是第一次了。 🙂

那么我应该如何处理我的API呢? 我希望它成功。 我当然可以“顺其自然”地执行SLF4J(目前看来最受欢迎),但是为了我自己的缘故,我仍然需要明白今天的JUL有什么问题可以保证所有的模糊? 我会selectJUL为我的图书馆破坏自己吗?

testing性能

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

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

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

上面的数字是msecs,所以越less越好。 所以10倍的性能差距实际上是非常接近的。 我最初的反应是:很多!

这是testing的核心。 可以看出一个整数和一个string被构造在一个循环中,然后用在log语句中:

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

(我希望log语句有一个基本的数据types(在本例中是一个int)和一个更复杂的数据types(在这个例子中是一个string),不知道它的重要性,但是你有它。

SLF4J的日志语句:

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

JUL的日志声明:

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

在实际测量完成之前,JVM被一次执行相同的testing“加热”。 在Windows 7上使用了Java 1.7.03。使用了SLF4J(v1.6.6)和Logback(v1.0.6)的最新版本。 标准输出和stderr被redirect到空设备。

然而,现在请注意,JUL花费大部分时间在getSourceClassName()因为默认情况下,JUL将在输出中输出源类名,而Logback则不会。 所以我们比较苹果和橘子。 我必须再次进行testing,并以类似的方式configuration日志实现,以便它们实际输出相同的东西。 然而,我怀疑SLF4J + Logback仍然会排在最前面,但远不及上面所给出的数字。 敬请关注。

顺便说一句:这个testing是我第一次使用SLF4J或Logback。 一个愉快的经历。 当你出发时,JUL肯定会less得多。

testing性能(第2部分)

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

事实certificate,如何在JUL中configuration你的模式,性能无关紧要,即它是否包含源名称。 我尝试了一个非常简单的模式:

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

而这并没有改变上述时机。 我的分析器显示logging器仍然花费了很多时间调用getSourceClassName()即使这不是我的模式的一部分。 模式并不重要。

因此,我总结了性能问题,至less对于基于testing模板的日志语句,JUL(慢速)和SLF4J + Logback(快速)之间的实际性能差异似乎大致为10倍。 就像Ceki所说的那样。

我还可以看到另一件事,即SLF4J的getLogger()调用比JUL的同上要贵得多。 (95毫秒比0.3毫秒,如果我的分析器是准确的)。 这是有道理的。 SLF4J必须在底层日志实现的绑定上花费一些时间。 这不会吓到我 这些调用在应用程序的生命周期中应该是比较less见的。 坚牢度应该在实际的日志调用中。

定论

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

谢谢你的答案。 相反,我最初以为我最终决定使用SLF4J为我的API。 这是基于一些事情和你的input:

  1. 它可以灵活地在部署时select日志实施。

  2. 在应用程序服务器中运行时JULconfiguration缺乏灵活性的问题。

  3. 如果你将它与Logback结合起来,SLF4J的速度肯定会更快。 即使这只是一个粗略的testing,我仍然有理由相信SLF4J + Logback的优化比在JUL上做了更多的努力。

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

  5. 模式灵活性。 正如我所做的testing,我设定让JUL模仿Logback的默认模式。 这个模式包括线程的名字。 事实certificate,JUL不能做到这一点。 好的,到现在为止我还没有错过,但我不认为这是一个应该从日志框架中丢失的东西。 期!

  6. 现在大多数(或许多)Java项目都使用Maven,因此添加依赖项并不是什么大事,特别是如果依赖项相当稳定,也就是说不会经常更改它的API。 这对于SLF4J来说似乎是正确的。 此外,SLF4Jjar和朋友是小尺寸。

所以发生的一件奇怪的事情是我在用SLF4J工作了一段时间之后,真的对JUL感到难过。 我仍然很遗憾,在七月份这样的情况应该是这样的。 JUL离完美还很远,但是做得很好。 只是不够好。 关于Properties也可以这样说,但我们不考虑抽象,以便人们可以插入他们自己的configuration库以及你有什么。 我认为其原因在于Properties刚好在酒吧的上方,而今天的JUL却恰恰相反……而过去因为它不存在而成为零。

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

对于喜欢SLF4J有客观的原因。 首先,它授予最终用户select底层日志logging框架的自由。 另外,越来越多的用户倾向于使用提供log4j之外的function的logback ,而jul落后了。 function明智的JUL可能已经足够一些用户,但对于其他许多人来说,它不是。 简而言之,如果日志logging对您来说很重要,您可能希望使用带有logback的SLF4J作为基础实现。 如果logging不重要,那么朱尔是好的。

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

  1. 那些更喜欢jul的人是因为jul与JDK捆绑在一起而不方便的。 据我所知,没有其他有利于朱尔的客观论据
  2. 你对自己的偏好就是这样, 一个偏好

因此,把“硬性事实”置于舆论之上,虽然看起来很勇敢,但在这种情况下是一个逻辑上的谬误。

如果还不确定的话, JB Nizet提出了另一个有力的论点:

除了最终用户可能已经为自己的代码或使用log4j或logback的另一个库进行了此自定义。 jul是可扩展的,但不得不扩展logback,jul,log4j和上帝只知道哪个其他日志框架,因为他使用四个库,使用四个不同的日志框架是麻烦的。 通过使用SLF4J,你可以让他configuration他想要的日志框架,而不是你select的日志框架。 请记住,一个典型的项目使用无数的图书馆,而不仅仅是你的

如果因为某种原因,你讨厌SLF4J API,并且使用它将会把工作中的乐趣扼杀掉,那么通过一切手段去做,毕竟有办法将julredirect到SLF4J 。

顺便说一下,JUL参数化至less比SLF4J慢10倍,最终导致明显的差异。

  1. java.util.logging是在Java 1.4 java.util.logging引入的。 之前有使用日志logging,这就是为什么许多其他日志API存在。 那些在Java 1.4之前就被大量使用的API,因此在1.4发布的时候,它们的市场份额并不只是下降到0。

  2. JUL并没有提到所有那些很好的东西,你提到的很多东西在1.4中糟糕得多,而且在1.5中只有更好(我猜也是6,但我不太确定)。

  3. JUL不适合在同一个JVM中使用不同configuration的多个应用程序(考虑多个不应该交互的Web应用程序)。 Tomcat需要通过一些环节来实现这个目标(如果我正确地理解了这个,有效地重新实现JUL)。

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

  5. 图书馆不能轻易改变。 如果以前版本的库用于使用logging-library-X,那么即使后者明显优于它,也不能轻松切换到logging-library-Y(例如JUL):该库的任何用户都需要学习新的日志框架和(至less)重新configuration他们的日志logging。 这是一个很大的禁忌,特别是当它没有给大多数人带来明显的好处时。

说了这么多,我认为JUL现在至less是其他日志框架的有效select。

恕我直言,使用像slf4j这样的日志外观的主要优点是,你让图书馆的最终用户select他想要的具体日志实现,而不是强加给最终用户。

也许他已经投入了时间和金钱在Log4j或LogBack(特殊的格式化器,appender等),并且倾向于继续使用Log4j或者LogBack,而不是configurationjul。 没问题:slf4j允许的。 在JUL上使用Log4j是明智的select吗? 也许,也许不是。 但你不在乎。 让最终用户select他喜欢的东西。

我开始了,就像你怀疑的那样,使用JUL是因为这是最简单的一个。 然而,多年来,我希望我花了更多的时间select。

我现在的主要问题是,我们有大量的“库”代码,在许多应用程序中使用,他们都使用JUL。 每当我在Web服务types的应用程序中使用这些工具时,日志logging就会消失或变得不可预知或奇怪。

我们的解决scheme是为库代码添加一个外观,这意味着库日志调用没有改变,但是被dynamic地redirect到任何可用的日志机制。 当被包含在POJO工具中时,它们被引导到JUL,但是当被部署为web应用时,它们被redirect到LogBack。

我们的遗憾 – 当然是库代码不使用参数化日志logging,但现在可以根据需要进行改进。

我们用slf4j来build立门面。

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