如何使用 testng、slf4s 和 logback 在 scala 单元测试中进行日志记录

发布于 2024-12-11 19:23:49 字数 2717 浏览 0 评论 0原文

我是 Scala 的新手,对 Java 的最新发展不太熟悉,所以我遇到了我认为是一个基本问题。

我正在编写一些 Scala 代码,并使用 ScalaTest 和 TestNG 通过测试装置对其进行测试。测试中的代码使用 slf4s 来执行日志记录,并由 logback 支持。

在我的“build.sbt”文件中,我有我需要的所有库的依赖项:

scalaVersion := "2.9.1"

// Add test dependencies on scalatest and testng

libraryDependencies ++= Seq("org.scalatest" %% "scalatest" % "1.6.1" % "test", "org.testng" % "testng" % "6.1.1" % "test")

// Use the slf4j logging facade for logging
libraryDependencies += "org.slf4j" % "slf4j-api" % "1.6.3"

//use the slf4j connectors to implement the JCL logging facade in terms of slf4j (which in turn is implemented in terms of logback)
//confused yet?
libraryDependencies += "org.slf4j" % "jcl-over-slf4j" % "1.6.3"

//use logback for the back-end slf4j logging impl.
libraryDependencies ++= Seq("ch.qos.logback" % "logback-core" % "0.9.30", "ch.qos.logback" % "logback-classic" % "0.9.30")

//use slf4s to expose the slf4j logging facade in scala

libraryDependencies += "com.weiglewilczek.slf4s" %% "slf4s" % "1.0.7"

//Add the dispatch HTTP client dependency

libraryDependencies ++= Seq(
  "net.databinder" %% "dispatch-http" % "0.8.5"
)

//I can't figure out how to use the dispatch HTTP client library, so just use the apache one

libraryDependencies += "org.apache.httpcomponents" % "httpclient" % "4.1.2"

我像这样执行日志记录(为了可读性而简化代码):

class MyClass extends Logging {
   def doSomething() {
      logger.debug("Hello world")
  }
}

当我运行一个测试来执行此代码时(使用“sbt test”命令)我做看不到调试消息,但我确实看到打印到控制台:

    SLF4J: The following loggers will not work because they were created
SLF4J: during the default configuration phase of the underlying logging system.
SLF4J: See also http://www.slf4j.org/codes.html#substituteLogger
SLF4J: MyClass

我在 src/test/resources 中有一个 logback.xml 文件,并且我知道日志记录本身正在工作,因为我看到 Apache HttpClient 库(使用 JCL)的输出)。

我错过了什么吗?我记录的信息有助于通过测试探索代码的行为,而且这似乎应该可行。我当然已经阅读了 http://www.slf4j.org/codes.html#substituteLogger 的页面 但我不知道在配置日志子系统之前我的记录器是如何创建的。

更新:这是我的 logback.xml 的内容:

<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <!-- encoders are assigned the type
     ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} %line --- %msg%n</pattern>
        </encoder>
    </appender>

    <root level="debug">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

I'm new to Scala, and not that familiar with recent developments in Java, so I am having what I assume is a basic problem.

I'm writing some Scala code, and testing it with test fixtures using ScalaTest and TestNG. The code under test uses slf4s to perform its logging, backed by logback.

In my 'build.sbt' file I have dependencies for all the libraries I need:

scalaVersion := "2.9.1"

// Add test dependencies on scalatest and testng

libraryDependencies ++= Seq("org.scalatest" %% "scalatest" % "1.6.1" % "test", "org.testng" % "testng" % "6.1.1" % "test")

// Use the slf4j logging facade for logging
libraryDependencies += "org.slf4j" % "slf4j-api" % "1.6.3"

//use the slf4j connectors to implement the JCL logging facade in terms of slf4j (which in turn is implemented in terms of logback)
//confused yet?
libraryDependencies += "org.slf4j" % "jcl-over-slf4j" % "1.6.3"

//use logback for the back-end slf4j logging impl.
libraryDependencies ++= Seq("ch.qos.logback" % "logback-core" % "0.9.30", "ch.qos.logback" % "logback-classic" % "0.9.30")

//use slf4s to expose the slf4j logging facade in scala

libraryDependencies += "com.weiglewilczek.slf4s" %% "slf4s" % "1.0.7"

//Add the dispatch HTTP client dependency

libraryDependencies ++= Seq(
  "net.databinder" %% "dispatch-http" % "0.8.5"
)

//I can't figure out how to use the dispatch HTTP client library, so just use the apache one

libraryDependencies += "org.apache.httpcomponents" % "httpclient" % "4.1.2"

I perform logging like this (code simplified for readability):

class MyClass extends Logging {
   def doSomething() {
      logger.debug("Hello world")
  }
}

when I run a test that exercises this code (using the 'sbt test' command) I do not see the debug message, but I do see this printed to the console:

    SLF4J: The following loggers will not work because they were created
SLF4J: during the default configuration phase of the underlying logging system.
SLF4J: See also http://www.slf4j.org/codes.html#substituteLogger
SLF4J: MyClass

I have a logback.xml file in src/test/resources, and I know logging itself is working as I see output from the Apache HttpClient library (which uses JCL).

Am I missing something? The information I'm logging is helpful in exploring the behavior of my code with tests, and besides it seems like this should work. I have of course read the page at http://www.slf4j.org/codes.html#substituteLogger but I don't see how my logger is getting created before the logging subsystem has been configured.

UPDATE: Here is the contents of my logback.xml:

<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <!-- encoders are assigned the type
     ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} %line --- %msg%n</pattern>
        </encoder>
    </appender>

    <root level="debug">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

慈悲佛祖 2024-12-18 19:23:49

我认为这是因为 SBT 并行运行测试,并且 Slf4j 中的一些初始化代码不是线程安全的(!)。
请参阅 http://jira.qos.ch/browse/SLF4J-167 ... 2年多前就已经报道过!

作为解决方法,我通过在测试运行之前加载根记录器来初始化 Slf4j。为此,只需将其添加到您的 SBT 设置中:

testOptions += Setup( cl =>
   cl.loadClass("org.slf4j.LoggerFactory").
     getMethod("getLogger",cl.loadClass("java.lang.String")).
     invoke(null,"ROOT")
)

I think it's because SBT runs the tests in parallel and some initialization code in Slf4j is not thread safe (!).
See http://jira.qos.ch/browse/SLF4J-167 ... it's been reported more than 2 years ago!

As a workaround I initialize Slf4j by loading the root logger before the tests run. To do so just add this to your SBT settings:

testOptions += Setup( cl =>
   cl.loadClass("org.slf4j.LoggerFactory").
     getMethod("getLogger",cl.loadClass("java.lang.String")).
     invoke(null,"ROOT")
)
佼人 2024-12-18 19:23:49

问题是,虽然第一个线程正在初始化底层日志记录实现(阻塞),但对于所有其他并发线程, SubstituteLoggerFactory 已创建。该替代记录器工厂返回 SubstituteLogger 而不是实际的记录器实现。 SLF4J-167 中未解决此问题。

在 Java 中不太可能遇到这个问题,因为记录器对象通常被创建为静态变量,因此 LoggerFactory 在类加载期间被初始化。在 Scala 中,没有 static 修饰符,伴生对象是延迟初始化的。
此外,Scala 中的大多数测试框架都是并行执行测试的。

要解决此问题,您可以更改测试环境:正如 Bruno Bieth 建议的那样,您可以在测试开始之前初始化 LoggerFactory。您也可以在测试代码而不是构建设置中执行此操作。您还可以将测试设置为按顺序运行,但这样会降低速度。

或者,您可以急切地初始化在伴生对象中初始化的 Logger。丑陋,但在大多数情况下确保并发创建的 Foo 对象不会使用 SubstituteLogger 进行初始化。

class Foo {
  val logger = Foo.singletonLogger
}

object Foo {
  val singletonLogger = LoggerFactory.getLogger(getClass)
}

The problem is that while the first thread is initializing the underlying logging implementation (blocking), for all other concurrent threads, SubstituteLoggerFactory is created. This substitute logger factory returns a SubstituteLogger instead the actual logger implementation. This issue isn't resolved in SLF4J-167.

It is less likely to meet this issue in Java, because often logger objects are created as a static variable, so the LoggerFactory is being initialized during the class loading. In Scala there is no static modifier and companion objects are initialized lazily.
Furthermore, most testing frameworks in Scala execute tests in parallel.

To workaround this issue, you can change the test environment: as Bruno Bieth suggested you can initialize the LoggerFactory before the tests start. You can do that also in the test code rather than the build settings. You can also set the test to run sequentially, but then you lose speed.

Alternatively you can eagerly initialize a Logger initialized in a companion object. Ugly, but in most cases ensures that Foo objects created concurrently won't be initialized with a SubstituteLogger.

class Foo {
  val logger = Foo.singletonLogger
}

object Foo {
  val singletonLogger = LoggerFactory.getLogger(getClass)
}
谁与争疯 2024-12-18 19:23:49

我有同样的问题。最终只是在类定义中实例化一个空记录器。

如果我将我的方法应用到你的代码中,那么

import com.weiglewilczek.slf4s.{Logger, Logging}

class MyClass with Logging {
   val _ = Logger("") // <--Solved problem

   def doSomething() {
      logger.debug("Hello world")
  }
}

请注意,我对 scala 非常陌生,所以我不知道我刚刚所做的事情的全部含义。

I had the same issue. Ended up just instantiating an empty logger in the class definition.

If I applied my method to your code then it would be,

import com.weiglewilczek.slf4s.{Logger, Logging}

class MyClass with Logging {
   val _ = Logger("") // <--Solved problem

   def doSomething() {
      logger.debug("Hello world")
  }
}

Note that I am very new to scala so I don't know the full implications of what I have just done.

渡你暖光 2024-12-18 19:23:49

slf4s 1.0.7 依赖于 slf4j 1.6.1,您可以在[此处][1]中看到。尝试使用此版本而不是 1.6.3 作为您的其他 slf4j 依赖项。

slf4s 1.0.7 depends on slf4j 1.6.1 as you can see [here][1]. Try to use this version instead of 1.6.3 for your other slf4j dependencies.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文