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:
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?
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.
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
<?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");
}
}
Log4j 2 seems to be pumped on steroids, if you get MDC logging for free already using the rolling file appender.
In any case, your log4j snippet looks strange. We see the closing appender element tag, but not its corresponding opening appender tag.
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"
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.
So put a feet on the break and focus on getting the: 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.
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.
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:
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.
So you have to fix that configuration.
Adding to answer.
Here you have a working Log4j 2 configuration for doing what you want:
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.
This last appender is configured based on the following thread: 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.
The last snippet are the maven dependencies:
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.
Cheers.