Java 带有 Log4j2 错误的 SLF4j 无法为元素 RollingFile 调用类类中的工厂方法...RollingFileAppender
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/36144276/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
SLF4j with Log4j2 ERROR Unable to invoke factory method in class class ...RollingFileAppender for element RollingFile
提问by James Affleck
I have some WebDriver tests running in parallel in TestNG. And I want to be able to have logging log to a separate file for each test that's run in a directory structure like this:
我在 TestNG 中并行运行了一些 WebDriver 测试。而且我希望能够将日志记录到一个单独的文件中,以便在这样的目录结构中运行的每个测试:
target\logs\TestNGSuiteName(SuiteStartTime)
Test1ClassName.TestMethod1 (TestStartTime).log
Test1ClassName.TestMethod2 (TestStartTime).log
etc.
等等。
Using Log4j and SLF4j is it possible to create a separate log file for each individual TestNG tests?
使用 Log4j 和 SLF4j 是否可以为每个单独的 TestNG 测试创建单独的日志文件?
I have attempted using a RollingFileAppender but it doesn't look like that is designed for having separate instances run for separate log files like I'm attempting to do here.
我曾尝试使用 RollingFileAppender,但它看起来不像是为单独的日志文件运行单独的实例而设计的,就像我在这里尝试做的那样。
And I'm getting the errors
我收到了错误
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
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>
LumberHyman.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 LumberHymanTest {
private static final Logger logger = LoggerFactory.getLogger(LumberHymanTest.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");
}
}
采纳答案by 99Sono
Log4j 2 seems to be pumped on steroids, if you get MDC logging for free already using the rolling file appender.
如果您已经使用滚动文件附加程序免费获得 MDC 日志记录,那么 Log4j 2 似乎被注入了类固醇。
In any case, your log4j snippet looks strange. We see the closing appender element tag, but not its corresponding opening appender tag.
无论如何,您的 log4j 片段看起来很奇怪。我们看到了结束 appender 元素标签,但没有看到它对应的开始 appender 标签。
Your rolling file appender name seems to have a space between the dynamic test name and test start time.
您的滚动文件附加程序名称似乎在动态测试名称和测试开始时间之间有一个空格。
fileName="target/logs/${ctx:suiteTimestamp}/${ctx:testName (${ctx:testStartTime}).log"
fileName="target/logs/${ctx:suiteTimestamp}/${ctx:testName (${ctx:testStartTime}).log"
Suggestion: How about you divide conquer.
建议:如何分治。
If such type of dynamic configuration is indeed supported. Why don't you try to configure first only the file name with a dynamic pattern?
如果确实支持这种类型的动态配置。为什么不首先尝试仅使用动态模式配置文件名?
You seem to be putting your log4j configuration on full steroids before you got the simplest possible configuration to work for your problem.
在您获得最简单的配置来解决您的问题之前,您似乎已经将您的 log4j 配置放在了完整的类固醇上。
So put a feet on the break and focus on getting the: fileName="target/logs/dummyTest_dynamicComponent_${ctx:testName}.log"
所以停下脚步,专注于获取:fileName="target/logs/dummyTest_dynamicComponent_${ctx:testName}.log"
To work for you.
为你工作。
In log4j 1.x version, you would have the log4j.debug system property to help you out figuring bugy configuration, and the output was very very useful.
在 log4j 1.x 版本中,您将拥有 log4j.debug 系统属性来帮助您找出错误配置,并且输出非常有用。
Finally, on log4j 1.X version the feature you want to use would require you to program explicitly an MDC appender of your own. Your MDC appender would typically instantiate RollingFileAppenders to log into files and you would tap into the MDC context (keyxValue) pairs put by the user.
最后,在 log4j 1.X 版本上,您要使用的功能将要求您明确编程您自己的 MDC 附加程序。您的 MDC appender 通常会实例化 RollingFileAppenders 以登录到文件,您将利用用户放置的 MDC 上下文(keyxValue)对。
But what you're doing looks promising, just reduce the level of complexity of your configuration if it is not working for you.
但是您正在做的事情看起来很有希望,如果它不适合您,请降低配置的复杂程度。
Finally, I would be very surprised if you would see any log file geting created when you have the following error:
最后,如果您在出现以下错误时看到任何日志文件被创建,我会感到非常惊讶:
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.
错误无法为元素 RollingFile 调用类 org.apache.logging.log4j.core.appender.RollingFileAppender 中的工厂方法。无法创建 RollingFile 类型的 Appender。
Log4j is telling you: Hey, that appender you are defining. My factory that is trying to swallog this configuration cannot handle it and I will not instantiate a rolling file appender with this configuration.
Log4j 告诉您:嘿,您正在定义的 appender。我的工厂尝试使用此配置无法处理它,我不会使用此配置实例化滚动文件附加程序。
So you have to fix that configuration.
所以你必须修复那个配置。
Adding to answer.
补充回答。
Here you have a working Log4j 2 configuration for doing what you want:
在这里你有一个可用的 Log4j 2 配置来做你想做的事:
First snippet if the log4j 2 configuration, where you will see that the root logger is given 3 different appenders to play around. You care mostly about appender 3, but the other two appenders are more of your typical starting point.
如果是 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>
This last appender is configured based on the following thread: https://issues.apache.org/jira/browse/LOG4J2-129
最后一个 appender 基于以下线程配置:https: //issues.apache.org/jira/browse/LOG4J2-129
The second snippet is a dummy junit test that you get out of eclipse when you create a new maven project out of a basic archetype. You will see in the test snippet that the stack over flow context context is being set in to the thread context, like you do in your snippets.
第二个片段是一个虚拟的 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);
}
}
The last snippet are the maven dependencies:
最后一个片段是 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>
I find funy that log4j brings in this new Routing Appender. If you could imagine how many people have had to implement their own rolling file appenders with MDC context support to do this type of stuff. It is very useful in web apps.
我觉得 log4j 引入这个新的 Routing Appender 很有趣。如果你能想象有多少人不得不使用 MDC 上下文支持来实现他们自己的滚动文件附加器来做这种类型的事情。它在网络应用程序中非常有用。
Cheers.
干杯。