2

I am trying to create multiple static loggers using Threadlocal, so that each static threadlocal will then log to seperate files with different messages. Below is my sample code:

public class LoggerTest implements Runnable
{
    private static ThreadLocal<Logger> log=new ThreadLocal<Logger>();
    FileHandler fh;
    String str;
    public LoggerTest(int counter,String instanceName) throws SecurityException, IOException
    {
        str=instanceName;
        log.set(Logger.getLogger("Logging"+counter));
        log.get().info("m");
        fh=new FileHandler(instanceName+".log");
        fh.setFormatter(new SimpleFormatter());
        log.get().addHandler(fh);
    }
    public static void main(String[] args) throws SecurityException, IOException
    {
        Thread t1=new Thread(new LoggerTest(1, "file"+1),"Thread1");
        Thread t2=new Thread(new LoggerTest(2, "file"+2),"Thread2");
        Thread t3=new Thread(new LoggerTest(3, "file"+3),"Thread3");
        t1.start();t2.start();t3.start();
    }

    @Override
    public void run()
    {
        for (int i = 0; i < 10; i++)
        {
            log.get().info("Message"+i);
            try
            {
                Thread.sleep(5000);
            } catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }
    }
}

According to this every thread will have its own static ThreadLocal variables. So i've created 3 threads to log in different files by using ThreadLocal. Can someone point where am i going wrong in understanding the concept of static ThreadLocal's

My objective: To achieve multiple loggers using static ThreadLocal's TIA!!!

Community
  • 1
  • 1
Mrunal Gosar
  • 4,595
  • 13
  • 48
  • 71
  • Why would you want to log via ThreadLocal if there are so many tested Logging frameworks that work just beautifully [slf4j, log4j, java.util.Log, ...]? – jgroehl Jan 27 '15 at 06:34
  • @jgroehl i had gone through the slf4j, log4j, apache commons logging to. but nothing in that provides capability to log. thing. I have jobs which are run by independent threads. i want to log each activity of that job into seperate files. – Mrunal Gosar Jan 27 '15 at 07:26
  • in log4j you can create appenders which work class specific. I've done that myself in a huge TomEE WebApp-Project. In the log4j.xml you can define appenders for certain packages or for certain classes. We created seperate log files for our database / application context / view layers of the project. Is this what you were looking for? – jgroehl Jan 27 '15 at 09:23
  • @jgroehl no no. that is not the intend. I've a class which has implmented Callable interface. That class acts as a Job. Each job can have different inputs and can produce different output. Each job is run by a different thread for performance optimization. So in this context just imagine if i will log everything into same logger in same file, i will go crazy which is why i wanted multiple loggers(i.e equal to number of jobs) and for each job i will have a separate log file. But anyways i've found out the issue, will soon post where i was going wrong. – Mrunal Gosar Jan 27 '15 at 09:54

3 Answers3

3

This is because you need to override the initialValue() method for the ThreadLocal class. E.g.

/** Thread local logger. */
private static final ThreadLocal<Logger> LOGGER = new ThreadLocal<Logger> () {

    @Override
    protected Logger initialValue() {
        return LoggerFactory.getLogger(ThisClass.class);
    }
};
Alex Suo
  • 2,977
  • 1
  • 14
  • 22
  • 1
    or you could simply not use a static ThreadLocal but use a non static private Logger, that you initialize in the constructor! – jgroehl Jan 27 '15 at 06:39
  • @jgroehl `ThreadLocal` instance guarantees thread uniqueness only and is essentially a Thread ID to instance map. That's very different from a logger per instance. In case this is a data object which got created a lot in the program, the difference of using `ThreadLocal` and instance class member is huge. – Alex Suo Jan 27 '15 at 06:55
  • @AlexSuo I tried using initialValue, but the problem is that it is creating only one logger, whereas i want to create multiple seperate loggers which will log all the activity that a running thread does. – Mrunal Gosar Jan 27 '15 at 07:06
  • @MrunalGosar The `ThreadLocal` would give you 1 instance per thread. If you need mutliple instances of loggers per thread you'd need to create multiple `ThreadLocal` instances. But having said that a lot of logging framework supports multithreading nicely; also note the common practice to have 1 uniform logger per class to tell you where the log is from; I am not sure why you need such a scenario. – Alex Suo Jan 27 '15 at 07:09
  • @AlexSuo "If you need mutliple instances of loggers per thread you'd need to create multiple ThreadLocal instances." I thought when we will create multiple threads then each thread will create its own static threadlocal instance. If i am wrong then how can i create multiple static threadlocal instances? i cannot have uniform logger as i want to assign one logger per job so that it runs independent and i can log all the activities in each job in seperate log files – Mrunal Gosar Jan 27 '15 at 08:18
  • @AlexSuo Also i tried using initialValue too which you've suggested. But it so happens that all logging is happening to all the log files, whereas i want to log per job per log file. – Mrunal Gosar Jan 27 '15 at 09:00
  • @AlexSuo in this certain example there is single instance for every thread. So for this case - using ThreadLocal is equivalent to using non static property. Imho - in most cases you need different logs for different instances, not threads. – porfirion Dec 26 '19 at 15:15
1

Do not use a static variable if you want the field to be unique in every instance!

public class LoggerTest implements Runnable
{
private final Logger log;
(...)

public LoggerTest(int counter, String instanceName) throws SecurityException, IOException
{
    log = Logger.getLogger("Logging " + counter);
    fh = new FileHandler(instanceName + ".log");
    fh.setFormatter(new SimpleFormatter());
    log.addHandler(fh);
}
(...)

@Override
public void run()
{
    (...)
    log.info("Message" + i);
}
}

Instead you could also check with the log4j framework capabilities:

http://logging.apache.org/log4j/1.2/manual.html

How to create different log files for different packages using same log4j logger?

Log4J loggers for different classes

log4j: Log output of a specific class to a specific appender

Community
  • 1
  • 1
jgroehl
  • 134
  • 6
  • aren't threadlocal used for the same purpose so that we can have static instances per thread? – Mrunal Gosar Jan 27 '15 at 08:19
  • @MrunalGosar I just read into the uses of the `ThreadLocal` class. For huge projects with several Threads creating numerous classes all using the same logger it would be much more performant using the `ThreadLocal` class. For the given example code, however, this is just overkill and the non static field is a better option. It all depends on the context of usage :-) if you have only one instance that shall log into each log file use this option! – jgroehl Jan 27 '15 at 09:18
0

I've found where i was going wrong. Below is the working solution for that.

public class LoggerTest implements Runnable
{
    static int inc=0;
    private static ThreadLocal<Logger> log=new ThreadLocal<Logger>();
    FileHandler fh;
    int str;
    public LoggerTest(int counter,String instanceName) throws SecurityException, IOException
    {
        str=counter;
        fh=new FileHandler(instanceName+".log");
        fh.setFormatter(new SimpleFormatter());
    }
    public static void main(String[] args) throws SecurityException, IOException
    {
        Thread t1=new Thread(new LoggerTest(1, "file"+1),"Thread1");
        Thread t2=new Thread(new LoggerTest(2, "file"+2),"Thread2");
        Thread t3=new Thread(new LoggerTest(3, "file"+3),"Thread3");
        Thread t4=new Thread(new LoggerTest(4, "file"+4),"Thread4");
        t1.start();t2.start();t3.start();t4.start();
    }
    void meth()
    {
        log.set(Logger.getLogger("Logging"+str));
        log.get().addHandler(fh);
        for (int i = 0; i < 5; i++)
        {
            log.get().info(log.get().getName()+" Message"+i+" "+RandomStringUtils.random(str));
            try
            {
                Thread.sleep(5000);
            } catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }
    }
    @Override
    public void run()
    {
        this.meth();
    }
}

The reason is i was trying to set ThreadLocal in constructor. And main thread initializes the log once and then it is getting overriden by null. So the actual Thread gets created when we trigger Thread.start(). And we have to set the ThreadLocal after thread has got kicked started. I hope this helps.

Mrunal Gosar
  • 4,595
  • 13
  • 48
  • 71