zoukankan      html  css  js  c++  java
  • 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
  • 相关阅读:
    HDU2027 统计元音 一点点哈希思想
    湖南工业大学第一届ACM竞赛 数字游戏 字符串处理
    湖南工业大学第一届ACM竞赛 我素故我在 DFS
    HDU3293sort
    HDU2082 找单词 母函数
    HDU1018 Big Number 斯特林公式
    湖南工业大学第一届ACM竞赛 分糖果 位操作
    UVA 357 Let Me Count The Ways
    UVA 147 Dollars
    UVA 348 Optimal Array Multiplication Sequence
  • 原文地址:https://www.cnblogs.com/vactor/p/14598520.html
Copyright © 2011-2022 走看看