How to create separate Log4j2 rolling file appenders and loggers programatically for multiple threads - multithreading

I am running my TestNG tests on multiple threads (Appium tests on multiple devices simultaneously) and want to write the test logs on different threads in different files. Here the threads are created automatically before the start of the test flow.
So I want to create separate appender and separate logger programatically so that each appender would be attached to its own thread only and then the loggers created in one thread would have the appender created in that thread only.
Please let me know how to achieve it step by step.

First, this feels like an XY Problem especially because you have not provided any reasoning as to why you want to go with a programmatic solution.
It is possible to achieve separate log files on a thread by thread basis without programmatically creating loggers and appenders. Since I believe this to be a more optimal solution I will provide a demo of how it can be done.
Explanation
This solution will use the RoutingAppender and ThreadContext to create appenders for each Thread. The example that follows will use a simple FileAppender but you can swap in a RollingFileAppender very easily. Since you did not state that you have any requirement to use different log levels for each thread the following example will not implement this feature. Finally, the example will not use TestNG as it has some overhead to set up; instead a simple class with a main that creates two threads will be used.
Example Implementation
log4j2.xml file:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Routing name="MyRoutingAppender">
<Routes pattern="$${ctx:threadName}">
<Route>
<File
fileName="logs/${ctx:threadName}/log.txt"
name="appender-${ctx:threadName}">
<PatternLayout>
<Pattern>[%date{ISO8601}][%-5level][%t] %m%n</Pattern>
</PatternLayout>
</File>
</Route>
</Routes>
</Routing>
<Console name="STDOUT" target="SYSTEM_OUT">
<PatternLayout pattern="[%date{ISO8601}][%-5level][%t] %m%n" />
</Console>
</Appenders>
<Loggers>
<Logger name="Thread" level="TRACE" additivity="false">
<AppenderRef ref="STDOUT" />
<AppenderRef ref="MyRoutingAppender" />
</Logger>
<Root level="WARN">
<AppenderRef ref="STDOUT" />
</Root>
</Loggers>
</Configuration>
Java class that generates logs using 2 concurrent threads:
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.ThreadContext;
public class MultiThreadLog4j2SepFilesMain {
//Create a lock to use for synchronizing the getting of the logger
private static final Object lock = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable(){
public void run() {
//Set up the context before getting logger
ThreadContext.put("threadName", Thread.currentThread().getName());
//Get the logger for this thread
Logger log = null;
synchronized(lock){
log = LogManager.getLogger(Thread.currentThread().getName());
}
//Generate some logs
log.info("here's the first thread");
//Wait a while so that threads interleave
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//Generate more logs
log.debug("some debug in first thread");
log.info("finishing first thread");
}}, "Thread.1"); //Use a name that will allow us to use Thread.getName when getting the logger inside the thread
Thread t2 = new Thread(new Runnable(){
public void run() {
//Set up the context before getting logger
ThreadContext.put("threadName", Thread.currentThread().getName());
//Get logger for this thread
Logger log = null;
synchronized(lock){
log = LogManager.getLogger(Thread.currentThread().getName());
}
//Generate some logs
log.info("here's the second thread");
log.debug("some debug in second thread");
}}, "Thread.2"); //Use a name that will allow us to use Thread.getName when getting the logger inside the thread
//Start both threads
t1.start();
t2.start();
}
}

Related

Rotating GC Log files with qos Logback

I am trying to rotate my gc.log file every time my application starts up.
I am using this file appender in my logback.xml file.
...
<appender name="GCFILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.directory}/gc.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.directory}/gc.log.%d{yyyyMMdd}_%d{HHmmss,aux}.gz</fileNamePattern>
<TimeBasedFileNamingAndTriggeringPolicy class="com.ga.omni.utility.StartupTriggeringPolicy" />
<maxHistory>50</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
...
<root level="INFO">
<appender-ref ref="FILE" />
<appender-ref ref="GCFILE" />
</root>
(the "FILE" ref is a reference to our default logging file for the app.)
The appender references a TimeBasedFileNamingAndTriggeringPolicy named StartupTriggeringPolicy:
#NoAutoStart //won't be autostarted by Joran at config time
public class StartupTriggeringPolicy<E> extends DefaultTimeBasedFileNamingAndTriggeringPolicy<E> {
Logger log = LoggerFactory.getLogger(StartupTriggeringPolicy.class);
public StartupTriggeringPolicy() {
log.info("StartupTriggeringPolicy constructor called");
}
#Override
public void start() {
log.info("StartupTriggeringPolicy start() called... initialting gc.log rollover");
super.start();
//only check this once, on startup.
nextCheck = 0L;
isTriggeringEvent(null, null);
try {
tbrp.rollover();
log.info("StartupTriggeringPolicy start() called... gc.log successfully rolled over.");
} catch (RolloverFailure e) {
log.warn("Error rolling over gc.log file in StartupTriggeringPolicy.start()");
//Do nothing
}
}
}
The trouble that I'm facing is that the app starts up, but the StartupTriggeringPolicy never seems to get instantiated. None of the logs from the constructor or start() method are written, and if I put breakpoints in those methods, the breakpoints don't get hit.
Any suggestions would be greatly apperciated!

Log4j create a file for each logger

I have a lot of selenium tests that create a logger per class, it might not be the best way but it's code written by somebody else and I dont have time to rewrite it. I would like each Test of have it's own logfile so that it's easier to see what went wrong.
Is there a way to have log4j create a file for each logger that is created?
Yes, you can do this with log4j1 but I believe the only way is through programmatically adding the file appender to the logger.
Here is some sample code:
package test;
import org.apache.log4j.Appender;
import org.apache.log4j.FileAppender;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;
public class Main {
private static final Logger logger = Logger.getLogger(Main.class);
private static final Logger logFoo = Logger.getLogger("test.Foo");
private static final Logger logBar = Logger.getLogger("test.Bar");
public static void main(String[] args) {
logger.addAppender(createFileAppender("logs/main.log"));
logFoo.addAppender(createFileAppender("logs/foo.log"));
logBar.addAppender(createFileAppender("logs/bar.log"));
logger.info("This is the main logger");
logFoo.info("this is the foo logger");
logBar.info("This is the bar logger");
}
private static Appender createFileAppender(String logName) {
FileAppender fa = new FileAppender();
fa.setName("FileLogger");
fa.setFile(logName);
fa.setLayout(new PatternLayout("%d %-5p [%c{1}] %m%n"));
fa.setThreshold(Level.DEBUG);
fa.setAppend(true);
fa.activateOptions();
return fa;
}
}
Here is a sample log4j.xml config file:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'>
<appender name="consoleAppender" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d %-5p [%c{1}] %m %n" />
</layout>
</appender>
<logger name="test" additivity="false">
<level value="DEBUG" />
<appender-ref ref="consoleAppender" />
</logger>
</log4j:configuration>
Note that you probably don't need to specify the console appender, I did it just to make sure things were working. You may not even need to specify any loggers, but I didn't test with that configuration.
The output of the above is 3 log files each containing the one message that was provided to the corresponding logger, and console output of all log messages.

Write Log4J to jTextArea

I'm an inexperienced Java developer writing an application that is handling a backup.
My application opens a gui (StepTwoBackup written using NetBeans Template), which gathers some information from the user, then when I press on the "Next" button, the gui passes these information to an object (BackupRestore which is logging all the operation using Log4J), and then opens another window (StepThreeBackup) and passes the object to it.
In this new window(StepThreeBackup), I automatically launch a method on the object passed (BackupRestore.execute()) which performs the backup.
In this last window (StepThreeBackup) I created a JTextArea where I would like to show the output of the Log4J (which currently writes to a log file and outputs to console).
Is there a way to do this? I've read that I should use an appender, but cannot figure out how to do this correctly.
For the time being I've created the following entry in my working Log4J property file:
<appender name="guiAppender" class="BackupAppGui.StatusMessageAppender">
<param name="Threshold" value="INFO" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{dd MMM yyyy HH:mm:ss} %5p %c{1} - %m%n"/>
</layout>
</appender>
Then the following Class in my Package (following another post):
package BackupAppGui;
/**
*
* #author MSTPA
*/
import javax.swing.JTextArea;
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.Level;
import org.apache.log4j.spi.LoggingEvent;
public class StatusMessageAppender extends AppenderSkeleton {
private final JTextArea jTextA;
public StatusMessageAppender() {
jTextA = StepThreeBackup.getJTextA();
}
protected void append(LoggingEvent event)
{
if(event.getLevel().equals(Level.INFO)){
jTextA.append(event.getMessage().toString());
}
}
public void close()
{
}
public boolean requiresLayout()
{
return false;
}
}
But nothing is written to the jTextArea.
What am I doing wrong? Can someone help me solving this? Thank you for all the help you can give me.
You need to make sure that the instance of JTextArea is not null. Yoy can try adding the appender programmatically (e.g. in the constructor of StepThreeBackup after create the components):
StatusMessageAppender appender = new StatusMessageAppender();
LogManager.getRootLogger().addAppender(appender);
Don't forget delete the entry in the log4j.xml file.

Spring equivalent of CompletionService?

In my app I have to process multiple jobs asynchronously from the main application thread and collect the result of each job. I have a plain Java solution that does this using a ExecutorService and a ExecutorCompletionService that collects the job results.
Now I would like to convert my code to a Spring solution. The docs show me how the use the ExecutorService and the #Async annotation, but I am not sure how and if I can collect the results of multiple jobs.
In other words: I am looking for the Spring equivalent of the CompletionService. Is there such a thing?
My current code:
class MyService {
private static ExecutorService executorService;
private static CompletionService<String> taskCompletionService;
// static init block
static {
executorService = Executors.newFixedThreadPool(4);
taskCompletionService = new ExecutorCompletionService<String>(executorService);
// Create thread that keeps looking for results
new Thread(new Runnable() {
#Override
public void run() {
while (true) {
try {
Future<String> future = taskCompletionService.take();
String s = future.get();
LOG.debug(s);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}).start();
}
// This method can and will be called multiple times,
// so multiple jobs are submitted to the completion service
public void solve(List<Long> ids) throws IOException, SolverException {
String data = createSolverData(ids);
taskCompletionService.submit(new SolverRunner(data, properties));
}
}
You need to consider what's your main goal, because your current code will work fine alongside other Spring-associated classes. Spring provides support for native Java ExecutorService as well as other popular 3rd party library such as Quartz
Probably what you're after is setting up the executor service on the spring container (eg: using following config on your spring beans xml)
<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="5" />
<property name="maxPoolSize" value="10" />
<property name="queueCapacity" value="25" />
</bean>
And decorate your MyService class with #Service annotation and inject the reference to the executor service
I ended up defining my beans in the Spring application context and injection the completionservice into MyService. Works as a charm.
<task:executor id="solverExecutorService" pool-size="5" queue-capacity="100" />
<spring:bean id="solverCompletionService" class="nl.marktmonitor.solver.service.SolverCompletionService" scope="singleton">
<constructor-arg name="executor" ref="solverExecutorService"/>
</spring:bean>

custom log4j layout class not being called

I've a custom log4j layout class that extends PatternLayout, my layout class simply masks the password in the log. It works in a simple console app. Here's the log4j.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="A1" class="org.apache.log4j.ConsoleAppender">
<layout class="com.PortalLog4jFilteringPattern"> <param name="ConversionPattern" value="%t %-5p %c{2} - %m%n"/> </layout>
</appender>
<root>
<priority value ="DEBUG" /> <appender-ref ref="A1" />
</root>
</log4j:configuration>
Here's a snipet of the layout class:
public class PortalLog4jFilteringPattern extends PatternLayout {
// omitted
#Override
public String format(LoggingEvent event) {
System.out.println("in format()...... ");
// rest omitted
Here's the calling code:
import org.apache.log4j.Logger;
public class ProductDemo {
private static Logger logger = Logger.getLogger(ProductDemo.class);
public ProductDemo() {
}
public void processOrder(CustomerOrder order) {
logger.info(order.getProductName());
}
// rest ommited
A sample result log with pswd being masked:
main INFO test.ProductDemo - "password":"*****"},
But once I moved the custom layout class to my webapp (log4j.xml is exactly the same.), it doesn't get called (i.e., no System.out output) and the pswd is still being shown. I'm running the webapp locally with maven on Jetty using this cmd: mvn jetty:run
Here's the calling code:
// original code, but I changed it to import org.apache.log4j.Logger for experiment
//import org.slf4j.LoggerFactory;
//import org.slf4j.Logger;
import org.apache.log4j.Logger;
public class BlahBlahClass extends Blah
// things omitted
private final static Logger log = Logger.getLogger( BlahBlahClass .class );
Any idea? thanks
In a Java EE server environment I would say: it's a class loader issue. Jetty is a servlet container, so it's class loading architecture is simpler; still, it's worth checking. If your log4j is not deployed within the WAR, but comes from the Jetty class path, this is almost certainly the cause.
Try changing the class loading strategy to "parent last", as described in the Jetty manual, see if it helps.
You should import package name of your class in log4j. I added to log4j2.xml as below:
<Configuration packages="package path of your class">

Resources