对 log4j2使用 slf4j 是否值得

我无法决定是否在 log4j2中使用 slf4j。根据在线文章,看起来不会有任何性能打击,但它真的是必需的。

这些观点也有利于 log4j2:

  • SLF4J 强制应用程序记录字符串。如果您想记录文本,Log4j2API 支持记录任何 CharSequence,但是也支持按原样记录任何 Object。
  • Log4j 2 API 支持记录 Message 对象、 Java8 lambda 表达式和无垃圾记录(它避免了创建 vararg 数组,也避免了在记录 CharSequence 对象时创建 String)。
99013 次浏览

继续: 编写 log4j2API 代替 slf4j

它是安全的: Log4j2API 提供了与 slf4j 完全相同的保证,甚至更多。

既然 Log4j2本身已经分离为一个 API 和一个实现模块,那么使用 SLF4J 就不再有任何价值了。

是的,这是一个好的工程实践,保持你的选择开放。稍后可能需要更改为另一个日志实现。

在过去10年左右的时间里,在应用程序中建立这样的灵活性意味着要使用 SLF4J 这样的包装器 API。但是这种灵活性并不是免费的: 这种方法的缺点是您的应用程序不能使用底层日志库的更丰富的特性集。

Log4j2提供的解决方案不需要你的应用仅限于最小公分母。

逃生阀: log4j-to-slf4j

Log4j2包含一个 log4j-to-slf4j桥接模块。任何针对 Log4j2API 编码的应用程序都可以选择在任何时候将支持实现切换到任何符合 slf4j 的实现。

log4j-to-slf4j

正如问题中提到的,与使用像 slf4j 这样的包装器 API 相比,直接使用 Log4j2 API 提供了更多的功能,并且具有一些非功能性的优势:

  • 消息 API
  • 懒惰伐木的 Lambdas
  • 记录任何对象而不仅仅是字符串
  • 无垃圾: 尽可能避免创建变量或创建字符串
  • 完成后,CloseableThreadContext 会自动从 MDC 中删除项

(详情请参阅 10个 Log4j2 API 特性在 SLF4J 中不可用。)

应用程序可以安全地使用 Log4j2API 的这些丰富特性,而不必锁定在本机 Log4j2核心实现中。

SLF4J 仍然是您的安全阀,它只是不意味着您的应用程序应该针对 SLF4J API 的代码了。


披露: 我对 Log4j2有贡献。


更新: 对 Log4j2API 的编程似乎引入了一个“ facade for a facade”,这似乎有些令人困惑。Log4j2API 和 SLF4J 在这方面没有区别。

这两个 API 在使用本机实现时都需要2个依赖项,而对于非本机实现则需要4个依赖项。SLF4J 和 Log4j2API 在这方面是相同的。例如:

需要的依赖项 使用 log4j-API 作为 API 以 SLF4J 为空气污染指数
Log4j 2作为实现 2: log4j-api 和 log4j-core 4: slf4j,log4j-slf4j-impl,log4j-api,log4j-core
作为实现的回溯 4: log4j-api,log4j-to-slf4j,slf4j,Logback 2: slf4j 和 Logback

有相当多的考虑,使伐木“比乍看起来更复杂”,(因此,数十年的激烈内斗!).

关注点分离

在一天结束时,代码“发送日志数据”,而这些数据“最终到达某个地方”。但是它最终会在哪里结束取决于它被收集的目的。现代软件是由各种组件组成的,而且它们都可能需要记录日志,这使得情况变得非常复杂。

让我们考虑一个最坏的情况: 所有组件都使用 System.out#println(String)。至少所有的语句都是按执行顺序排列的,但是要分辨出哪个组件生成了每个输出部分可能并不容易。而且有些组件在使用它们的上下文中可能过于冗长。

让我们考虑下一个最坏的情况: 所有组件都为控制日志记录行为和目标做出自己的安排。管理员可能需要为一个软件配置几十个日志系统。现在日志语句不在一起,而且顺序错误。希望它们都有一个一致的时间戳策略!

我们想要一些介于两者之间的东西: 一些代码可以说‘ log this’和管理员可以控制它在哪里结束的东西。

过于简短的历史

进入 Log4J v1,它用“级别”、“附件”、“过滤器”、“布局”和“上下文”等概念解决了这个问题... ... 一个由层次化的“ logger 名称空间”支持的概念性建筑(包括一种自然利用 Java 包名称空间的方法) ,加上一个易于管理的配置机制。

这一切都很好... 只要软件中的所有组件都依赖于相同的版本!曾几何时,这些东西处于不断变化之中。SLF4J 的主要贡献是从组件开发人员的角度将这些概念“硬化”成一个稳定的 API,而不影响管理员完成他们那部分工作的选择。库可以依赖 SLF4J‘ facade’,期望它们只需要在堆栈中进行几次调用就可以与‘实现’进行对话。管理员可以选择适合他们的方式将日志组合成他们所关心的连贯记录。

(如果你的软件在一个容器中运行,而且容器有自己的日志记录需求,你甚至不是在容器中运行的同一个应用程序,那么情况就更加复杂了... ... Tomcat 的 JULI 日志——用于自己的内部日志记录——“避开了”在类加载器子上下文中运行的应用程序。)

令人费解的是,由于对 Log4J 的工作嗤之以鼻,Java Community Process 决定在 java.util.logging中实现几乎相同的概念性建筑,但在细节方面的灵活性可以说较低。然而,由于 j.u.l本质上是 SLF4J 语义丰富性的一个子集,所以很容易使 SLF4J 成为 j.u.l的一个外观。

Apache 的 Commons Util Logging 可能并不是很有必要。Ceki 自己的 Logback 引入了 Log4J v1当时没有的管理特性——不仅仅是 SLF4J 的一个实现,解决了所有那些非常真实的类加载器问题,而且还为管理员提供了一个有吸引力的特性的有效实现。

针对不同情况进行日志记录

但是日志记录是在许多不同的环境中完成的。将这些消息删除到超级慢的 I/O 而不会过度阻塞线程,除非需要,否则不会付出计算日志消息的代价,并且在多线程上下文中生成连贯的日志... ... 这些都很重要。(这就是为什么 java.util.logging不常用的原因!).

有时,所需的优化会影响到概念性建筑,而这又必然会影响到开发人员方面的 API。例如,如果由于过滤而导致日志消息最终成为 no-op,那么闭包提供的机会肯定会加快处理速度。要利用这个特性,需要考虑使用 SLF4J.next 或其他一些 API,而 Log4J2不需要被排除在这个决定之外。由于 API 部分是 SLF4J 提供的概念性超集,因此很容易将其作为 SLF4J 及其下的那些实现的外观,或者作为管理员所喜欢的更直接的桥梁。

对于应用程序开发人员来说,只要最终有一个由管理员选择的日志工具,并且所有组件都可以将日志输出到该工具中,那么这真的无关紧要。如果工具可以通过 SLF4J还有 Log4J2-the-API 接收消息,那么就是 很好。Log4J2-the-實現就是這樣做的。您可以有蛋糕,也可以吃蛋糕: 您的应用程序可以享受 Log4J2-the-API 提供的机会,同时仍然使用 SLF4J 充分满足的库。如果管理员轻视 Log4J2-the-实现(尽管从任何角度都很难看出他们为什么这么做) ,那么他们可以使用任何已经支持 SLF4J 的东西,而不必等待日志实现支持 Log4J2-the-API。你可以吃你的蛋糕。

对于 图书馆发展商而言,这更是一个问题。安全的路径是 SLF4J,因为它被广泛采用。如果日志记录对于库的成功至关重要... ... 特别是如果它是多线程的,那么生成日志语句可能会非常昂贵,如果它们最终不会被使用,如果有大量的日志语句需要处理,那么最好省略处理,性能也是至关重要的,而且用户可能会认识到 Log4J2的实现的好处,那么就使用 Log4J2。但是您也不能通过继续使用 SLF4J 来窃取用户的机会。如果管理员愿意,他们仍然可以使用 Log4J- 实现。

这是底线

如果您希望获得 Log4J2提供的特性,那么可以尝试使用它们。如果您不需要它们,SLF4J 是一个成熟、稳定的界面,提供了很多支持。SLF4J 仍然是开源基础设施的重要组成部分。Ceki 为社区做出了巨大的贡献,作为回报,他发了很多牢骚。

但是由有能力的实现支持的丰富 API 最终占了上风。今天的稳定是明天的停滞。细化的过程一直在进行。只要公共汽车是去你想去的地方,你就不必下车。