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

时间:2016-03-22 00:41:46

标签: java testng slf4j log4j2

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

target\logs\TestNGSuiteName(SuiteStartTime)
    Test1ClassName.TestMethod1 (TestStartTime).log
    Test1ClassName.TestMethod2 (TestStartTime).log

等。

使用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.

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>

LumberJack.java  

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");
    }
}

1 个答案:

答案 0 :(得分:4)

如果你已经使用滚动文件追加器免费获得MDC日志记录,那么Log4j 2似乎会被类固醇激活。

无论如何,你的log4j片段看起来很奇怪。我们看到了关闭的appender元素标记,但没有看到它对应的开放appender标记。

您的滚动文件追加者名称似乎在动态测试名称和测试开始时间之间有空格。

fileName =&#34; target / logs / $ {ctx:suiteTimestamp} / $ {ctx:testName($ {ctx:testStartTime})。log&#34;

建议: 你如何划分征服。

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

在您获得最简单的配置以解决问题之前,您似乎正在将log4j配置置于完全类固醇上。

所以放下休息时间并专注于获得:   文件名=&#34;目标/日志/ dummyTest_dynamicComponent _ $ {CTX:测试名}的.log&#34;

为你工作。

在log4j 1.x版本中,你可以使用log4j.debug系统属性来帮助你搞清楚bugy配置,输出非常有用。

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

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

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

  

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

Log4j告诉你: 嘿,你正在定义的那个追加者。我的工厂尝试swallog这个配置无法处理它,我不会用这种配置实例化一个滚动文件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>

最后一个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);
    }
}

最后一个片段是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>

我发现log4j引入了这个新的路由选择器。如果你能想象有多少人必须用MDC上下文支持来实现他们自己的滚动文件appender来做这种类型的东西。 它在网络应用程序中非常有用。

干杯。