Log4j2 implements different levels of log output from different threads to different files

Keywords: log4j Apache xml Lombok

Java Logging Framework

As a Java programmer, it must be inseparable from the log framework. Now the best Java log framework is Log4j2, not one of them. According to the official test, Log4j2's asynchronous log performance is better in multi-threaded environment. In asynchronous logging, Log4j2 uses separate threads to perform I/O operations, which can greatly improve the performance of applications.

In the official test, the following figure compares the performance of Sync, Async Appenders and Loggers all async. Loggers all async performs best, and the more threads, the better the performance of Loggers all async.

In addition to comparing the different modes of Log4j2 itself, the official comparisons of Log4j2/Log4j1/Logback are also made, as shown in the figure below.

Among them, Loggers all async is based on LMAX Disruptor Realized.

Using Log4j2

What JAR s are needed

Log4j2 requires at least two JARs, log4j-api-2.x and log4j-core-2.x, and other JAR packages are added according to application requirements.

Configuration file location

By default, Log4j2 looks for a configuration file named log4j2.xml under classpath. You can also use system property to specify the full path of the configuration file. - Dlog4j.configurationFile=path/to/log4j2.xml, specifying the path in Java code as follows

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.LoggerContext;

import java.io.File;

public class Demo {
    public static void main(String[] args) {
        LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false);
        File file = new File("path/to/a/different/log4j2.xml");
        loggerContext.setConfigLocation(file.toURI());
    }
}

 

Normally, you don't need to close Log4j2 manually, if you want to close Log4j2 manually in your code as follows

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.Configurator;

public class Demo {
    public static void main(String[] args) {
        Configurator.shutdown((LoggerContext) LogManager.getContext());
    }
}


Different threads output logs to different files about Log4j2, which can not be listed one by one. If you encounter any problems in development, it is recommended to go. Official Documents In search of solutions.

Method 1 uses ThreadContext

In multi-threaded programming, if no special settings are made, the logs of multiple threads will be output to the same log file, which will bring many inconveniences when looking up the logs. Naturally, we thought about letting different threads output logs to different files. Isn't that better? In the process of looking through official documents, we found FAQ (Frequently Asked Questions), which has a problem. How do I dynamically write to separate log files? That's what we need. Step by step according to the prompt can solve the problem smoothly. The log4j2.xml configuration is as follows:

<?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 value 'special' for key ROUTINGKEY. -->
                <Route key="special">
                    <RollingFile name="Rolling-${ctx:ROUTINGKEY}" fileName="logs/special-${ctx:ROUTINGKEY}.log"
                                 filePattern="./logs/${date:yyyy-MM}/${ctx:ROUTINGKEY}-special-%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>
                <!-- This route is chosen if ThreadContext has no value for key ROUTINGKEY. -->
                <Route key="$${ctx:ROUTINGKEY}">
                    <RollingFile name="Rolling-default" fileName="logs/default.log"
                                 filePattern="./logs/${date:yyyy-MM}/default-%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>
                <!-- 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/other-${ctx:ROUTINGKEY}.log"
                                 filePattern="./logs/${date:yyyy-MM}/${ctx:ROUTINGKEY}-other-%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>
        <!--Very straightforward, Console Specifies the output to the 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>
        <!-- Level order (low to high): TRACE < DEBUG < INFO < WARN < ERROR < FATAL -->
        <Root level="DEBUG" includeLocation="true">
            <!--AppenderRef Medium ref Values must be defined earlier appenders-->
            <AppenderRef ref="Routing"/>
            <AppenderRef ref="ConsolePrint"/>
        </Root>
    </Loggers>
</Configuration>


The test class is shown below.

import lombok.extern.log4j.Log4j2;
import org.apache.logging.log4j.ThreadContext;

@Log4j2
public class TestLog {
    public static void main(String[] args) {
        new Thread(() -> {
            ThreadContext.put("ROUTINGKEY", Thread.currentThread().getName());
            log.info("info");
            log.debug("debug");
            log.error("error");
            ThreadContext.remove("ROUTINGKEY");
        }).start();
        new Thread(() -> {
            ThreadContext.put("ROUTINGKEY", Thread.currentThread().getName());
            log.info("info");
            log.debug("debug");
            log.error("error");
            ThreadContext.remove("ROUTINGKEY");
        }).start();
    }
}


Running the test class results in two log files, other-Thread-1.log and other-Thread-2.log, each of which corresponds to a thread. The program is built using Gradle and relies on JAR packages as follows:

dependencies {
    compile 'org.projectlombok:lombok:1.16.10'
    compile 'org.apache.logging.log4j:log4j-core:2.6'
    compile 'org.apache.logging.log4j:log4j-api:2.6'
}


One thing to note is that each time you use a log object, you need to set ThreadContext. put ("ROUTINGKEY"), Thread. current Thread (). getName ()); the key set should be consistent with the key in the log4j2.xml configuration file, and the value can be any value, which can be understood by referring to the configuration file.

Did you find that every time you use a log object, you need to add extra code. Isn't that nausea that fucking opens the door to nausea - nausea at home? Is there a more elegant solution? And look at the next section.

Method 2 Implementing StrLookup

Modify the log4j2.xml configuration file as follows

<?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>
        <!--Very straightforward, Console Specifies the output to the 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>


Implement the lookup method in StrLookup with the following code:

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();
    }

}


Write the test class with the following code:

import lombok.extern.log4j.Log4j2;

@Log4j2
public class TestLog {
    public static void main(String[] args) {
        new Thread(() -> {
            log.info("info");
            log.debug("debug");
            log.error("error");
        }).start();
        new Thread(() -> {
            log.info("info");
            log.debug("debug");
            log.error("error");
        }).start();
    }
}


The test class will also get two log files, one for each thread, but no ThreadContext. put ("ROUTINGKEY", Thread. current Thread (). getName ()) is needed before using the log object; is it much more refreshing?

According to the official performance test, we know that Loggers all async has the highest performance, but method one uses Sync mode (because Appender defaults to synchronous), method two uses Async Appender mode, so how to further make all Loggers Asynchronous, so that our configuration is more perfect What about it? If you want to use Loggers all async, you need to do one more step. If you are a Maven or Gradle project, you need to add the log4j2.component.properties configuration file in the src/main/resources directory. Official website description The content is

Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector

 

dependencies {
    compile 'org.projectlombok:lombok:1.16.10'
    compile 'org.apache.logging.log4j:log4j-core:2.6'
    compile 'org.apache.logging.log4j:log4j-api:2.6'
    compile 'com.lmax:disruptor:3.3.6'
}

 

It is also necessary to add the dependent disruptor JAR package com.lmax:disruptor:3.3.6 under the classpath. When the configuration uses the AsyncLoggerContextSelector, all loggers are asynchronous. Is there a way to prove that Loggers all async is used? The answer is yes, default location will not be passed to Loggers all async's I/O threads, so if includeLocation=true is not set, the location information in the printed log is "?", such as "2016-12-16:16:38:47, 285 INFO [Thread-3]?-info", if set. After setting includeLocation= "true", print out "2016-12-16 16:39:14, 899 INFO [Thread-3] TestLog-info", Gradle builds dependent on the following:

 

Different levels of log output to different files

Usually, the log levels used are mainly info/debug/error, and if no special configuration is made, the three information is written to a log file. When we need different levels of log output to different files, what should we do? log4j2.xml configuration information is as follows:


 

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="OFF">
    <Properties>
        <Property name="logFilePath">logs</Property>
        <Property name="logFileName">testLog</Property>
    </Properties>
    <Appenders>
        <!--Very straightforward, Console Specifies the output to the 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>
        <!--<File>Output result to specified file</File>-->
        <!--<RollingFile>Similarly, output the result to the specified file, but use buffer,It's going to be faster.</RollingFile>-->
        <!--filePattern: Represents the named path of an old log when it reaches a specified size or time to generate a new log.-->
        <!--PatternLayout: and log4j Similarly, specify the format of the output log. append Indicates whether to append content, the default value is true-->
        <RollingFile name="RollingFileDebug" fileName="${logFilePath}/${logFileName}-debug.log"
                     filePattern="${logFilePath}/$${date:yyyy-MM}/${logFileName}-%d{yyyy-MM-dd}_%i.log.gz">
            <PatternLayout pattern="%d{yyyy.MM.dd HH:mm:ss z} %-5level %class{36} %L %M - %msg%xEx%n"/>
            <!--Note that if there are more than one ThresholdFilter,that Filters Labels are necessary-->
            <Filters>
                <!--First, you need to filter out inconsistent log levels and prioritize unnecessary ones DENY Get rid of it, and then ACCEPT Required log level, order can not be reversed-->
                <!--INFO Denial of output at or above level-->
                <ThresholdFilter level="INFO" onMatch="DENY" onMismatch="NEUTRAL"/>
                <!--Output only DEBUG Level information-->
                <ThresholdFilter level="DEBUG" onMatch="ACCEPT" onMismatch="DENY"/>
            </Filters>
            <Policies>
                <!--Timing strategy to generate new log files every 24 hours-->
                <TimeBasedTriggeringPolicy/>
                <!--Size strategy, every 30 M Generate new log files-->
                <SizeBasedTriggeringPolicy size="30MB"/>
            </Policies>
        </RollingFile>

        <RollingFile name="RollingFileInfo" fileName="${logFilePath}/${logFileName}-info.log"
                     filePattern="${logFilePath}/$${date:yyyy-MM}/${logFileName}-%d{yyyy-MM-dd}_%i.log.gz">
            <Filters>
                <!--onMatch:Action to take when the filter matches. The default value is NEUTRAL-->
                <!--onMismatch:    Action to take when the filter does not match. The default value is DENY-->
                <!--Level at ERROR All above refuse to export-->
                <!--In combination filters, accept use NEUTRAL(Neutral), the log information accepted by the first filter will continue to be filtered with the latter filter. Only log information that meets all filter requirements will be finally written to the log file.-->
                <ThresholdFilter level="ERROR" onMatch="DENY" onMismatch="NEUTRAL"/>
                <ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
            </Filters>
            <PatternLayout pattern="%d{yyyy.MM.dd HH:mm:ss z} %-5level %class{36} %L %M - %msg%xEx%n"/>
            <Policies>
                <TimeBasedTriggeringPolicy/>
                <SizeBasedTriggeringPolicy size="30MB"/>
            </Policies>
        </RollingFile>

        <RollingFile name="RollingFileError" fileName="${logFilePath}/${logFileName}-error.log"
                     filePattern="${logFilePath}/$${date:yyyy-MM}/${logFileName}-%d{yyyy-MM-dd}_%i.log.gz">
            <Filters>
                <ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
            </Filters>
            <PatternLayout pattern="%d{yyyy.MM.dd HH:mm:ss z} %-5level %class{36} %L %M - %msg%xEx%n"/>
            <Policies>
                <TimeBasedTriggeringPolicy/>
                <SizeBasedTriggeringPolicy size="30MB"/>
            </Policies>
        </RollingFile>
    </Appenders>
    <Loggers>
        <!--logger Used to define log Of level And appender,If you don't need to customize, you can use root Solve, root The label is log Default output form-->
        <!-- Level order (low to high): TRACE < DEBUG < INFO < WARN < ERROR < FATAL -->
        <Root level="DEBUG" includeLocation="true">
            <!-- As long as it's level ratio ERROR High, including ERROR Output to console -->
            <!--appender-ref The value in the appenders-->
            <Appender-ref level="ERROR" ref="ConsolePrint"/>
            <Appender-ref ref="RollingFileDebug"/>
            <Appender-ref ref="RollingFileInfo"/>
            <Appender-ref ref="RollingFileError"/>
        </Root>
    </Loggers>
</Configuration>

The contents of src main resources log4j2. component. properties remain unchanged, as follows

Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector


The test code is as follows

import lombok.extern.log4j.Log4j2;

@Log4j2
public class TestLog {
    public static void main(String[] args) {
        new Thread(() -> {
            log.info("info");
            log.debug("debug");
            log.error("error");
        }).start();
        new Thread(() -> {
            log.info("info");
            log.debug("debug");
            log.error("error");
        }).start();
    }
}


Thread 1 generates three log files [testLog-debug.log, testLog-error.log, testLog-info.log]. If you are careful enough, you will find that the info|debug|error information of thread 1 and thread 2 is output to an info|debug|error log file. How to solve this problem? In other words, I want to get it.

Thread 1

Thread 1 Of info Journal
Thread 1 Of debug Journal
Thread 1 Of error Journal

Thread 2

Thread 2 Of info Journal
Thread 2 Of debug Journal
Thread 2 Of error Journal

How to implement six log files? This is what the next section is about.

Different threads and different levels of log output to different files

To achieve this function, we need to do something about Routing Appender. Routing Appenders are mainly used to evaluate LogEvents and then route them to lower-level Appenders. The target Appender can be a previously configured Appender that can be referenced by its name, or it can be dynamically created as needed. Routing Appender should be configured after any Appenders it references to ensure that it closes correctly.

The name attribute in Routing Appender is used to specify the name of the Appender. It can contain multiple Routes subnodes to identify the conditions for selecting the Appender, while Routes has only one attribute, pattern, which evaluates all registered Lokups and the results are used to select routes. There can be multiple routes under Routes, each of which must be configured with a key, which is selected if it matches the evaluation result of "pattern". At the same time, each Route must refer to an Appender. If the Route contains a ref attribute, the Route will refer to an Appender defined in the configuration. If the Route contains an Appender definition, the Appender will be created and reused in the context of the Routing Appender.

If you talk too much nonsense, you can configure it directly and succinctly. log4j2.xml is configured as follows:


 

<?xml version="1.0" encoding="UTF-8"?>
<!--status It means whether to record or not. log4j2 Oneself event Information, by default OFF-->
<Configuration status="OFF">
    <Properties>
        <!--Customize some constants and use them later ${Variable name}Quote-->
        <Property name="logFilePath">logs</Property>
        <Property name="logFileName">testLog</Property>
    </Properties>
    <Appenders>
        <!--Very straightforward, Console Specifies the output to the 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>
        <!--<File>Output result to specified file</File>-->
        <!--<RollingFile>Similarly, output the result to the specified file, but use buffer,It's going to be faster.</RollingFile>-->
        <!--filePattern: Represents the named path of an old log when it reaches a specified size or time to generate a new log.-->
        <!--PatternLayout: and log4j Similarly, specify the format of the output log. append Indicates whether to append content, the default value is true-->
        <Routing name="RollingFileDebug_${thread:threadName}">
            <Routes pattern="$${thread:threadName}">
                <Route>
                    <RollingFile name="RollingFileDebug_${thread:threadName}"
                                 fileName="${logFilePath}/${logFileName}_${thread:threadName}_debug.log"
                                 filePattern="${logFilePath}/$${date:yyyy-MM}/${logFileName}-%d{yyyy-MM-dd}-${thread:threadName}-debug_%i.log.gz">
                        <PatternLayout pattern="%d{yyyy.MM.dd HH:mm:ss z} %-5level %class{36} %L %M - %msg%xEx%n"/>
                        <!--Note that if there are more than one ThresholdFilter,that Filters Labels are necessary-->
                        <Filters>
                            <!--First, you need to filter out inconsistent log levels and prioritize unnecessary ones DENY Get rid of it, and then ACCEPT Required log level, order can not be reversed-->
                            <!--INFO Denial of output at or above level-->
                            <ThresholdFilter level="INFO" onMatch="DENY" onMismatch="NEUTRAL"/>
                            <!--Output only DEBUG Level information-->
                            <ThresholdFilter level="DEBUG" onMatch="ACCEPT" onMismatch="DENY"/>
                        </Filters>
                        <Policies>
                            <!--Timing strategy to generate new log files every 24 hours-->
                            <TimeBasedTriggeringPolicy/>
                            <!--Size strategy, every 30 M Generate new log files-->
                            <SizeBasedTriggeringPolicy size="30MB"/>
                        </Policies>
                    </RollingFile>
                </Route>
            </Routes>
        </Routing>

        <Routing name="RollingFileInfo_${thread:threadName}">
            <Routes pattern="$${thread:threadName}">
                <Route>
                    <RollingFile name="RollingFileInfo_${thread:threadName}"
                                 fileName="${logFilePath}/${logFileName}_${thread:threadName}_info.log"
                                 filePattern="${logFilePath}/$${date:yyyy-MM}/${logFileName}-%d{yyyy-MM-dd}-${thread:threadName}-info_%i.log.gz">
                        <Filters>
                            <!--onMatch: Action to take when the filter matches. The default value is NEUTRAL-->
                            <!--onMismatch:    Action to take when the filter does not match. The default value is DENY-->
                            <!--Level at ERROR All above refuse to export-->
                            <!--In combination filters, accept use NEUTRAL(Neutral), the log information accepted by the first filter will continue to be filtered with the latter filter. Only log information that meets all filter requirements will be finally written to the log file.-->
                            <ThresholdFilter level="ERROR" onMatch="DENY" onMismatch="NEUTRAL"/>
                            <ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
                        </Filters>
                        <PatternLayout pattern="%d{yyyy.MM.dd HH:mm:ss z} %-5level %class{36} %L %M - %msg%xEx%n"/>
                        <Policies>
                            <TimeBasedTriggeringPolicy/>
                            <SizeBasedTriggeringPolicy size="30MB"/>
                        </Policies>
                    </RollingFile>
                </Route>
            </Routes>
        </Routing>

        <Routing name="RollingFileError_${thread:threadName}">
            <Routes pattern="$${thread:threadName}">
                <Route>
                    <RollingFile name="RollingFileError_${thread:threadName}"
                                 fileName="${logFilePath}/${logFileName}_${thread:threadName}_error.log"
                                 filePattern="${logFilePath}/$${date:yyyy-MM}/${logFileName}-%d{yyyy-MM-dd}-${thread:threadName}-error_%i.log.gz">
                        <Filters>
                            <ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
                        </Filters>
                        <PatternLayout pattern="%d{yyyy.MM.dd HH:mm:ss z} %-5level %class{36} %L %M - %msg%xEx%n"/>
                        <Policies>
                            <TimeBasedTriggeringPolicy/>
                            <SizeBasedTriggeringPolicy size="30MB"/>
                        </Policies>
                    </RollingFile>
                </Route>
            </Routes>
        </Routing>
        <!--bufferSize Integer, specifying what can be queued events Maximum quantity if used BlockingQueue,This number must be a power of 2.-->
        <!--includeLocation The default value is FALSE,If specified as TRUE,It reduces performance, but the recommended settings are TRUE,Otherwise, the location line information will not be printed.-->
        <Async name="async" bufferSize="262144" includeLocation="true">
            <AppenderRef ref="RollingFileDebug_${thread:threadName}"/>
            <AppenderRef ref="RollingFileInfo_${thread:threadName}"/>
            <AppenderRef ref="RollingFileError_${thread:threadName}"/>
            <!-- As long as it's level ratio ERROR High, including ERROR Output to console -->
            <AppenderRef ref="ConsolePrint" level="ERROR"/>
        </Async>
    </Appenders>
    <Loggers>
        <!--Logger Used to define log Of level And appender,If you don't need to customize, you can use root Solve, root The label is log Default output form-->
        <!-- Level order (low to high): TRACE < DEBUG < INFO < WARN < ERROR < FATAL -->
        <Root level="DEBUG" includeLocation="true">
            <!--AppenderRef Medium ref Values must be defined earlier appenders-->
            <AppenderRef ref="async"/>
        </Root>
    </Loggers>
</Configuration>

The log4j2.component.properties and ThreadLookup classes remain unchanged, depending on the same JAR package as in the previous section. The test classes are as follows

import lombok.extern.log4j.Log4j2;

@Log4j2
public class TestLog {
    public static void main(String[] args) {
        new Thread(() -> {
            log.info("info");
            log.debug("debug");
            log.error("error");
        }).start();
        new Thread(() -> {
            log.info("info");
            log.debug("debug");
            log.error("error");
        }).start();
    }
}


TesLog_Thread-2_debug.log This program outputs six log files, which are

testLog_Thread-2_error.log
testLog_Thread-2_info.log
testLog_Thread-3_debug.log
testLog_Thread-3_error.log
testLog_Thread-3_info.log

So far, it has realized the function of log output from different threads and different levels to different files.

How to enable All Loggers Asynchronous

In order to make all Loggers asynchronous, is there any other way besides adding a new configuration file, log4j2.component.properties? Yes, just listed below.

  • For example, [IntelliJ IDEA] uses Gradle to build projects, which can be filled in Settings | Build, Execution, Deployment | Build Tools | Gradle | Gradle VM options

    -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector

     

  • The other is to add static code blocks to the ThreadLookup class mentioned earlier.

    static {
        System.setProperty("Log4jContextSelector", "org.apache.logging.log4j.core.async.AsyncLoggerContextSelector");
    }

     

According to the reference manual, it is important to use <Root> or <Logger> tags instead of <asyncRoot> and <asyncLogger> as follows:

When AsyncLoggerContextSelector is used to make all loggers asynchronous, make sure to use normal <root> and <logger> elements in the configuration. The AsyncLoggerContextSelector will ensure that all loggers are asynchronous, using a mechanism that is different from what happens when you configure <asyncRoot> or <asyncLogger>. The latter elements are intended for mixing async with sync loggers.

Mix Synchronous and Asynchronous Loggers

You need a disruptor-3.0.0.jar or higher version of the jar package, you don't need to set the system property Log4jContextSelector, you can mix Synchronous and asynchronous loggers in the configuration, use < AsyncRoot > or < AsyncLogger > to specify the asynchronous loggers, and < AsyncLogger > elements can also contain < Root > and < Logger > for the same purpose. Step Loggers. Note that if <AsyncRoot> or <AsyncLogger> is used, then there is no need to set the system property Log4jContextSelector.

A mixed synchronous and asynchronous Loggers configuration is as follows:

<?xml version="1.0" encoding="UTF-8"?>
<!-- No need to set system property "Log4jContextSelector" to any value
     when using <asyncLogger> or <asyncRoot>. -->
<Configuration status="WARN">
  <Appenders>
    <!-- Async Loggers will auto-flush in batches, so switch off immediateFlush. -->
    <RandomAccessFile name="RandomAccessFile" fileName="asyncWithLocation.log"
              immediateFlush="false" append="false">
      <PatternLayout>
        <Pattern>%d %p %class{1.} [%t] %location %m %ex%n</Pattern>
      </PatternLayout>
    </RandomAccessFile>
  </Appenders>
  <Loggers>
    <!-- pattern layout actually uses location, so we need to include it -->
    <AsyncLogger name="com.foo.Bar" level="trace" includeLocation="true">
      <AppenderRef ref="RandomAccessFile"/>
    </AsyncLogger>
    <Root level="info" includeLocation="true">
      <AppenderRef ref="RandomAccessFile"/>
    </Root>
  </Loggers>
</Configuration>


Reference material

[1]Different log files for multiple threads using log4j2
[2]log4j2: Location for setting Log4jContextSelector system property for asynchronous logging
[3]Making All Loggers Asynchronous
[4]AsyncAppender
[5]Mixing Synchronous and Asynchronous Loggers
[6]How to verify log4j2 is logging asynchronously via LMAX disruptor?
[7]Log4j2 synchronous logging
[8]Log4J2 sync loggers faster than mixed async/sync loggers
[9]Difference between Asynclogger and AsyncAppender in Log4j2

Posted by kristoff on Fri, 05 Jul 2019 14:07:46 -0700