这是我人生中第一次发现自己正在编写一个开源的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". Hmm. I'm not kidding, this is actually what people say. With this argument we could all be doing COBOL. (however I can certainly relate to this being a lazy dude myself) "I don't like the names of the logging levels in JUL". Ok, seriously, this is just not enough of a reason to introduce a new dependency. "I don't like the standard format of the output from JUL". Hmm. This is just configuration. You do not even have to do anything code-wise. (true, back in old days you may have had to create your own Formatter class to get it right). "I use other libraries that also use logging-framework-X so I thought it easier just to use that one". This is a circular argument, isn't ? Why does 'everybody' use logging-framework-X and not JUL? "Everybody else is using logging-framework-X". This to me is just a special case of the above. Majority is not always right.

所以真正的大问题是为什么不是JUL?我错过了什么?关于日志facade (SLF4J, JCL)的原因是历史上已经存在了多种日志实现,其原因实际上可以追溯到JUL之前的时代。如果JUL是完美的,那么日志facade就不存在了。更让人困惑的是,JUL在某种程度上是一个门面,它允许处理程序、格式化器甚至LogManager交换。

与其采用多种方式来做同一件事(日志),我们难道不应该质疑为什么它们是必需的吗?(看看这些原因是否还存在)

好的,到目前为止,我的研究导致了一些事情,我可以看到可能是JUL的真正问题:

Performance. Some say that performance in SLF4J is superior to the rest. This seems to me to be a case of premature optimization. If you need to log hundreds of megabytes per second then I'm not sure you are on the right path anyway. JUL has also evolved and the tests you did on Java 1.4 may no longer be true. You can read about it here and this fix has made it into Java 7. Many also talk about the overhead of string concatenation in logging methods. However template based logging avoids this cost and it exist also in JUL. Personally I never really write template based logging. Too lazy for that. For example if I do this with JUL: log.finest("Lookup request from username=" + username + ", valueX=" + valueX + ", valueY=" + valueY)); my IDE will warn me and ask permission that it should change it to: log.log(Level.FINEST, "Lookup request from username={0}, valueX={1}, valueY={2}", new Object[]{username, valueX, valueY}); .. which I will of course accept. Permission granted ! Thank you for your help. So I don't actually write such statements myself, that is done by the IDE. In conclusion on the issue of performance I haven't found anything that would suggest that JUL's performance is not ok compared to the competition. Configuration from classpath. Out-of-the-box JUL cannot load a configuration file from the classpath. It is a few lines of code to make it do so. I can see why this may be annoying but the solution is short and simple. Availability of output handlers. JUL comes with 5 output handlers out-of-the-box: console, file stream, socket and memory. These can be extended or new ones can be written. This may for example be writing to UNIX/Linux Syslog and Windows Event Log. I have personally never had this requirement nor have I seen it used but I can certainly relate to why it may be a useful feature. Logback comes with an appender for Syslog for example. Still I would argue that 99.5% of the needs for output destinations are covered by what is in JUL out-of-the-box. Special needs could be catered for by custom handlers on top of JUL rather than on top of something else. There's nothing to me that suggests that it takes more time to write a Syslog output handler for JUL than it does for another logging framework.

我很担心我忽略了什么。除了JUL之外,日志门面和日志实现的使用是如此广泛,以至于我不得不得出结论,是我自己不理解。恐怕这不是第一次了。: -)

那么我应该如何处理我的API呢?我希望它能成功。当然,我可以“随大流”并实现SLF4J(这似乎是目前最流行的),但为了我自己的缘故,我仍然需要确切地了解今天的JUL有什么问题,这使得所有的争论都是错误的?我会因为选择JUL作为我的图书馆而破坏自己吗?

测试性能

(nolan600于2012年7月7日新增)

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

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

上面的数字是msec,所以越少越好。所以10倍的性能差异实际上已经非常接近了。我的第一反应是:太多了!

这是测试的核心。可以看到,在循环中构造了一个整数和一个字符串,然后在log语句中使用:

    for (int i = 0; i < noOfExecutions; i++) {
        for (char x=32; x<88; x++) {
            String someString = Character.toString(x);
            // here we 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)的最新版本。标准输出和标准错误被重定向到空设备。

但是,现在要注意,原来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毫秒vs 0.3毫秒,如果我的分析器是准确的)。这是有道理的。SLF4J必须在底层日志实现的绑定上花费一些时间。这吓不倒我。在应用程序的生命周期中,这些调用应该很少。快速性应该体现在实际的日志调用中。

最终结论

(部分由peter于2012年7月8日添加)

谢谢你的回答。与我最初的想法相反,我最终决定为我的API使用SLF4J。这是基于一些事情和你的输入:

It gives flexibility to choose log implementation at deployment time. Issues with lack of flexibility of JUL's configuration when run inside an application server. SLF4J is certainly a lot faster as detailed above in particular if you couple it with Logback. Even if this was just a rough test I have reason to believe that a lot more effort has gone into optimization on SLF4J+Logback than on JUL. Documentation. The documentation for SLF4J is simply a lot more comprehensive and precise. Pattern flexibility. As I did the tests I set out to have JUL mimic the default pattern from Logback. This pattern includes the name of the thread. It turns out JUL cannot do this out of the box. Ok, I haven't missed it until now, but I don't think it is a thing that should be missing from a log framework. Period! Most (or many) Java projects today use Maven so adding a dependency is not that big a thing especially if that dependency is rather stable, i.e. doesn't constantly change its API. This seems to be true for SLF4J. Also the SLF4J jar and friends are small in size.

So the strange thing that happened was that I actually got quite upset with JUL after having worked a bit with SLF4J. I still regret that it has to be this way with JUL. JUL is far from perfect but kind of does the job. Just not quite well enough. The same can be said about Properties as an example but we do not think about abstracting that so people can plug in their own configuration library and what have you. I think the reason is that Properties comes in just above the bar while the opposite is true for JUL of today ... and in the past it came in at zero because it didn't exist.

最终的结论(可能)

(部分由peter于02-OCT-2022添加)

Java 9引入了系统。记录器,它是日志实现的门面。因此,据我所知,它与SLF4J竞争,但它的优势在于它包含在JDK中。因此,也许库开发人员应该使用System。记录器而不是SLF4J ?

我发现Renato Athaydes的这篇博客文章解释得很好。(顺便说一下:Renato提到的Log4j-v2桥的bug似乎已经在Log4j v2的v2.13.2中修复了)


当前回答

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

and

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);
    }

}

其他回答

以我之见,使用slf4j这样的日志facade的主要优势在于,您可以让库的最终用户选择他想要的具体日志实现,而不是将您的选择强加给最终用户。

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

java.util.logging was introduced in Java 1.4. There were uses for logging before that. That's why many other logging APIs exist. Those APIs were used heavily before Java 1.4 and thus had a great market share that didn't just drop to zero when 1.4 was release. JUL didn't start out all that great. Many of the things you mentioned were a lot worse in 1.4 and only got better in 1.5 (and I guess in 6 as well, but I'm not too sure). JUL isn't well suited for multiple applications with different configurations in the same JVM (think multiple web applications that should not interact). Tomcat needs to jump through some hoops to get that working (effectively re-implementing JUL if I understood that correctly). You can't always influence what logging framework your libraries use. Therefore using SLF4J (which is actually just a very thin API layer above other libraries) helps keep a somewhat consistent picture of the entire logging world (so you can decide the underlying logging framework while still having library logging in the same system). Libraries can't easily change. If a previous version of a library used to use logging-library-X it can't easily switch to logging-library-Y (for example JUL), even if the latter is clearly superior. Any user of that library would need to learn the new logging framework and (at least) reconfigure their logging. That's a big no-no, especially when it brings no apparent gain to most people.

说了这么多,我认为JUL至少是目前其他日志框架的有效替代品。

我开始使用JUL,我怀疑,因为它是最容易立即上手的。然而,这些年来,我开始希望自己当初多花一点时间来做选择。

我现在的主要问题是,我们有大量的“库”代码,在许多应用程序中使用,他们都使用july .每当我在web服务类型的应用程序中使用这些工具时,日志就会消失或到不可预测或奇怪的地方。

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

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

我们使用slf4j来构建facade。

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

There are objective reasons for preferring SLF4J. For one, SLF4J allows the end-user the liberty to choose the underlying logging framework. In addition, savvier users tend to prefer logback which offers capabilities beyond log4j, with j.u.l falling way behind. Feature-wise j.u.l may be sufficient for some users but for many others it just isn't. In a nutshell, if logging is important to you, you would want to use SLF4J with logback as the underlying implementation. If logging is unimportant, j.u.l is fine.

然而,作为一个操作系统开发者,你需要考虑用户的偏好,而不仅仅是你自己的。因此,您应该采用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提出了一个额外的有力的论点:

Except the end user could have already done this customization for his own code, or another library that uses log4j or logback. j.u.l is extensible, but having to extend logback, j.u.l, log4j and God only knows which other logging framework because he uses four libraries that use four different logging frameworks is cumbersome. By using SLF4J, you allow him to configure the logging frameworks he wants, not the one you have chosen. Remember that a typical project uses myriads of libraries, and not just yours.

如果出于某种原因,您讨厌SLF4J API,并且使用它会使您的工作失去乐趣,那么无论如何都要选择j.u.l。毕竟,有方法可以将j.u.l重定向到SLF4J。

顺便说一下,j.u.l参数化至少比SLF4J慢10倍,这最终产生了明显的差异。

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

and

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);
    }

}