记录已填充消息和异常堆栈跟踪的正确方法是什么?

logger.error(
    "\ncontext info one two three: {} {} {}\n",
    new Object[] {"1", "2", "3"},
    new Exception("something went wrong"));

我想产生一个类似这样的输出:

context info one two three: 1 2 3
java.lang.Exception: something went wrong
stacktrace 0
stacktrace 1
stacktrace ...

我的SLF4J版本是1.6.1。

这是我人生中第一次发现自己正在编写一个开源的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中修复了)

我的应用程序将部署在tcServer和WebSphere 6.1上。这个应用程序使用ehCache,因此需要slf4j作为依赖项。 因此,我将slf4j-api.jar (1.6) jar添加到war文件包中。

应用程序在tcServer中正常工作,除了以下错误:

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

然而,当我在WebSphere中部署时,我得到了一个java.lang.NoClassDefFoundError: org.slf4j.impl.StaticLoggerBinder。

同时伴有加载类“org.slf4j.impl.StaticMDCBinder”失败

我已经检查了两个应用服务器的类路径,没有其他slf4j jar。

有人知道这里会发生什么吗?