SBT运行scala和java之间的差异?

Qui*_*ibo 9 java scala sbt log4j2

我正在尝试遵循SBT 0.12.1项目中的log4j2配置教程.这是我的build.sbt:

name := "Logging Test"

version := "0.0"

scalaVersion := "2.9.2"

libraryDependencies ++= Seq(
  "org.apache.logging.log4j" % "log4j-api" % "2.0-beta3",
  "org.apache.logging.log4j" % "log4j-core" % "2.0-beta3"
)
Run Code Online (Sandbox Code Playgroud)

我有两个独立的主要课程.第一个是logtest.ScalaTestsrc/main/scala/logtest/ScalaTest.scala中:

package logtest

import org.apache.logging.log4j.{Logger, LogManager}

object ScalaTest {
  private val logger = LogManager.getLogger(getClass())
  def main(args: Array[String]) {
    logger.trace("Entering application.")
    val bar = new Bar()
    if (!bar.doIt())
      logger.error("Didn't do it.")

    logger.trace("Exiting application.")
  }
}
Run Code Online (Sandbox Code Playgroud)

第二个是logtest.JavaTestsrc/main/java/logtest/JavaTest.java中:

package logtest;

import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;

public class JavaTest {
  private static Logger logger = LogManager.getLogger(JavaTest.class.getName());

  public static void main(String[] args) {
    logger.trace("Entering application.");
    Bar bar = new Bar();

    if (!bar.doIt())
      logger.error("Didn't do it.");

    logger.trace("Exiting application.");
  }
}
Run Code Online (Sandbox Code Playgroud)

如果我logtest.ScalaTest.main() 从sbt内部运行,我得到了我期望的输出,因为src/main/resources/log4j2.xml将根日志记录级别设置为trace:

> run-main logtest.ScalaTest
[info] Running logtest.ScalaTest 
10:26:23.730 [run-main] TRACE logtest.ScalaTest$ - Entering application.
10:26:23.733 [run-main] TRACE logtest.Bar -  entry
10:26:23.733 [run-main] ERROR logtest.Bar - Did it again!
10:26:23.733 [run-main] TRACE logtest.Bar -  exit with (false)
10:26:23.733 [run-main] ERROR logtest.ScalaTest$ - Didn't do it.
10:26:23.733 [run-main] TRACE logtest.ScalaTest$ - Exiting application.
[success] Total time: 0 s, completed Dec 21, 2012 10:26:23 AM
Run Code Online (Sandbox Code Playgroud)

但是,当我logtest.JavaTest.main()从内部运行时 ,我得到不同的输出

> run-main logtest.JavaTest
[info] Running logtest.JavaTest 
ERROR StatusLogger Unable to locate a logging implementation, using SimpleLogger
ERROR Bar Did it again!
ERROR JavaTest Didn't do it.
[success] Total time: 0 s, completed Dec 21, 2012 10:27:29 AM
Run Code Online (Sandbox Code Playgroud)

据我所知,ERROR StatusLogger Unable to ...通常表明log4j-core不在我的类路径上.缺少TRACE消息似乎表明我的log4j2.xml设置也不在类路径上.如果我运行Foo.main与LoggerTest.main,为什么类路径会有任何差异?或者是否有其他原因造成这种行为?

更新

我使用SBT Assembly来构建这个项目的胖罐,并指定logtest.JavaTest作为主类.从命令行运行它会产生正确的结果:

$ java -jar "Logging Test-assembly-0.0.jar" 
10:29:41.089 [main] TRACE logtest.JavaTest - Entering application.
10:29:41.091 [main] TRACE logtest.Bar -  entry
10:29:41.091 [main] ERROR logtest.Bar - Did it again!
10:29:41.091 [main] TRACE logtest.Bar -  exit with (false)
10:29:41.091 [main] ERROR logtest.JavaTest - Didn't do it.
10:29:41.091 [main] TRACE logtest.JavaTest - Exiting application.
Run Code Online (Sandbox Code Playgroud)

GitHub示例

按照Edmondo1984的建议,我把一个完整的例子放在github上.

Edm*_*984 1

此类问题通常是由于类加载差异造成的,在这种情况下,差异是不小的。

在此初始化阶段,LogManager首次加载类时将调用静态初始化程序。如果您查看静态初始值设定项内部,您将看到:

        Enumeration<URL> enumResources = null;
        try {
            enumResources = cl.getResources(LOGGER_RESOURCE);
        } catch (IOException e) {
            logger.fatal("Unable to locate " + LOGGER_RESOURCE, e);
        }
Run Code Online (Sandbox Code Playgroud)

稍后在代码中,您将看到枚举资源的循环以创建记录器上下文工厂。

但是,当您运行 Scala 类时enumResources.hasMoreElements()返回true,而当您运行 java 类时它返回false(因此没有记录器上下文,也没有记录器添加到 LogManager 中)。

如果你进一步研究,你会发现该cl变量实际上是一个类加载器,对于 Java 类来说,它是一个实例,sun.misc.Launcher$AppClassLoader而对于 Scala 类来说,它是一个实例sbt.classpath.ClasspathUtilities$$anon$1

如果您查看静态初始值设定项的开头,您将看到以下语句:

 static {
        // Shortcut binding to force a specific logging implementation.
        PropsUtil managerProps = new PropsUtil("log4j2.LogManager.properties");
        String factoryClass = managerProps.getStringProperty(FACTORY_PROPERTY_NAME);
        ClassLoader cl = findClassLoader(); 
Run Code Online (Sandbox Code Playgroud)

所以你可能想看看这个findClassLoader()方法:

private static ClassLoader findClassLoader() {
            ClassLoader cl;
            if (System.getSecurityManager() == null) {
                cl = Thread.currentThread().getContextClassLoader();
            } else {
                cl = java.security.AccessController.doPrivileged(
                    new java.security.PrivilegedAction<ClassLoader>() {
                        public ClassLoader run() {
                            return Thread.currentThread().getContextClassLoader();
                        }
                    }
                );
            }
            if (cl == null) {
                cl = LogManager.class.getClassLoader();
            }

            return cl;
        }
Run Code Online (Sandbox Code Playgroud)

在这两种情况下,由于 SecurityManager 不为 null,因此它返回当前的 Thread 上下文类加载器。Java 类和 Scala 类是不同的。