Thursday, May 21, 2015

JAVA - Separate log files for threads using log4j2

General

I've created this page to document how to create separate log file (in separate directory) using log4j2.
It contains a small example (1 Main thread and 2 test threads), how to and explanation how it works.

I hope you'll find it useful

This tutorial is based on the post at http://stackoverflow.com/questions/25114526/log4j2-how-to-write-logs-to-separate-files-for-each-user!

Assumptions

  1. You are familiar with JAVA
  2. You are familiar with log4j2 configuration
  3. You know how to link JARs (although I explain how to do it in IntelliJ)
Still, if you need help since something is unclear don't hesitate to contact me :)

Prerequisites 

  1. Download log4j2 from https://logging.apache.org/log4j/2.0/download.html (Current version 2.3) 
  2. Download  slf4j from http://www.slf4j.org/download.html (Current version 1.7.12)

How to:

you download the project https://github.com/tzachs/test-log4j2-threads or do the following yourself
  1. Start IntelliJ
  2. Create a new project named test-log4j2-threads
  3. Create a directory named lib
  4. Move the files apache-log4j-2.3-bin.tar.gz and slf4j-1.7.12.tar.gz to the lib directory
  5. Extract the files in the directory
  6. Create 3 classes
    1. Main.java
    2. TestThread.java
    3. MyLogger.java
  7. Create a file named log4j2.xml
  8. Copy the following to Main.java
  9. import org.apache.logging.log4j.ThreadContext;
    
    /**
     * Created by tzach on 5/16/15.
     */
    public class Main {
    
        public static void main(String[] args) {
            ThreadContext.put("logFilename","main");
            MyLogger.init();
    
    
            int i;
            MyLogger.getLogger().info("Started");
            TestThread testThread;
            testThread = new TestThread("test1");
            testThread.start();
            testThread = new TestThread("test2");
            testThread.start();
    
            for ( i = 0; i < 4; i++){
                try {
    
                    MyLogger.getLogger().debug("GUI log");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            MyLogger.getLogger().info("Finished");
        }
    }
    
  10. Copy the following code to TestThread.java
  11. import org.apache.logging.log4j.ThreadContext;
    
    /**
     * Created by tzach on 5/16/15.
     */
    public class TestThread extends Thread implements Runnable {
    
        private final String threadName;
    
        public TestThread(String threadName){
            this.threadName = threadName;
        }
    
        @Override
        public void run() {
            Thread.currentThread().setName(threadName);
            ThreadContext.put("logFilename",   Thread.currentThread().getName());
    
            MyLogger.getLogger().debug("Starting new loop");
            int i = 0;
            long threadId = Thread.currentThread().getId();
            for ( int z = 0; z < 3; z++){
                MyLogger.getLogger().warn("Thread number: " + threadId + " message number " + ++i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    MyLogger.getLogger().error(e.toString(),e);
                }
            }
        }
    }
    
  12. Copy the following code to TestThread.java
  13. import org.apache.logging.log4j.LogManager;
    import org.apache.logging.log4j.Logger;
    
    /**
     * Created by tzach on 5/16/15.
     */
    public class MyLogger {
    
        private static Logger logger;
    
        public static void init(){
            logger = LogManager.getLogger("test-log4j2-threads");
        }
    
        public static Logger getLogger(){
            return logger;
        }
    }
    
  14. Copy the following to log4j2.xml
  15. <?xml version="1.0" encoding="UTF-8"?>
    <Configuration status="WARN">
        <Appenders>
            <Console name="Console" target="SYSTEM_OUT">
                <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
            </Console>
    
            <Routing name="RoutingAppender">
                <Routes pattern="$${ctx:logFilename}">
                    <Route>
                        <RollingFile name="Rolling-${ctx:logFilename}" fileName="logs/${ctx:logFilename}/test.log"
                                     filePattern="logs/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
                            <PatternLayout pattern="%d{ABSOLUTE} %level{length=5} [%thread] %logger{1} - %msg%n"/>
                            <SizeBasedTriggeringPolicy size="512" />
                        </RollingFile>
                    </Route>
    
                    <!-- By having this set to ${ctx:logFileName} it will match when filename
                         is not set in the context -->
                    <Route ref="Console" key="${ctx:logFileName}"/>
                </Routes>
            </Routing>
        </Appenders>
        <Loggers>
            <Root level="trace">
                <AppenderRef ref="RoutingAppender"/>
            </Root>
        </Loggers>
    </Configuration>
    
  16. Link the libraries. In Intellij you'll do it by doing the following:
    1. Open the Project Structure (Setting). In Ubunutu you can press Ctrl+Alt+Shift+S
    2. Go to Modules
    3. Press on the Dependencies tab
    4. Click on the plus sign and add the JARs. You should see something like this in the end of the process.
  17. Click on build --> Make (Or Ctrl+F9)
  18. Press on the run (or Shift+F10)
  19. You should see a directory named logs  created with the following structure:
    1. logs
      1. main
        1. test.log
      2. test1
        1. test.log
      3. test2
        1. test.log
  20. Notice that each test.log has messages only from it's own thread
  21. That's it :)

Explanation

So what is actually going on?

As you can see, I've used Routing Appender which is named RoutingAppender in my example.
The pattern to of route is depended on the variable in each Thread context named logFilename.

Notice that I do not handle any thing related to the creating of the files or the directories, it is all done by the log4j2.

All you need to do, is to put a name of the directory you want be created for each Thread, for example, Line number 9 in class Main.java:
ThreadContext.put("logFilename","main")

By typing "main", I've decided that all the logs for the main thread will go to the directory logs/main

For more info see https://logging.apache.org/log4j/2.0/faq.html#separate_log_files

No comments:

Post a Comment