【问题标题】:SLF4j with Log4j2 ERROR Unable to invoke factory method in class class ...RollingFileAppender for element RollingFileSLF4j with Log4j2 ERROR Unable to invoke factory method in class class ...RollingFileAppender for element RollingFile
【发布时间】:2016-03-22 00:41:46
【问题描述】:

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

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

【问题讨论】:

  • 您有带有错误消息的堆栈跟踪吗?目前尚不清楚为什么会附加错误。
  • 嗨 Julien,我刚刚再次运行代码,没有堆栈跟踪。日志文件已正确创建,但没有写入任何内容。我不确定我在这里做错了哪一部分,但我认为这可能与这一行有关:LoggerFactory.getLogger(LumberjackTest.class);或者可能是 RollingFile appender 不是执行此操作的正确方法。我想不出通过使用 File Appender 传递如下变量来获取日志文件名称的方法。
  • @JulienHerr fileName="target/logs/${ctx:suiteTimestamp}/${ctx:testName(${ctx:testStartTime}).log"
  • 也许我需要使用configuration factory 处理某些事情,或者我必须使用loggerConfiguration / Logmanager 做一些事情。文档有点难以理解,感觉就像从消防水管中喝水一样。如果有一种简单的方法可以使用 LogBack 而不是 Log4j 或者不使用 SLF4j 外观。我也对这些选项持开放态度。
  • stackoverflow.com/q/25148492/3255152。您需要将其调整为测试而不是类,但这应该不会太难。您可以使用 TestListener 来帮助管理记录器的创建等。

标签: java testng slf4j log4j2


【解决方案1】:

如果您已经使用滚动文件附加程序免费获得 MDC 日志记录,Log4j 2 似乎被打了个激灵。

无论如何,您的 log4j sn-p 看起来很奇怪。我们看到了结束 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 附加程序通常会实例化 RollingFileAppenders 以登录文件,然后您将利用用户放置的 MDC 上下文 (keyxValue) 对。

但是您所做的看起来很有希望,如果它不适合您,只需降低配置的复杂程度即可。

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

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

Log4j 告诉你: 嘿,你正在定义的那个 appender。我试图吞下这个配置的工厂无法处理它,我不会用这个配置实例化一个滚动文件附加程序。

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


添加到答案。

这里你有一个工作的 Log4j 2 配置来做你想做的事:

如果是 log4j 2 配置,则首先 sn-p,您将看到根记录器有 3 个不同的附加程序可供使用。 您主要关心 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

第二个 sn-p 是一个虚拟的 junit 测试,当您从基本原型创建一个新的 maven 项目时,您可以从 eclipse 中得到它。您将在测试 sn-p 中看到堆栈溢出上下文上下文被设置到线程上下文中,就像您在 sn-ps 中所做的那样。

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

最后一个sn-p是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 引入了这个新的 Routing Appender 很有趣。如果你能想象有多少人不得不实现他们自己的带有 MDC 上下文支持的滚动文件附加器来做这类事情。 它在网络应用中非常有用。

干杯。

【讨论】:

  • 感谢您与我们联系。用你的评论'Log4j 告诉你:嘿,你正在定义的那个 appender。我试图吞下此配置的工厂无法处理它,我不会使用此配置实例化滚动文件附加程序。这有点帮助。我还尝试创建一个更简单的文件(仅使用单个变量作为文件名,filename="c:\users\me\logging-setup/${ctx:methodName}.log" 但这并没有取得更多成功。
  • 我尝试将配置状态设置为跟踪(我在文档中的某处看到),这让我可以在 Log4J2 上进行更多登录,但遗憾的是没有提供有关此特定错误的更多具体信息。 :-/
  • 你给了我一个想法。我发现 log4j1.2 文档看起来不那么复杂,我认为阅读这将帮助我从更基本的层面理解 log4j 的架构以及所有部分需要如何组合在一起(如配置、附加程序、过滤器等) .感谢您的想法!
  • 附录是一个可重用的实现类,其目的是将日志事件泵入输出流。可以文件、套接字、数据库等。取决于您使用的附加程序技术。记录器就像您电子邮件上的谷歌标签。他们对日志事件进行分类,并为您提供对天气的细粒度控制,您是否应该记录该类别的内容。通常将附加程序绑定到根类别而不是特定类别。您正在使用细粒度配置,当您声明类别“james.log”时,您希望 2 使用您的附加程序。通常你会在根记录器/类别上设置它。
  • P.S.我喜欢LALAL LLA
猜你喜欢
  • 2015-06-14
  • 1970-01-01
  • 1970-01-01
  • 2017-07-26
  • 2021-07-13
  • 2016-12-08
  • 2022-10-06
  • 2020-10-11
  • 1970-01-01
相关资源
最近更新 更多