文档结构  
翻译进度:已翻译     翻译赏金:0 元 (?)    ¥ 我要打赏

Java有三个主要的日志框架: Java Util Logging, Log4j 2, and Logback。当为你的项目挑选日志框架的时候,你考虑过他们的性能吗?毕竟,如果仅仅是因为你选择了一个缓慢的框架或者是配置了次优的配置,就让你的应用变慢,那也太蠢了。

当你登陆Twitter,查看Java主题的时候,你一定会发现大量的争论,这也许是最好的总结了,来自Dan Allen:

@connolly_s@rmannibucau Java的日志状况仍然不堪入目,由于这个荒谬的问题已经损失了数百万美金了。

— Dan Allen  (@mojavelinux) January 28, 2017

第 1 段(可获 1.45 积分)

实际上有很多人非常“幸运”,还没有遇到Java的日志问题:

  • 初级开发者通常没有权力作出这种类型的选择。
  • 中级开发者经常为代码细节工作,这个选择已经做好了。
  • 高级开发人员需要作出选择,因为他们的首席技术官或其他外部决策者会强迫他们做选择。

然而,迟早,你将达到这个地步,你必须作出日志框架的选择。在2017年,我们可以分解这个选择,共4个选项:

  1. 日志垫片
  2. Java Util Logging.
  3. Log4j 2.x
  4. Logback
第 2 段(可获 1.48 积分)

日志垫片

如果你是…

  • 开发 API
  • 开发共享库
  • 开发一个应用程序,将部署到不由你负责的执行环境(比如一个 .war 或者 .ear 文件,将被部署到共享应用容器)

… 那么日志框架的选择不是你的职责,你应该使用日志垫片。

一般而言,你有这两个选择:

  1. Apache Commons Logging
  2. SLF4J

但是Apache Commons logging API是如此的基础和有限,以至于最好的推荐是 不用, 除非你想这样写代码。

if (log.isDebugEnabled()) {
    log.debug("Applying widget id " + id + " flange color " + color);
}

推荐 SLF4J

第 3 段(可获 1.3 积分)

日志框架

如果您正在开发一个微服务,或其他一些,您对执行环境有控制权,的应用程序,您可能负责选择日志框架。可能会有一些情况,你决定推迟选择,暂时使用日志垫片,但你最终还是必须要做出选择。

从一个日志框架变为另一个日志框架通常是一个简单的搜索和替换,再坏一点,用更高级的的“结构性”检索替换,很多好一点的IDE都提供。如果日志性能对你很重要,日志框架的默认API总是使用日志垫片,因为这样转换开销更低,直接用日志框架编码可能更好。

第 4 段(可获 1.61 积分)

我还应该提到,框架之间存在着哲学上的差异:

所有三个框架都是分层日志框架,允许运行时修改配置和支持多样的输出位置。为了迷惑用户,不同的框架有时会使用不同的术语来处理同一件事情,比如:

  • Handler vs Appender – 这是一样的,把日志消息记录到日志文件.
  • Formatter vs Layout – 这是一样的,负责格式化日志消息到一个特定的样式。
第 5 段(可获 1.4 积分)

我们可以做一个特性比较, 但在我看来,唯一值得区分的功能是映射诊断上下文 (MDCs), 所有其他特征差异都可以由编码做的很接近。 映射诊断上下文 提供一种增强日志信息的方法,这种方法不需要直接修改实际记录日志地方的代码。举个例子,web应用程序可能会注入请求用户和请求的其他细节到MDC中, 这将允许拥有,依赖请求处理线程不同细节,的日志记录信息。

第 6 段(可获 1.15 积分)

有一些情况下,缺乏MDC支持可能不那么重要,例如:

  • 微服务架构中,没有共享的JVM, 因此,您必须在服务API中建立等效功能, 比如显式传递状态来替代MDC。
  • 在单用户应用程序中, 通常只能有一个上下文,也没有多少并发性,所以很少需要MDCs.

我们可以用易配置性做比较。 可悲的是,这三个的文档都同样没好好记录如何配置日志输出。没有任何一个提供了一个简单的“最佳实践”配置给普通用户。

  • 存到日志文件里,很绕。
  • 发送日志到操作系统级日志系统,比如syslog,windows event log,等等。

信息通常有,但隐藏在JavaDoc里,并且表达的也不清楚,让软件工程师或操作工程师搞不明白这个配置是否对软件性能有影响。

它们让我们自己处理性能。

第 7 段(可获 2.06 积分)

日志框架性能

好吧,让我们回到不同框架性能的基准测试。这里有大量的方式能让你迷失在Java微基准测试编写中。聪明的Java工程师用JMH写微基准测试。

在基准测试中经常遗漏的一件事是不日志的开销。真实世界中,不执行日志记录的记录器的日志记录语句,应完全从执行代码路径中删除。好的日志框架特性之一应该允许运行时改变日志记录级别。在现实中,我们至少需要定期检查记录器的级别的变化,所以我们不能完全消除日志记录语句。

日志基准测试的另一个方面,是可选配置。 每个不同的日志框架都有各自不同的默认输出配置。如果我们不配置所有的记录器使用相同的输出格式,我们就不太容易比较 。相反, 一些日志框架可能有特殊情况下的优化,具体的日志格式可能影响结果。

第 8 段(可获 2.38 积分)

这是我们想测试的情况:

  • 日志记录配置为不记录
  • 日志记录配置为Java Util Logging默认样式
  • 日志记录配置为Log4j 2.x默认样式
  • 日志记录配置为Logback默认样式

我们将只看写日志到滚动日志文件的性能。 我们也可以测试syslog handlers / appenders 或到控制台的日志记录,,但风险是,有一些来自外部系统(syslogd 或者 terminal)的单方面影响可能干扰测试。

第 9 段(可获 1.23 积分)

基准测试源代码可以从GitHub获取.

基准测试结构

每个基准测试都差不多是下面这个样子:

@State(Scope.Thread)
public class LoggingBenchmark {
    private static final Logger LOGGER = ...;
    private int i;

    @Benchmark
    public void benchmark() {
        LOGGER.info("Simple {} Param String", new Integer(i++));
    }
}

 

  • 记录器的引用存储为静态最终字段。
  • 每个线程都有独立的变量 i 。 这是为了防止 错误的共享 访问状态字段。
  • 为了 防止字符串连接优化,每次方法调用,参数都会改变。
  • 这个构造器new Integer(i++)是重要的,可以抵消自动装箱缓存。因为i几乎不在范围 -128到127之间,我们需要显式的装箱而不是隐式调用Integer.valueOf(int),也会污染我们的基准测试结果,因为它会自动检查缓存,而我们希望没有缓存。
第 10 段(可获 1.35 积分)

JMH 提供许多不同的模式运行,我们最感兴趣的两个是:

  • 吞吐量模式 – 测量每秒可以完成多少次方法调用,越高越好。
  • 采样模式 – 测量每次调用执行究竟花费了多少时间(精确至百分位),越低越好。.

样本的目的

基准测试和比较不同供应商的差异,有时是有益的,有益了解性能能被推进多远。由于这个原因,我的基准测试包含大量的测试样本。 在下面的图表中,在每一个场景,我选择了最好的样本。 在提供样本时,我的目标不是测试不同供应商的解决方案的性能。我的目标是提供内部上下文来帮助我们比较不同日志框架的性能。

第 11 段(可获 1.75 积分)

我的样本显示执行相同的操作的,尽可能高的性能实现。成本更高的性能也属于总体效用的损失。 通过提供样本,然而,我们可以看到测试机的性能。

通过一个例子, 不做日志的样本做如下处理:

  • 设置后台守护线程,每秒钟唤醒一次,并根据现有(实际上没有)文件设置enabled字段。
  • 每个日志语句检查 enabled 字段,仅在值为 true 时做日志。

写一个空日志语句,甚至不检查enabled字段,将是很容易的, 但JVM将内联空方法并完全移除日志记录。如果我们没有后台线程定期写入字段 (事实上,它总是写 false ,但JVM不知道) 然后JVM可以得出结论:没有人会改变该字段,并将得到的条件语句内联。在哪里样本能获得性能,,对于这种情况,是通过丢弃分层配置的方法。

第 12 段(可获 2.34 积分)

其他的样本实现都写入了相同格式的日志消息,并且性能得益于硬编码log formatter / layout 日期格式化和字符串连接等。 样本都格式化参数日志消息,并且是在运行时格式化,但是这些框架都没有什么安全检查。

Even horror clowns are interested in logging performance

基准测试结果

以下版本进行了测试:

  • Java 8 build 121
  • Apache Log4J version 2.7
  • Logback version 1.2.1

不做日志

No logging output - throughput

查看吞吐量图,我们可能认为我们已经直接找到了赢家,Java Util Logging。当然吞吐量似乎与线程数成线性关系,只是后面的样本,Log4J和Logback的吞吐量看上去随着线程数的增加而性能下降了。当然,这是神器,JMH吞吐量基准测试模式.

第 13 段(可获 1.8 积分)

框架性能的真正比较,是方法的实际运行时间,而不是每秒钟运行的次数。

No logging output - sample time

这些结果显示,三个框架的性能几乎没有区别。

Java Util Logging 输出格式

Java Util Logging其中一个有趣的因素是默认格式包括源代码类和方法的具体细节。如果你想知道源代码类和方法的具体细节,java 9之前有一个且只有一个办法: 创建一个异常并查看堆栈跟踪。这将是有趣的,查看Java 9的新 stack walker API,可以提高性能。

第 14 段(可获 1.53 积分)

然而,在这篇文章写作的时候, 无论Log4J 还是 Logback都集成了预发布的stack walker API,所以这是不公平的,那Java9的预发布版本和其他框架相比。

Java Util Loging formatJava Util Loging format

分析:

  • Java Util Logging 由于缺乏缓冲处理程序,性能受影响严重。
  • 在Log4j和Logback之间,性能没有太大的区别.
  • 不管Log4j还是Logback都只有我特意优化的样本的性能的一半。

基于性能,Java Util Logging 不推荐这个输出格式。在性能方面,Log4j和Logback没有明显区别 。

第 15 段(可获 1.25 积分)

需要仔细分析才能确定是否同选定样本的性能差异是由于日志框架的通用性还是没有进行潜在的进一步优化。

Log4j 2.x 默认输出格式

Log4j的输出格式是所有框架都很容易适应的。我们用它同样比较了同步和异步输出。

Log4j Output FormatLog4j Output Format

分析:

  • Java Util Logging 由于缺乏缓冲处理程序,性能受影响严重。
  • Logback’s 异步多线程追加不推荐,由于现在的实现里有一个待解决的bug
  • Log4j’s 同步追加是最快的。
  • Log4j’s 异步追加(无论有没有LMAX)跟Logback的同步追加吞吐量差不多。
  • 我的同步样本差不多比Log4j的同步追加快10%。我怀疑这是通用实例(general utility)的开销.
第 16 段(可获 1.98 积分)

Logback 默认输出格式

这种格式有一个微妙的怪癖。有一个缩写算法应用于日志记录器的名称。只有Logback原生支持这种格式。

这为我们提供了使用Java Util Logging自定义格式的机会,这允许我们查看Java Util Logging原生性能的一些问题。为了最大化比较的效用,我们也测试了其他框架不用缓冲器的性能。

Log4J不支持日志记录器的名称缩写,我取代了Apache Log4J配置中日志名字的最右侧36个字符。

第 17 段(可获 1.38 积分)

Logback Output FormatLogback Output Format

分析:

  • Java Util Logging is 使用自定义格式 更加优化和展示了Java Util Logging内部的一些问题,使得它接近了其他框架不使用缓冲区时的性能。性能仍然落后于其他框架。
  • 使用无缓冲的 handlers / appenders 对记录器有着明显性能影响。在日志性能很重要时,缓冲记录器明显是赢家。
  • Log4j的性能明显高于Logback,但是可能是由于记录器名字简单的原因。
  • Log4j的性能, Logback和样本缓冲记录器与Log4j格式化结果有可比性。
第 18 段(可获 1.36 积分)

给框架开发者的留言

在Java中做日志真正的挑战是配置框架成为你想要的格式,那花费大量的挖掘,研究和实践来获得一个我想要的,高性能的,容易使用的配置,作为比较的基础。我很确定,一些日志记录框架的开发人员会考虑我的配置的某些方面。

如果我们想推进Java日志前进,我首先,并坚决推荐先把文档弄好。除了Java Util Logging, 性能都接近我的样板实现,表明java日志框架通用代码是非常高效的,或者我写不出来更快的专用代码。

第 19 段(可获 1.59 积分)

日志框架应该提供更好的文档,关于如何配置记录器。 在理想的情况下, 他们应该提供的最佳配置日志的例子,分别用于:

  • 控制台
  • 不定大小的追加文件,至少一天一换。
  • 系统日志

这些都有不同的要求:

  • 控制台日志要求确保日志至少100ms刷新一次,这样人们在需要的时候才能看清。而且,视觉上的空格也是需要的,省略日期,缩写记录器名字,什么的。
  • 文件日志要求确保日志 占用的空间是有限的。当特殊情况需要检索日志文件时,包含日期将很有帮助。相对的,视觉上的空格就不那么重要,同样的记录器名字缩写也不那么重要。
  • 系统日志要求很严格的格式,可能很难适应多变的输出格式。
第 20 段(可获 2.05 积分)

结论

Java Util Logging在性能方面不推荐。它缺少缓冲器实现,对性能影响太大。

当配置记录器时, I/O文件缓冲的能力对性能有极大影响。风险是日志信息并不是那么即时的写入文件,程序崩溃很可能导致信息丢失,可能无法找出出现问题的根本原因。另一个问题是,缓冲文件尾不及时,无法实时跟踪系统。如果不同的日志框架提供最大缓冲时间的保证就好了。确保100ms刷新一次对系统性能的影响可能并不如每个日志语句刷新一次大,这也允许人们感知实时日志。

第 21 段(可获 1.93 积分)

这些基准测试了记录器在持续负载下的性能。同步记录器提供了罕见记录的低延迟,瓶颈是文件I/O。额外的工作,两个不同线程直接交换记录器也会影响吞吐量。同样的,像我们期望和发现的那样,异步记录器显示了比同步记录器更低的吞吐量。设计一个基准证明异步记录器的功能并非微不足道。你需要确保记录器输出不被文件I/O限制,这限制了整个的吞吐量。JMH被设计为度量最大化吞吐量,并且可能不是判断异步记录器声明益处的最合适的测试框架。

第 22 段(可获 1.58 积分)

对比Java Util Logging,无论Logback还是Log4j 2.x 都很好,在有性能需求时。Apache Log4J’s 同步记录器大约比  Logback的同步记录器快25%,给了它一个轻微优势。我需要指出Logback 1.2.1之前的版本性能明显很差,这些框架与我的样本之间存在不少差距,清晰地表明了框架还有不少优化空间。

第 23 段(可获 0.98 积分)

文章评论