这是我生命中的第一次,我发现自己处于一个可以开源的Java API的位置。希望被包括在许多其他项目中。
对于日志记录我(以及与我一起工作的人)总是使用JUL(java.util.logging)并且从未遇到任何问题。但是现在我需要更详细地了解我应该为我的API开发做些什么。我已经对此做了一些研究,并且我得到的信息让我感到更加困惑。因此这篇文章。
由于我来自JUL,我对此持偏见态度。我对其余的知识并不是那么大。
从我所做的研究中我得出了人们不喜欢JUL的原因:
所以真正的大问题是为什么不是JUL?我错过了什么?伐木立面的存在理由(SLF4J,JCL)是历史上存在多种伐木实施,其原因可以追溯到JUL之前的时代,正如我所看到的那样。如果JUL是完美的那么伐木外墙将不存在,或者什么?让事情变得更加混乱JUL在某种程度上是一个外观本身,允许交换处理程序,格式化程序甚至LogManager。
我们不应该首先质疑为什么它们是必要的,而不是采用多种方式来做同样的事情(日志记录)? (看看这些原因是否仍然存在)
好吧,到目前为止我的研究已经导致我可以看到的一些事情可能是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的表现与竞争对手相比并不好。我真的很担心我忽略了一些东西。除了JUL之外,使用伐木外墙和伐木实施是如此普遍,我必须得出结论,我只是不明白。那恐怕不是第一次。 :-)
那我该怎么办?我希望它成功。我当然可以“顺其自然”并实施SLF4J(这些日子似乎最受欢迎)但是为了我自己的缘故,我仍然需要了解今天的JUL究竟有什么问题需要保证所有的模糊?我会为我的图书馆选择JUL来破坏自己吗?
(nolan600于2012年7月7日添加的部分)
下面有一篇来自Ceki的参考文献,其中提到SLF4J的参数化比JUL快10倍或更快。所以我开始做一些简单的测试。乍一看,这种说法肯定是正确的。以下是初步结果(但请继续阅读!):
上面的数字是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肯定不那么热情了。
(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必须在底层日志记录实现的绑定上做一些时间。这不会吓到我。这些调用在应用程序的生命周期中应该是少见的。牢度应该在实际的日志调用中。
(nolan600于2012年7月8日添加的部分)
谢谢你的所有答案。与我最初的想法相反,我最终决定将SLF4J用于我的API。这是基于许多事情和您的意见:
所以发生的奇怪事情是我在使用SLF4J后对JUL感到非常不满。我仍然感到遗憾的是,JUL必须采用这种方式。 JUL远非完美,但有点像这样的工作。只是不太好。关于Properties
的例子也可以这样说,但我们不考虑抽象,所以人们可以插入他们自己的配置库,你有什么。我认为原因在于Properties
恰好位于栏杆上方,而今天的JUL却恰恰相反......而在过去它因为它不存在而在零位置上升。
免责声明:我是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。如果您最终决定不关心民意,请考虑以下事实:
因此,将“硬性事实”置于公众舆论之上,虽然看似勇敢,但在这种情况下是一种逻辑谬误。
如果仍然不相信,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倍,最终产生明显的差异。
java.util.logging
是在Java 1.4中引入的。之前有用于记录的原因,这就是为什么存在许多其他日志记录API的原因。那些在Java 1.4之前大量使用的API因此具有很好的市场份额,当1.4发布时它不会降到0。尽管如此,我认为JUL至少是目前其他日志框架的有效替代品。
恕我直言,使用像slf4j这样的日志外观的主要优点是你让库的最终用户选择他想要的具体日志记录实现,而不是将你的选择强加给最终用户。
也许他已经在Log4j或LogBack(特殊格式化程序,appender等)上投入了时间和金钱,并且更喜欢继续使用Log4j或LogBack,而不是配置jul。没问题:slf4j允许这样做。使用Log4j比jul更明智吗?也许,也许不是。但你不在乎。让最终用户选择他喜欢的东西。
我开始像我怀疑的那样,使用JUL因为它是最容易立即开始的。然而,多年来,我已经希望我花了一点时间选择。
我现在的主要问题是我们有大量的“库”代码在许多应用程序中使用,它们都使用JUL。每当我在Web服务类型的应用程序中使用这些工具时,日志记录就会消失或变得无法预测或奇怪。
我们的解决方案是为库代码添加一个外观,这意味着库日志调用没有改变,但是动态地重定向到可用的任何日志记录机制。当包含在POJO工具中时,它们被定向到JUL,但当部署为Web应用程序时,它们被重定向到LogBack。
我们的遗憾 - 当然 - 库代码不使用参数化日志记录,但现在可以在需要时对其进行改造。
我们使用slf4j来构建外观。
我通过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);
}
}