ChatGPT解决这个技术问题 Extra ChatGPT

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

我有生以来第一次发现自己处于编写开源 Java API 的位置。希望被包括在许多其他项目中。

对于日志记录,我(实际上是与我一起工作的人)一直使用 JUL (java.util.logging) 并且从未遇到任何问题。但是现在我需要更详细地了解我应该为我的 API 开发做些什么。我已经对此进行了一些研究,并且根据我获得的信息,我变得更加困惑。因此这篇文章。

因为我来自七月,所以我对此有偏见。我对其余部分的了解并不大。

根据我所做的研究,我提出了人们不喜欢 JUL 的以下原因:

“早在 Sun 发布 JUL 之前,我就开始使用 Java 进行开发,我继续使用 logging-framework-X 比学习新东西更容易”。唔。我不是在开玩笑,这实际上是人们所说的。有了这个论点,我们都可以做 COBOL。 (但我当然可以将其视为一个懒惰的家伙)“我不喜欢 JUL 中的日志记录级别的名称”。好吧,说真的,这还不足以成为引入新依赖项的理由。 “我不喜欢 JUL 输出的标准格式”。唔。这只是配置。您甚至不必在代码方面做任何事情。 (的确,在过去,您可能必须创建自己的 Formatter 类才能正确处理)。 “我使用其他也使用 logging-framework-X 的库,所以我认为使用它更容易”。这是一个循环论证,不是吗?为什么“每个人”都使用 logging-framework-X 而不是 JUL? “其他人都在使用 logging-framework-X”。对我来说,这只是上述情况的一个特例。大多数人并不总是正确的。

所以真正的大问题是为什么不是 JUL?我错过了什么?日志记录立面(SLF4J、JCL)存在的理由是历史上已经存在多个日志记录实现,而在我看来,其原因实际上可以追溯到 7 月之前的时代。如果 JUL 是完美的,那么日志立面将不存在,还是什么?更令人困惑的是,JUL 在某种程度上本身就是一个外观,它允许交换处理程序、格式化程序甚至 LogManager。

与其采用多种方式来做同样的事情(记录),我们难道不应该首先质疑为什么它们是必要的吗? (看看这些原因是否仍然存在)

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

表现。有人说 SLF4J 的性能优于其他性能。在我看来,这似乎是过早优化的情况。如果您需要每秒记录数百兆字节,那么我不确定您是否走在正确的道路上。 JUL 也在不断发展,您在 Java 1.4 上所做的测试可能不再适用。您可以在此处阅读有关它的信息,并且此修复程序已将其纳入 Java 7。许多人还谈到了日志记录方法中字符串连接的开销。然而,基于模板的日志记录避免了这种成本,它也存在于 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[] {用户名,valueX,valueY}); ..我当然会接受。许可授予 !谢谢您的帮助。所以我自己实际上并没有编写这样的语句,这是由 IDE 完成的。总之,关于性能问题,我没有发现任何表明 JUL 的性能与竞争对手相比不好的东西。来自类路径的配置。开箱即用的 JUL 无法从类路径加载配置文件。只需几行代码就可以做到这一点。我明白为什么这可能很烦人,但解决方案简短而简单。输出处理程序的可用性。 JUL 带有 5 个开箱即用的输出处理程序:控制台、文件流、套接字和内存。这些可以扩展或编写新的。例如,这可能是写入 UNIX/Linux 系统日志和 Windows 事件日志。我个人从未有过这个要求,也没有见过它使用过,但我当然可以理解为什么它可能是一个有用的功能。例如,Logback 带有一个用于 Syslog 的附加程序。尽管如此,我仍然认为 99.5% 的输出目的地需求都被 JUL 开箱即用的内容所覆盖。特殊需求可以通过 JUL 之上的自定义处理程序来满足,而不是在其他东西之上。对我来说,没有任何迹象表明为 JUL 编写 Syslog 输出处理程序比为另一个日志框架编写的时间更长。

我真的很担心我忽略了一些东西。除了 JUL 之外,日志门面和日志实现的使用如此广泛,以至于我不得不得出结论,就是我不明白。恐怕这不是第一次了。 :-)

那么我应该如何处理我的 API 呢?我希望它成功。我当然可以“顺其自然”并实现 SLF4J(这似乎是当今最流行的),但为了我自己,我仍然需要确切地了解今天的 JUL 到底有什么问题,这保证了所有的模糊性?为我的图书馆选择 JUL 会破坏自己吗?

测试性能

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

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

执行时间 SLF4J,后端 Logback:1515

执行时间 SLF4J,后端 JUL:12938

执行时间7月:16911

上面的数字是毫秒,所以越少越好。所以 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)。标准输出和标准错误被重定向到空设备。

但是,现在小心,事实证明 JUL 大部分时间都花在 getSourceClassName() 上,因为 JUL 默认会在输出中打印源类名称,而 Logback 不会。所以我们在比较苹果和橙子。我必须再次进行测试并以类似的方式配置日志记录实现,以便它们实际上输出相同的内容。然而,我确实怀疑 SLF4J+Logback 仍然会排在首位,但与上面给出的初始数字相去甚远。敬请关注。

顺便说一句:该测试是我第一次实际使用 SLF4J 或 Logback。一次愉快的经历。当你刚开始的时候,JUL 肯定不会那么受欢迎。

测试性能(第 2 部分)

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

事实证明,在 JUL 中如何配置模式对性能并不重要,即它是否包含源名称。我尝试了一个非常简单的模式:

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

这根本没有改变上述时间。我的分析器显示,即使这不是我的模式的一部分,记录器仍会花费大量时间调用 getSourceClassName()。图案无所谓。

因此,我在性能问题上得出结论,至少对于经过测试的基于模板的日志语句,JUL(慢)和 SLF4J+Logback(快)之间的实际性能差异似乎大约是 10 倍。就像Ceki说的那样。

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

定论

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

感谢您的所有回答。与我最初的想法相反,我最终决定将 SLF4J 用于我的 API。这是基于许多事情和您的输入:

它提供了在部署时选择日志实现的灵活性。在应用程序服务器中运行时,JUL 的配置缺乏灵活性的问题。 SLF4J 肯定比上面详述的要快得多,特别是如果你将它与 Logback 结合使用。即使这只是一个粗略的测试,我有理由相信在 SLF4J+Logback 上的优化要比在 JUL 上付出更多的努力。文档。 SLF4J 的文档更加全面和精确。模式灵活性。当我做测试时,我开始让 JUL 模仿 Logback 的默认模式。此模式包括线程的名称。事实证明,JUL 不能开箱即用。好的,直到现在我还没有错过它,但我认为它不应该是日志框架中缺少的东西。时期!今天大多数(或许多)Java 项目都使用 Maven,因此添加依赖项并不是什么大事,尤其是如果该依赖项相当稳定,即不会不断更改其 API。这对于 SLF4J 来说似乎是正确的。 SLF4J jar 和朋友的体积也很小。

所以发生的奇怪的事情是,在使用了 SLF4J 之后,我实际上对 JUL 非常不满。我仍然很遗憾 JUL 必须这样。 JUL 远非完美,但可以胜任。只是不够好。以 Properties 为例也可以这样说,但我们不考虑将其抽象化,以便人们可以插入他们自己的配置库和你拥有的东西。我认为原因是 Properties 出现在条形上方,而今天的 JUL 则相反……过去它出现为零,因为它不存在。

我不会要求结束,因为这个很好提出的问题很有趣,但是如果您阅读常见问题解答,那就是边缘:很难找到不基于意见的明确的唯一答案。
您可能错过的是许多框架作者放弃尝试使用 JUL,因此如果您不简单地制作 vanilla java,通常会更难使用它。
在提及早于 7 月的流行日志框架时,使用通用术语“logging-framework-X”会产生误导。在这种情况下,您应该使用“log4j”。其他流行的框架,如 SLF4J 和 logback,在 jul 发布之后就出现了。
@Acuariano。 Netty 项目只是使用反射来测试类路径上可用的日志框架。有关来源,请参见 here。见InternalLoggerFactory.java
@xenoterracide 更重要的是对 Java 9 的更新,因为它引入了 java.lang.System.Logger,这是一个 接口,只要该框架被捕获,它就可以重定向到您想要的任何实际日志框架up 并提供该接口的实现。结合模块化,如果您更喜欢不同的框架,您甚至可以使用不包含 java.util.logging 的捆绑 JRE 部署应用程序。

C
Ceki

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

偏爱 SLF4J 有客观原因。一方面,SLF4J 允许最终用户自由选择底层日志框架。此外,精明的用户往往更喜欢 logback which offers capabilities beyond log4j,而 jul 则远远落后。对于某些用户来说,功能方面的 jul 可能就足够了,但对于许多其他用户来说,它就不是了。简而言之,如果日志记录对您很重要,您可能希望使用带有 logback 的 SLF4J 作为底层实现。如果日志记录不重要,那么 jul 就可以了。

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

喜欢 jul 的人这样做是出于方便,因为 jul 与 JDK 捆绑在一起。据我所知,没有其他支持 jul 的客观论据,您自己对 jul 的偏好就是这样,一种偏好。

因此,将“确凿的事实”置于舆论之上,虽然看似勇敢,但在这种情况下却是一个逻辑谬误。

如果仍然不相信,JB Nizet 提出了一个额外的有力论据:

除了最终用户可能已经为他自己的代码或使用 log4j 或 logback 的另一个库完成了此自定义。 jul 是可扩展的,但是必须扩展 logback、jul、log4j 和上帝只知道其他哪个日志框架,因为他使用四个库,使用四个不同的日志框架很麻烦。通过使用 SLF4J,您允许他配置他想要的日志框架,而不是您选择的那个。请记住,一个典型的项目使用无数的库,而不仅仅是您的。

如果出于某种原因您讨厌 SLF4J API 并且使用它会扼杀您工作中的乐趣,那么请务必选择 jul 毕竟,有办法redirect j.u.l to SLF4J

顺便说一句,jul 参数化至少比 SLF4J 慢 10 倍,这最终会产生明显的差异。


@Ceki 您可能需要详细说明您的免责声明,以便提及您当前在 log4j、slf4j 和 logback 项目中的角色。原因自然是为了解释你的偏见。
对于大多数 Java 开发人员更喜欢 SLF4J 作为他们的日志 API 的说法,是否有一些支持?
我的帖子的本质是不同的开发者有不同的偏好,这似乎是无可争议的。是的?
我在这里,使用 SLF4J,我仍然必须处理其他库使用的所有其他日志框架。使用 SLF4J 并不能解决异构记录器的问题,只会让情况变得更糟。 xkcd.com/927
大声笑,我昨天不得不调试一个使用 SLF4J 的项目。日志记录功能什么也没做,确定原因是不可能的。 java.util.logging 也失败了。所以我求助于编写一个以附加模式打开文件的类(与日志记录一样,Java 变成了一个涉及 1/4 个对象的难题)。现在我正在调试另一个 Java 项目,但日志在那里也不起作用!而且我已经删除了另一个项目,所以我最终会从头开始重写我的文件附加程序。我不会尝试将 SLF4J 添加到项目中。
A
Aleksandr Dubinsky

java.util.logging 是在 Java 1.4 中引入的。在此之前有记录的用途。这就是存在许多其他日志记录 API 的原因。这些 API 在 Java 1.4 之前被大量使用,因此拥有很大的市场份额,而在 1.4 发布时不仅下降到零。七月的开始并没有那么好。你提到的许多事情在 1.4 中要糟糕得多,在 1.5 中才变得更好(我猜在 6 中也是如此,但我不太确定)。 JUL 不太适合在同一个 JVM 中具有不同配置的多个应用程序(想想不应该交互的多个 Web 应用程序)。 Tomcat 需要跳过一些障碍才能使其正常工作(如果我理解正确,则有效地重新实现 JUL)。你不能总是影响你的库使用的日志框架。因此,使用 SLF4J(实际上只是其他库之上的一个非常薄的 API 层)有助于保持整个日志世界的一致图景(因此您可以决定底层日志框架,同时仍然在同一系统中拥有库日志)。库不能轻易改变。如果以前版本的库曾经使用 logging-library-X,则它不能轻易切换到 logging-library-Y(例如 JUL),即使后者显然更胜一筹。该库的任何用户都需要学习新的日志记录框架并(至少)重新配置他们的日志记录。这是一个很大的禁忌,尤其是当它没有给大多数人带来明显的收益时。

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


谢谢约阿希姆,我很欣赏你的帖子。你的(1)和(2)对我来说只是历史。很久以前。你的 (4) 是一个结果,然后成为我所说的循环论证。但是,您的(3)确实很有趣。也许你正在做某事?但这只会影响那些正在构建应用程序容器的人,而最终这些人很少。或者是什么?
好吧,谁忽视历史注定要重蹈覆辙 ;-) 历史与软件开发非常相关。人们行动不会太快,只有在标准 API 至少与第三方库一样好工作时,用标准 API 替换现有的第三方库才能很好地工作。而且他们最初没有(并且可以说在某些情况下仍然没有)。
@nolan6000:我对细节的了解还不够,无法详细介绍该短语,这并不是我要说的重点。即使 JUL 现在与第三方框架相提并论,惯性和现有基础设施仍然是不切换的一个强有力的理由。例如,如果库 X 在 1.1 版本中使用 slf4j,那么在 1.2(甚至 2.0)中切换到 JUL 将是许多用户的主要问题(他们已经正确配置了旧系统并且必须重新执行此操作而没有明显的收益) .
@nolan6000 即使您不关心历史,您在应用程序中使用的库也肯定会关心。仅仅因为它使用与您不同的日志框架而不得不丢弃一个库并不有趣。
这里的第 3 点仍然是最相关的——正是因为 jul 是平台的一部分,所以它直接处于劣势。任何其他日志框架,您可以在具有独立配置的不同类加载器中拥有不同框架或同一框架的不同版本,但 jul 位于引导类路径上,因此根据定义,每个 JVM 都必须有一组配置。日志框架是您特别不希望与 JVM 捆绑在一起的东西。
J
JB Nizet

恕我直言,使用像 slf4j 这样的日志外观的主要优点是您可以让库的最终用户选择他想要的具体日志实现,而不是将您的选择强加给最终用户。

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


谢谢JB。我的问题是,我是否真的通过强制 JUL 对我的图书馆的用户/实施者强加那么多?如果他对例如 JUL 的标准输出处理程序不满意,他可以在部署时将它们换成他自己的,正如我所看到的那样。我真的不认为 JUL 是一件紧身衣。在我看来,它与其他人一样灵活和可扩展。
除了最终用户可能已经为他自己的代码或使用 log4j 或 LogBack 的其他库完成了此自定义。 jul 是可扩展的,但是必须扩展 LogBack、jul、log4j 和上帝只知道其他哪个日志框架,因为他使用 4 个库,使用 4 个不同的日志框架很麻烦。通过使用 slf4j,您可以让他配置他想要的日志框架。不是你选择的那个。请记住,典型的项目使用无数的库,而不仅仅是您的。
O
OldCurmudgeon

我开始,就像我怀疑的你一样,使用 JUL,因为它是最容易立即开始的。然而,多年来,我开始希望我能多花一点时间来选择。

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

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

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

我们使用 slf4j 来构建外观。


你有什么理由不只是在 slf4j 发行版中使用“将 java.util.logging 重定向到 slf4j”包?
我们做到了,但价值不大,因为迁移到 slf4j 的主要好处是有效的参数化日志记录。如果我们从一开始就使用它,我们现在就没有任何工作要做了。
我同意这是 slf4j 的唾手可得的成果。
w
weberjn

我在 logback-1.1.7 上针对 slf4j-1.7.21 运行了 jul,输出到 SSD、Java 1.8、Win64

jul 运行 48449 毫秒,1M 循环的 logback 27185 毫秒。

尽管如此,对我来说,更快的速度和更好的 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);
    }

}

你不是在比较同类。你为什么要为 jul 显式创建一个数组?我猜这是因为 slf4j 缺少 logger.info() 的单参数重载。因此,您故意削弱 jul 性能以弥补 slf4j 界面中的缺陷。相反,您应该按照惯用的编码方式对这两种方法进行编码。
你误会了。您不必使用额外的 800K。共识是非常薄的 SLF4J api 值得使用,因为这样您(或其他可能有一天会重用您的代码的人!)可以在 JUL、Logback、Log4j 等之间自由切换。SLF4J 只有 ~28K。 SLF4J 到 JUL 桥(slf4j-jdk...jar)只有 ~9K。