带有 Log4j2 错误的 SLF4j 无法为元素 RollingFile 调用类类中的工厂方法...RollingFileAppender

Jam*_*eck 6 java testng slf4j log4j2

我有一些在 TestNG 中并行运行的 WebDriver 测试。而且我希望能够将日志记录到一个单独的文件中,以便在这样的目录结构中运行的每个测试:

target\logs\TestNGSuiteName(SuiteStartTime)
    Test1ClassName.TestMethod1 (TestStartTime).log
    Test1ClassName.TestMethod2 (TestStartTime).log
Run Code Online (Sandbox Code Playgroud)

等等。

使用 Log4j 和 SLF4j 是否可以为每个单独的 TestNG 测试创建单独的日志文件?

我曾尝试使用 RollingFileAppender,但它看起来不像是为像我在这里尝试做的那样为单独的日志文件运行单独的实例而设计的。

我收到了错误

ERROR Unable to invoke factory method in class class org.apache.logging.log4j.core.appender.RollingFileAppender for element RollingFile.
Unable to create Appender of type RollingFile.
Run Code Online (Sandbox Code Playgroud)

Log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
        <Routing name="Routing">
            <Routes pattern="$${ctx:ROUTINGKEY}">
                <Route>
                    <RollingFile name="Rolling-${ctx:ROUTINGKEY}"
                                 fileName="target/logs/${ctx:suiteTimestamp}/${ctx:testName} (${ctx:testStartTime}).log"
                                 filePattern="target/logs/${ctx:testname} ${ctx:testStartTime}_%i.log.gz">
                        <PatternLayout>
                            <pattern>%d{HH:mm:ss.SSS} [%t] %p %c{3} - %m%n</pattern>
                        </PatternLayout>
                        <Policies> <!-- 6 hour rollover-->
                            <TimeBasedTriggeringPolicy interval="6" modulate="true"/>
                            <SizeBasedTriggeringPolicy size="10 MB"/>
                        </Policies>
                    </RollingFile>
                </Route>
            </Routes>
        </Routing>
    </Appenders>
    <Loggers>
        <Logger name="james.log" level="debug" additivity="false">
            <AppenderRef ref="Routing"/>
        </Logger>
    </Loggers>
</Configuration>
Run Code Online (Sandbox Code Playgroud)

伐木工人

package james.log;

import james.util.ConcurrentDateFormatAccess;
import org.apache.logging.log4j.ThreadContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.ITestContext;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.AfterSuite;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import java.lang.reflect.Method;

/**
 * @author james.affleck
 */
public class LumberjackTest {
    private static final Logger logger = LoggerFactory.getLogger(LumberjackTest.class);
    private static ThreadLocal<String> methodLogName = new ThreadLocal<>();
    private static String suiteName = "";

    @BeforeMethod
    public void loggerDoTheThings(ITestContext context, Method method) {
        if(suiteName.isEmpty()) {
            String suite = context.getSuite().getName() + "(";
            String suiteTime = new ConcurrentDateFormatAccess().getCurrentDateSPrecision();
            suite += suiteTime + ")";
            suiteName = suite;
        }

        // Test filename = testClass.testMethodname
        String classname = this.getClass().getName();
        classname = classname.substring(classname.lastIndexOf(".") + 1); //get rid of package info we don't care about
        String testName = classname + "." + method.getName();

        // Using this to store logger instance for later
        String testStart = new ConcurrentDateFormatAccess().getCurrentDateMSPrecision();
        methodLogName.set(testName + testStart);

        ThreadContext.put("suiteTimestamp", suiteName);
        ThreadContext.put("testName", testName);
        ThreadContext.put("testStartTime", testStart);
    }

    @AfterMethod
    public void closeTheThings() {
        methodLogName.set(null);
    }
    @AfterSuite
    public void closeSuite() {
        suiteName = null;
    }

    @Test
    public void testLog1() {
        logThings();
    }

    @Test
    public void testLog2() {
        logThings();
    }

    public void logThings() {
        logger.info("info message");
        logger.debug("debug message");
        logger.warn("warn message");
    }
}
Run Code Online (Sandbox Code Playgroud)

99S*_*ono 5

如果您已经使用滚动文件附加程序免费获得 MDC 日志记录,那么 Log4j 2 似乎被注入了类固醇。

无论如何,您的 log4j 片段看起来很奇怪。我们看到了结束 appender 元素标签,但没有看到它对应的开始 appender 标签。

您的滚动文件附加程序名称似乎在动态测试名称和测试开始时间之间有一个空格。

fileName="target/logs/${ctx:suiteTimestamp}/${ctx:testName (${ctx:testStartTime}).log"

建议:如何分治。

如果确实支持这种类型的动态配置。为什么不首先尝试仅使用动态模式配置文件名?

在您获得最简单的配置来解决您的问题之前,您似乎已经将您的 log4j 配置放在了完整的类固醇上。

所以停下脚步,专注于获取:fileName="target/logs/dummyTest_dynamicComponent_${ctx:testName}.log"

为你工作。

在 log4j 1.x 版本中,您将拥有 log4j.debug 系统属性来帮助您找出错误配置,并且输出非常有用。

最后,在 log4j 1.X 版本上,您要使用的功能将要求您明确编程您自己的 MDC 附加程序。您的 MDC appender 通常会实例化 RollingFileAppenders 以登录到文件,您将利用用户放置的 MDC 上下文(keyxValue)对。

但是您正在做的事情看起来很有希望,如果它不适合您,请降低配置的复杂程度。

最后,如果您在出现以下错误时看到任何日志文件被创建,我会感到非常惊讶:

错误无法为元素 RollingFile 调用类 org.apache.logging.log4j.core.appender.RollingFileAppender 中的工厂方法。无法创建 RollingFile 类型的 Appender。

Log4j 告诉您:嘿,您正在定义的 appender。我的工厂尝试使用此配置无法处理它,我不会使用此配置实例化滚动文件附加程序。

所以你必须修复那个配置。


补充回答。

在这里你有一个可用的 Log4j 2 配置来做你想做的事:

如果是 log4j 2 配置的第一个片段,您将在其中看到根记录器有 3 个不同的 appender 可供使用。您主要关心 appender 3,但其他两个 appender 更像是您的典型起点。

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        <!-- APPENDER 1: CONSOLE -->
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
        </Console>

        <!-- APPENDER 2: ROLLING FILE -->
        <RollingFile name="AppenderTwo" fileName="target/logs/test.log" filePattern="logs/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
            <PatternLayout>
                <Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
            </PatternLayout>
            <Policies>
                <TimeBasedTriggeringPolicy />
                <SizeBasedTriggeringPolicy size="10 MB" />
            </Policies>
        </RollingFile>

        <!-- APPENDER 3: ROUTING APPENDER -->
        <Routing name="AppenderThree">
            <Routes pattern="${ctx:stackOverFlow}">
                <!-- Route Nr.1 -->
                <Route>
                    <!-- Rolling file appender for route Nr.1 -->
                    <RollingFile name="NestedAppender-${ctx:stackOverFlow}" fileName="target/logs/test_threadContext_${ctx:stackOverFlow}.log"
                        filePattern="logs/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
                        <PatternLayout>
                            <Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
                        </PatternLayout>
                        <Policies>
                            <TimeBasedTriggeringPolicy />
                            <SizeBasedTriggeringPolicy size="10 MB" />
                        </Policies>
                    </RollingFile>
                </Route>

                <!-- Route Nr.2 fallback -->
                <!-- By having this set to ${ctx:filename} it will match when filename is not set in the context -->
                <Route ref="Console" key="${ctx:stackOverFlow}" />
            </Routes>
        </Routing>


    </Appenders>
    <Loggers>
        <Root level="all">
            <AppenderRef ref="Console" />
            <AppenderRef ref="AppenderTwo" />
            <AppenderRef ref="AppenderThree" />
        </Root>

    </Loggers>
</Configuration>
Run Code Online (Sandbox Code Playgroud)

最后一个 appender 基于以下线程配置:https : //issues.apache.org/jira/browse/LOG4J2-129

第二个片段是一个虚拟的 junit 测试,当您根据基本原型创建一个新的 maven 项目时,您将从 Eclipse 中退出。您将在测试代码段中看到,流上下文上下文的堆栈正在设置到线程上下文中,就像您在代码段中所做的那样。

package stackoverflow.test.tutorial;

import org.apache.logging.log4j.ThreadContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

/**
 * Unit test for simple App.
 */
public class AppTest extends TestCase {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestCase.class);

    /**
     * Create the test case
     *
     * @param testName
     *            name of the test case
     */
    public AppTest(String testName) {
        super(testName);
    }

    /**
     * @return the suite of tests being tested
     */
    public static Test suite() {
        return new TestSuite(AppTest.class);
    }

    /**
     * Rigourous Test :-)
     */
    public void testApp() {
        ThreadContext.put("stackOverFlow", "dummyContextValue");
        LOGGER.info("LALAL LLA");
        assertTrue(true);
    }
}
Run Code Online (Sandbox Code Playgroud)

最后一个片段是 maven 依赖项:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>stackoverflow.test</groupId>
  <artifactId>tutorial</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>tutorial</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
   <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.12</version>    
  </dependency>  
    <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.5</version>
    <scope>runtime</scope>
  </dependency>
  <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.5</version>
    <scope>runtime</scope>
  </dependency>
  <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>2.5</version>
</dependency>

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>
Run Code Online (Sandbox Code Playgroud)

我觉得 log4j 引入这个新的 Routing Appender 很有趣。如果你能想象有多少人不得不使用 MDC 上下文支持来实现他们自己的滚动文件附加器来做这种类型的事情。它在网络应用程序中非常有用。

干杯。

  • 感谢您伸出援手。使用您的评论'Log4j 告诉您:嘿,您正在定义的附加程序。我的工厂尝试使用此配置无法处理它,我不会使用此配置实例化滚动文件附加程序。这有点帮助。我还尝试为它创建一个更简单的文件(仅使用单个变量作为文件名,filename="c:\users\me\logging-setup/${ctx:methodName}.log" 但这并没有取得更大的成功。 (2认同)
  • PS我喜欢“LALAL LLA” (2认同)