log4j多線程以及分文件輸出日志


前段時間項目里因為多線程的問題對log4j進行了一下學習,今天有空匯總一下。

需求是有一個多線程程序,需要對每個線程的日志單獨按級別存儲。如下圖所示。

 

 項目代碼不方便發出,簡單寫一個demo。

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.ThreadContext;

public class TestLog {
    private static Logger log = LogManager.getLogger(TestLog.class);
    public static void main(String[] args) {

        Thread[] threads = new Thread[10];
        for(int i=0;i<10;i++){
            threads[i] = new TestThread("-"+i);
        }
        for(Thread thread : threads) {
            thread.start();
        }
    }
}
TestLog
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.ThreadContext;

class TestThread extends Thread {

    private static Logger log = LogManager.getLogger(TestLog.class);
    public TestThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName());
            log.info(Thread.currentThread().getName());
            log.error("error:"+Thread.currentThread().getName());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
TestThread

一、log4j

1.每個線程中輸出一份文件(此方法不通,留痕)

  方法一是可以通過filter進行過濾。

  但是考慮到需要對不同線程的日志按級別進行輸出,還需要進一步改造。

  因此考慮,首先在log4j.properties中對日志進行過濾,,將不同級別的日志輸出到不同文件。可參考https://blog.csdn.net/liuxiao723846/article/details/69295428  

log4j.rootLogger=info,stdout,infolog,errorlog
 
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=[%d{MM-dd HH:mm:ss}] [%p] [%c:%L] %m%n
 
log4j.appender.infolog = org.apache.log4j.DailyRollingFileAppender
log4j.appender.infolog.Threshold = INFO
log4j.appender.infolog.File = /data/logs/logtest/test.log
log4j.appender.infolog.layout = org.apache.log4j.PatternLayout
log4j.appender.infolog.layout.ConversionPattern = [%d{MM-dd HH:mm:ss}] [%p] [%c:%L] %m%n
log4j.appender.infolog.filter.infoFilter = org.apache.log4j.varia.LevelRangeFilter
log4j.appender.infolog.filter.infoFilter.LevelMin = INFO
log4j.appender.infolog.filter.infoFilter.LevelMax = INFO
 
log4j.appender.warnlog = org.apache.log4j.DailyRollingFileAppender
log4j.appender.warnlog.Threshold = WARN
log4j.appender.warnlog.File = /data/logs/logtest/test_warn.log
log4j.appender.warnlog.layout = org.apache.log4j.PatternLayout
log4j.appender.warnlog.layout.ConversionPattern = [%d{MM-dd HH:mm:ss}] [%p] [%c:%L] %m%n
log4j.appender.warnlog.filter.warnFilter = org.apache.log4j.varia.LevelRangeFilter
log4j.appender.warnlog.filter.warnFilter.LevelMin = WARN
log4j.appender.warnlog.filter.warnFilter.LevelMax=WARN
log4j.properties

  然后,在每個線程中對輸出的日志名稱進行重命名。對TestThread類進行改造

class TestThread extends Thread {

    private static final Logger log = Loggger.getLogger(TestLog.class);
    public TestThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        try {
FileAppender appender = (FileAppender) log.getRootLogger().getAppender("errorlog");
            appender.setFile("C:/Test/SystemOutError"+Thread.currentThread().getName()+".log");
            appender.activateOptions();
            FileAppender appender = (FileAppender) log.getRootLogger().getAppender("infolog");
            appender.setFile("C:/Test/SystemOut"+Thread.currentThread().getName()+".log");
            appender.activateOptions();

            System.out.println(Thread.currentThread().getName());
            log.info(Thread.currentThread().getName());
            log.error("error:"+Thread.currentThread().getName());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
TestThread

  但是,該方法能夠按照線程生成不同的文件,並且文件分為不同的級別,例如下圖。但是打開后發現error的內容都會存儲在thread1的文件中,thread2中為空文件。

  搜索了一下是因為log4j是單例模式,參考https://blog.csdn.net/guan0005/article/details/84139142,摘錄其中一段。 

  但log4j中有個陷阱:單例模式。在log4j中,配置的每個Logger,都只有一個實例。即在不同線程中,取到的同名Logger,全都是同一個實例。那么,之前的思路就    行不通了。除非為每個線程配置一個特定的Logger,或者配置一定數量的Logger,組成一個Logger池,各線程爭用Logger池中的Logger實例。但這兩種方法要么不夠靈活,要么邏輯復雜,並不是我想要的方案。

分析:
既然問題出在單例模式上,那我們只要在創建子線程的時候,為線程創建一個私有的Logger實例,不就行了嗎。遺憾的是,log4j並沒有提供創建全新Logger實例的接口給我們使用,Logger對象只能通過Logger.getLogger(*)方法獲取,而不能new一個Logger對象。怎么辦?只能重寫Logger類,或創建Logger的子類了。

   上篇文章中給出了創建子類的方式,后續給出。

2.threadlocal

待補充。

二、log4j2

重點參考這篇文章https://my.oschina.net/u/2300159/blog/887687

導入兩個包

<dependencies>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.12.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.12.0</version>
        </dependency>
    </dependencies>
pom.xml

配置log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="OFF">
    <Appenders>
        <Routing name="Routing">
            <Routes pattern="$${ctx:ROUTINGKEY}">
                <!-- This route is chosen if ThreadContext has a value for ROUTINGKEY
                     (other than the value 'special' which had its own route above).
                     The value dynamically determines the name of the log file. -->
                <Route>
                    <RollingFile name="Rolling-${ctx:ROUTINGKEY}" fileName="logs/SystemOut-${ctx:ROUTINGKEY}.log"
                                 filePattern="./logs/${date:yyyy-MM}/${ctx:ROUTINGKEY}-SystemOut-%d{yyyy-MM-dd}-%i.log.gz">
                        <PatternLayout>
                            <pattern>%d{ISO8601} [%t] %p %c{3} - %m%n</pattern>
                        </PatternLayout>
                        <Policies>
                            <TimeBasedTriggeringPolicy interval="6" modulate="true"/>
                            <SizeBasedTriggeringPolicy size="10 MB"/>
                        </Policies>
                    </RollingFile>
                </Route>

            </Routes>
        </Routing>
        <!--很直白,Console指定了結果輸出到控制台-->
        <Console name="ConsolePrint" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{yyyy.MM.dd HH:mm:ss z} %t %-5level %class{36} %L %M - %msg%xEx%n"/>
        </Console>
    </Appenders>
    <Loggers>
        <!-- 級別順序(低到高):TRACE < DEBUG < INFO < WARN < ERROR < FATAL -->
        <Root level="DEBUG" includeLocation="true">
            <!--AppenderRef中的ref值必須是在前面定義的appenders-->
            <AppenderRef ref="Routing"/>
            <AppenderRef ref="ConsolePrint"/>
        </Root>
    </Loggers>
</Configuration>
log4j.xml

該xml中有一個變量${ctx:ROUTINGKEY},此變量需要在代碼中設定,對TestThread進行修改

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.ThreadContext;
import org.apache.logging.log4j.core.appender.FileAppender;

class TestThread extends Thread {

    private static Logger log = LogManager.getLogger(TestLog.class);
    public TestThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        ThreadContext.put("ROUTINGKEY", Thread.currentThread().getName());
        try {
            System.out.println(Thread.currentThread().getName());
            log.info(Thread.currentThread().getName());
            log.error("error:"+Thread.currentThread().getName());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
TestThread

點擊運行,便可以按照線程生成日志。

 

 該部分代碼可以

2.不使用ThreadContext

首先修改xml配置

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="OFF">
    <Appenders>
        <Routing name="Routing">
            <Routes pattern="$${thread:threadName}">
                <Route>
                    <RollingFile name="logFile-${thread:threadName}"
                                 fileName="logs/concurrent-${thread:threadName}.log"
                                 filePattern="logs/concurrent-${thread:threadName}-%d{MM-dd-yyyy}-%i.log">
                        <PatternLayout pattern="%d %-5p [%t] %C{2} - %m%n"/>
                        <Policies>
                            <SizeBasedTriggeringPolicy size="50 MB"/>
                        </Policies>
                        <DefaultRolloverStrategy max="100"/>
                    </RollingFile>
                </Route>
            </Routes>
        </Routing>
        <Async name="async" bufferSize="1000" includeLocation="true">
            <AppenderRef ref="Routing"/>
        </Async>
        <!--很直白,Console指定了結果輸出到控制台-->
        <Console name="ConsolePrint" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{yyyy.MM.dd HH:mm:ss z} %t %-5level %class{36} %L %M - %msg%xEx%n"/>
        </Console>
    </Appenders>
    <Loggers>
        <Root level="info" includeLocation="true">
            <AppenderRef ref="async"/>
            <AppenderRef ref="ConsolePrint"/>
        </Root>
    </Loggers>
</Configuration>
View Code

然后實現StrLookup

import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.lookup.StrLookup;

@Plugin(name = "thread", category = StrLookup.CATEGORY)
public class ThreadLookup implements StrLookup {
    @Override
    public String lookup(String key) {
        return Thread.currentThread().getName();
    }

    @Override
    public String lookup(LogEvent event, String key) {
        return event.getThreadName() == null ? Thread.currentThread().getName()
                : event.getThreadName();
    }

}
View Code
ThreadLookup類可以獲取到代碼中的線程名稱,所以我們要對每個線程分別命名,用於區分日志名稱。可以通過Thread.currentThread().setName(name);設置。
首先主函數
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.ThreadContext;

import java.util.concurrent.*;

public class TestLog {
    private static Logger log = LogManager.getLogger(TestLog.class);
    public static void main(String[] args) {
        testValidate();

//        Thread[] threads = new Thread[10];
//        for(int i=0;i<10;i++){
//            threads[i] = new TestThread("-"+i);
//        }
//        for(Thread thread : threads) {
//            thread.start();
//        }

//        new Thread(() -> {
//            log.info("info");
//            log.debug("debug");
//            log.error("error");
//            ThreadContext.remove("ROUTINGKEY");
//        }).start();
//        new Thread(() -> {
//            log.info("info");
//            log.debug("debug");
//            log.error("error");
//            ThreadContext.remove("ROUTINGKEY");
//        }).start();
    }
    private static  void testValidate(){
        int threadNum = 3;
        int totalNum = 3;
        ConcurrentLinkedDeque<Future> futureList = new ConcurrentLinkedDeque<>();
        CountDownLatch begin = new CountDownLatch(1);
        ExecutorService executor = Executors.newFixedThreadPool(threadNum);
        for(int i=0;i<totalNum;i++){
            Future submit = executor.submit(new TestThread("name"+i));
            futureList.add(submit);
        }
        begin.countDown();
        executor.shutdown();
    }
}
View Code

主函數對TestThread進行調用,在TestThread中完成對於線程的命名。

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.ThreadContext;
import org.apache.logging.log4j.core.appender.FileAppender;

import java.util.concurrent.Callable;

class TestThread implements Callable {

    private static Logger log = LogManager.getLogger(TestLog.class);
    String name;
    public TestThread(String name) {
        this.name = name;
    }

//    @Override
//    public void run() {
////        ThreadContext.put("ROUTINGKEY", Thread.currentThread().getName());
//        try {
//            System.out.println(Thread.currentThread().getName());
//            log.info(Thread.currentThread().getName());
//            log.error("error:"+Thread.currentThread().getName());
//            Upload upload = new Upload();
//            upload.testUpload(Thread.currentThread().getName());
//        } catch (Exception e) {
//            e.printStackTrace();
//        }
//    }
    @Override
    public Object call(){
        Thread.currentThread().setName(name);
        Upload upload = new Upload();
        upload.testUpload(name);

        return null;
    }
}
View Code

TestThread調用upload方法,進行日志的輸出。

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class Upload {
    private static Logger log = LogManager.getLogger(Upload.class);
    public void testUpload(String name){
        log.info("in testUpload" + name);
    }
}
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM