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
  • 相关阅读:
    webpack基本使用笔记
    gulp学习记录
    页面优化
    linux下使用indent整理代码
    C++中的getline()
    Sum of Two Integers
    TwoSum
    IDEA个人常用快捷键总结
    mysql数据库遇到的各种问题
    Python中*args 和**kwargs的用法和区别
  • 原文地址:https://www.cnblogs.com/vactor/p/14598520.html
Copyright © 2011-2022 走看看