为什么不使用java.util.logging?
在我的生活中,我第一次发现自己处于一个正在编写一个开源的Java API的位置。 希望被纳入许多其他项目。
对于日志logging,我(和我工作的人)总是使用JUL(java.util.logging),从来没有任何问题。 但是现在我需要更详细地了解我应该为我的API开发做些什么。 我已经做了一些这方面的研究,并用我得到的信息,我只是更加困惑。 因此这篇文章。
因为我来自JUL,所以我对此很有偏见。 我对其余的知识不是那么大。
从我所做的研究中,我已经想出了人们不喜欢JUL的原因:
-
“在Sun发布JUL之前,我就开始用Java开发了,而且我继续使用logging-framework-X而不是学习新的东西” 。 嗯。 我不是在开玩笑,这其实是人们所说的。 有了这个论点,我们都可以做COBOL。 (但是我当然可以把这当作是一个懒惰的家伙)
-
“我不喜欢JUL的日志级别的名字” 。 好吧,严重的是,这只是不足以引入新的依赖关系。
-
“我不喜欢JUL输出的标准格式” 。 嗯。 这只是configuration。 你甚至不需要做任何代码明智的事情。 (事实上,在过去,您可能不得不创build自己的Formatter类才能正确使用)。
-
“我使用其他也使用logging-framework-X的库,所以我认为使用这个库更容易 。” 这是一个循环论证,不是吗? 为什么'每个人'使用日志框架-X而不是JUL?
-
“其他人都在使用logging-framework-X” 。 这对我来说只是上述的一个特例。 多数并不总是正确的。
所以真正的大问题是为什么不是JUL? 。 我错过了什么? 日志外观(SLF4J,JCL)存在的理由是历史上存在多种日志实现方式,其原因实际上可以追溯到JUL看到的那个时代。 如果JUL是完美的,那么伐木外墙将不存在,或者什么? 而不是拥抱他们,我们不应该问为什么他们是必要的第一位? (看看这些原因是否还存在)
好的,到目前为止,我的研究已经导致了几个我可以看到的JUL可能是真正的问题 :
-
性能 。 有人说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与比赛相比并不好。
-
从类pathconfiguration 。 开箱即用的JUL无法从类path加载configuration文件。 这是几行代码 ,使它这样做。 我可以看到为什么这可能是烦人的,但解决scheme是简短的。
-
输出处理程序的可用性 。 JUL带有5个输出处理程序开箱即用:控制台,文件stream,套接字和内存。 这些可以扩展或者可以写入新的。 例如,这可以写入UNIX / Linux系统日志和Windows事件日志。 我个人从来没有这个要求,也没有看到它的使用,但我可以肯定,为什么它可能是一个有用的function。 例如,Logback带有一个用于Syslog的appender。 我仍然会争辩说
- JUL开箱即可满足输出目的地99.5%的需求。
- 特殊需求可以由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:
-
它可以灵活地在部署时select日志实施。
-
在应用程序服务器中运行时JULconfiguration缺乏灵活性的问题。
-
如果你将它与Logback结合起来,SLF4J的速度肯定会更快。 即使这只是一个粗略的testing,我仍然有理由相信SLF4J + Logback的优化比在JUL上做了更多的努力。
-
文档。 SLF4J的文档只是更全面和更精确。
-
模式灵活性。 正如我所做的testing,我设定让JUL模仿Logback的默认模式。 这个模式包括线程的名字。 事实certificate,JUL不能做到这一点。 好的,到现在为止我还没有错过,但我不认为这是一个应该从日志框架中丢失的东西。 期!
-
现在大多数(或许多)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。 如果最终你决定不关心民意,请考虑以下事实:
- 那些更喜欢jul的人是因为jul与JDK捆绑在一起而不方便的。 据我所知,没有其他有利于朱尔的客观论据
- 你对自己的偏好就是这样, 一个偏好 。
因此,把“硬性事实”置于舆论之上,虽然看起来很勇敢,但在这种情况下是一个逻辑上的谬误。
如果还不确定的话, JB Nizet提出了另一个有力的论点:
除了最终用户可能已经为自己的代码或使用log4j或logback的另一个库进行了此自定义。 jul是可扩展的,但不得不扩展logback,jul,log4j和上帝只知道哪个其他日志框架,因为他使用四个库,使用四个不同的日志框架是麻烦的。 通过使用SLF4J,你可以让他configuration他想要的日志框架,而不是你select的日志框架。 请记住,一个典型的项目使用无数的图书馆,而不仅仅是你的 。
如果因为某种原因,你讨厌SLF4J API,并且使用它将会把工作中的乐趣扼杀掉,那么通过一切手段去做,毕竟有办法将julredirect到SLF4J 。
顺便说一下,JUL参数化至less比SLF4J慢10倍,最终导致明显的差异。
-
java.util.logging
是在Java 1.4java.util.logging
引入的。 之前有使用日志logging,这就是为什么许多其他日志API存在。 那些在Java 1.4之前就被大量使用的API,因此在1.4发布的时候,它们的市场份额并不只是下降到0。 -
JUL并没有提到所有那些很好的东西,你提到的很多东西在1.4中糟糕得多,而且在1.5中只有更好(我猜也是6,但我不太确定)。
-
JUL不适合在同一个JVM中使用不同configuration的多个应用程序(考虑多个不应该交互的Web应用程序)。 Tomcat需要通过一些环节来实现这个目标(如果我正确地理解了这个,有效地重新实现JUL)。
-
你不能总是影响你的库使用什么日志框架。 因此,使用SLF4J(实际上是其他库之上的非常薄的API层)有助于保持整个日志logging世界的某种一致的图像(因此,您可以决定底层日志logging框架,同时仍然在同一个系统中login库)。
-
图书馆不能轻易改变。 如果以前版本的库用于使用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); } }