【问题标题】:SLF4J Initialization - Substitute LoggersSLF4J 初始化 - 替代记录器
【发布时间】:2024-01-01 02:18:01
【问题描述】:

我在 Scala 中做一个项目,我正在使用 slf4j 和 Logback 进行日志记录。现在,日志初始化似乎不是线程安全的。作为一种解决方案,slf4j 正在创建替代记录器,即 NoOp 记录器,它吞下初始化期间生成的日志语句。 The slf4j homepage 就此事表示:

替代记录器是在底层记录系统的默认配置阶段创建的

高度可配置的日志记录系统(例如 logback 和 log4j)可能会创建在自己的初始化期间调用记录器的组件。有关典型情况,请参阅问题 LOGBACK-127。但是,由于与 SLF4J 的绑定过程尚未完成(因为底层日志系统尚未完全加载到内存中),因此无法满足此类日志创建请求。

为了避免这种先有鸡还是先有蛋的问题,SLF4J 在这个阶段(初始化)创建了替代记录器。在此阶段对替代记录器的调用被简单地丢弃。初始化完成后,替代记录器会将记录调用委托给适当的记录器实现,否则将像 LoggerFactory 返回的任何其他记录器一样运行。

如果必须创建任何替代记录器,SLF4J 将发出此类记录器的列表。此列表旨在让您知道在初始化期间对这些记录器进行的任何记录调用都已被删除。

还有一个尚未解决的issue 描述了该问题。

对我来说,问题发生在我测试应用程序的各个部分如何协同工作时。在自己的线程中运行的生产者的日志语句丢失,因为它们被发送到替代记录器。在创建生产者线程之前添加一条日志语句似乎有助于及时初始化记录器。但是,我想知道对 LoggerFactory.getLogger 作为应用程序中的第一条语句的任意调用是否保证我永远不会登录到替代记录器。

简而言之,我的问题是:

  • LoggerFactory.getLogger(classOf[A]) 是否实例化所有记录器,或者可能是两个之后,对 LoggerFactory.getLogger(classOf[B]) 的并发调用将产生一个替代记录器?

  • 有没有办法获得保证,即检查记录器是否已初始化(我无法检查记录器的类型,因为它被 slf4j 门面隐藏)编辑 : 实际上,我只是想我也许可以检查记录器的类型。以下想法能否带来有用的解决方案?:

    def logger(context: Class[_]) = {
      log = LoggerFactory.getLogger(context)
      if (log.isInstanceOf[SubstituteLogger]) logger(context) else log
    

    我看到这种方法的问题是它依赖于一个实现特定的类,即 NOPLogger SubstituteLogger。

附录: 我不确定这是否与这个问题相关,但我将 slf4j 记录器包装在为每个日志记录上下文实例化的类中(上下文 = 调用记录器的类)。此外,还有一个创建此包装器实例的对象,该对象作为隐式构造函数参数传递给每个想要进行日志记录的类。我将记录器作为参数传递,而不是记录到静态对象(或混合特征)以在单元测试中传递特殊的记录器。

【问题讨论】:

  • 刚刚使用 Akka 得到了这个......这是因为记录器初始化是同时进行的多次(第一次访问记录器)。所以在应用启动时添加一个虚拟日志修复了它。
  • 我在 scalatest 并行测试中遇到了同样的问题。因为它对延迟不敏感,所以当log 是 SubstituteLogger 时,我已经修复了睡眠并重试

标签: scala logging slf4j logback race-condition


【解决方案1】:

我遇到了同样的问题,因为多个依赖项带来了它们自己的 NoOp Logger 副本。就我而言,解决方案是通过以下方式明确地对slf4j-log4j12 继承依赖:

libraryDependencies = Seq(
  ...
  ).map(_.exclude("org.slf4j", "slf4j-log4j12" ))

【讨论】: