springboot slf4j log4j2 动态创建日志的方法

2 篇文章 0 订阅

 

Java日志框架

作为一个Java程序员,肯定离不开日志框架,现在最优秀的Java日志框架是Log4j2,没有之一。根据官方的测试表明,在多线程环境下,Log4j2的异步日志表现更加优秀。在异步日志中,Log4j2使用独立的线程去执行I/O操作,可以极大地提升应用程序的性能。

在官方的测试中,下图比较了SyncAsync AppendersLoggers all async三者的性能。其中Loggers all async表现最为出色,而且线程数越多,Loggers all async性能越好。

b9a258ea5b0d1248a36de147459e0861.png

除了对Log4j2自身的不同模式做对比以外,官方还做了Log4j2/Log4j1/Logback的对比,如下图所示

a8efcc5ca91280253430ffa69aa1505a.png

其中,Loggers all async是基于LMAX Disruptor实现的。

使用Log4j2

需要哪些JAR

使用Log4j2最少需要两个JAR,分别是log4j-api-2.xlog4j-core-2.x,其它JAR包根据应用程序需要添加。

配置文件位置

默认的,Log4j2在classpath下寻找名为log4j2.xml的配置文件。也可以使用system property指定配置文件的全路径。-Dlog4j.configurationFile=path/to/log4j2.xml,在Java代码中指定路径如下所示


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

   }

}

 
 

 

一般的,不需要手动关闭Log4j2,如果想手动在代码中关闭Log4j2如下所示

package com;

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

有关Log4j2的内容很多,不能一一列出,如果在开发中遇到任何问题,推荐去官方文档中寻找解决方案。

不同的线程输出日志到不同的文件中

方法一使用ThreadContext

在多线程编程中,如果不做特殊的设置,那么多个线程的日志会输出到同一个日志文件中,这样在查阅日志的时候,会带来诸多不便。很自然地,我们想到了让不同的线程输出日志到不同的文件中,这样不是更好吗?在翻阅官方文档过程中,找到了FAQ(Frequently Asked Questions),其中有个问题How do I dynamically write to separate log files?正是我们所需要的。根据提示步步推进可以顺利解决该问题。其中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 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>
		<!--很直白,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>

 

测试类如下所示

package com;

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

 

 

 

运行测试类,会得到如下两个日志文件,other-Thread-1.logother-Thread-2.log,每个日志文件对应着一个线程。该程序使用Gradle构建,依赖的JAR包如下:

 

1

2

3

4

5

 

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'

}

 

需要注意的一点是,每次在使用log对象之前,需要先设置ThreadContext.put("ROUTINGKEY", Thread.currentThread().getName());,设置的keylog4j2.xml配置文件中的key要一致,而value可以是任意值,参考配置文件即可理解。

有没有发现,每次使用log对象,还需要添加额外的代码,这不是恶心他妈给恶心开门——恶心到家了吗?有没有更加优雅地解决办法呢?且看下节。

方法二实现StrLookup

修改log4j2.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>

 

 

实现StrLookup中的lookup方法,代码如下:

package com;

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


}

 

 

 

编写测试类,代码如下:

package com;

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

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

 

该测试类同样会得到两个日志文件,每个线程分别对应一个,但是在使用log对象之前不再需要设置ThreadContext.put("ROUTINGKEY", Thread.currentThread().getName());,是不是清爽多了。

根据官方的性能测试我们知道,Loggers all async的性能最高,但是方法一使用的是Sync模式(因为Appender默认是synchronous的),方法二使用的是Async Appender模式,那么如何更进一步让所有的Loggers都是Asynchronous的,让我们的配置更完美呢?想要使用Loggers all async还需要做一步设置,如果是Maven或Gradle项目,需要在src/main/resources目录下添加log4j2.component.properties配置文件,根据官网说明,其中内容为

 

1

 

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

同时还需要在classpath下添加依赖的disruptor JAR包com.lmax:disruptor:3.3.6。当配置使用了AsyncLoggerContextSelector之后,所有的Loggers就都是异步的了。有方法证明使用了Loggers all async吗,答案是有,默认的location不会传递给Loggers all async的I/O线程,所以如果不设置includeLocation=true的话,打印出来的日志中location信息是“?”,例如“2016-12-16 16:38:47,285 INFO [Thread-3] ? - info”,如果设置includeLocation=”true”之后,打印出“2016-12-16 16:39:14,899 INFO [Thread-3] TestLog - info”,Gradle构建依赖如下:

 

1

2

3

4

5

6

 

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'

}

 

不同级别的日志输出到不同的文件中

通常情况下,用到的日志级别主要是info/debug/error三个,而如果不做特殊配置,这三者信息是写到一个日志文件中的,当我们需要不同级别的日志输出到不同的文件中时,需要如何做呢?log4j2.xml配置信息如下:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="OFF">
    <Properties>
        <Property name="logFilePath">logs</Property>
        <Property name="logFileName">testLog</Property>
    </Properties>
    <Appenders>
        <!--很直白,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>输出结果到指定文件</File>-->
        <!--<RollingFile>同样输出结果到指定文件,但是使用buffer,速度会快点</RollingFile>-->
        <!--filePattern:表示当日志到达指定的大小或者时间,产生新日志时,旧日志的命名路径。-->
        <!--PatternLayout:和log4j一样,指定输出日志的格式,append表示是否追加内容,值默认为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"/>
            <!--注意,如果有多个ThresholdFilter,那么Filters标签是必须的-->
            <Filters>
                <!--首先需要过滤不符合的日志级别,把不需要的首先DENY掉,然后在ACCEPT需要的日志级别,次序不能颠倒-->
                <!--INFO及以上级别拒绝输出-->
                <ThresholdFilter level="INFO" onMatch="DENY" onMismatch="NEUTRAL"/>
                <!--只输出DEBUG级别信息-->
                <ThresholdFilter level="DEBUG" onMatch="ACCEPT" onMismatch="DENY"/>
            </Filters>
            <Policies>
                <!--时间策略,每隔24小时产生新的日志文件-->
                <TimeBasedTriggeringPolicy/>
                <!--大小策略,每到30M时产生新的日志文件-->
                <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-->
                <!--级别在ERROR之上的都拒绝输出-->
                <!--在组合过滤器中,接受使用NEUTRAL(中立),被第一个过滤器接受的日志信息,会继续用后面的过滤器进行过滤,只有符合所有过滤器条件的日志信息,才会被最终写入日志文件-->
                <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用于定义log的level以及所采用的appender,如果无需自定义,可以使用root解决,root标签是log的默认输出形式-->
        <!-- 级别顺序(低到高):TRACE < DEBUG < INFO < WARN < ERROR < FATAL -->
        <Root level="DEBUG" includeLocation="true">
            <!-- 只要是级别比ERROR高的,包括ERROR就输出到控制台 -->
            <!--appender-ref中的值必须是在前面定义的appenders-->
            <Appender-ref level="ERROR" ref="ConsolePrint"/>
            <Appender-ref ref="RollingFileDebug"/>
            <Appender-ref ref="RollingFileInfo"/>
            <Appender-ref ref="RollingFileError"/>
        </Root>
    </Loggers>
</Configuration>

 

src\main\resources\log4j2.component.properties内容不变,如下

 

1

 

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

 

测试代码如下

package com;

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


    }
}

 

 

该程序会生成三份日志文件【testLog-debug.log,testLog-error.log,testLog-info.log】,如果你足够细心的话,就会发现线程1和线程2的info|debug|error信息都输出到一个info|debug|error日志文件中了。如何解决这个问题呢?换句话说,我想得到

Thread 1

Thread 1的info日志
Thread 1的debug日志
Thread 1的error日志

Thread 2

Thread 2的info日志
Thread 2的debug日志
Thread 2的error日志

六个日志文件,要如何实现呢?这正是下一节要讲述的内容。

不同线程不同级别的日志输出到不同的文件中

要实现该功能,还要从RoutingAppender身上做文章。RoutingAppender主要用来评估LogEvents,然后将它们路由到下级Appender。目标Appender可以是先前配置的并且可以由其名称引用的Appender,或者可以根据需要动态地创建Appender。RoutingAppender应该在其引用的任何Appenders之后配置,以确保它可以正确关闭。

RoutingAppender中的name属性用来指定该Appender的名字,它可以包含多个Routes子节点,用来标识选择Appender的条件,而Routes只有一个属性“pattern”,该pattern用来评估所有注册的Lookups,并且其结果用于选择路由。在Routes下可以有多个Route,每个Route都必须配置一个key,如果这个key匹配“pattern”的评估结果,那么这个Route就被选中。同时每个Route都必须引用一个Appender,如果这个Route包含一个ref属性,那么这个Route将引用一个在配置中定义的Appender,如果这个Route包含一个Appender的定义,那么这个Appender将会根据RoutingAppender的上下文创建并被重用。

废话说多了,直接上配置才简洁明了。log4j2.xml配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<!--status的含义为是否记录log4j2本身的event信息,默认是OFF-->
<Configuration status="OFF">
    <Properties>
        <!--自定义一些常量,之后使用${变量名}引用-->
        <Property name="logFilePath">logs</Property>
        <Property name="logFileName">testLog</Property>
    </Properties>
    <Appenders>
        <!--很直白,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>输出结果到指定文件</File>-->
        <!--<RollingFile>同样输出结果到指定文件,但是使用buffer,速度会快点</RollingFile>-->
        <!--filePattern:表示当日志到达指定的大小或者时间,产生新日志时,旧日志的命名路径。-->
        <!--PatternLayout:和log4j一样,指定输出日志的格式,append表示是否追加内容,值默认为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"/>
                        <!--注意,如果有多个ThresholdFilter,那么Filters标签是必须的-->
                        <Filters>
                            <!--首先需要过滤不符合的日志级别,把不需要的首先DENY掉,然后在ACCEPT需要的日志级别,次序不能颠倒-->
                            <!--INFO及以上级别拒绝输出-->
                            <ThresholdFilter level="INFO" onMatch="DENY" onMismatch="NEUTRAL"/>
                            <!--只输出DEBUG级别信息-->
                            <ThresholdFilter level="DEBUG" onMatch="ACCEPT" onMismatch="DENY"/>
                        </Filters>
                        <Policies>
                            <!--时间策略,每隔24小时产生新的日志文件-->
                            <TimeBasedTriggeringPolicy/>
                            <!--大小策略,每到30M时产生新的日志文件-->
                            <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-->
                            <!--级别在ERROR之上的都拒绝输出-->
                            <!--在组合过滤器中,接受使用NEUTRAL(中立),被第一个过滤器接受的日志信息,会继续用后面的过滤器进行过滤,只有符合所有过滤器条件的日志信息,才会被最终写入日志文件-->
                            <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整数,指定可以排队的events最大数量,如果使用BlockingQueue,这个数字必须是2的幂次-->
        <!--includeLocation默认值是FALSE,如果指定为TRUE,会降低性能,但是推荐设置为TRUE,否则不打印位置行信息-->
        <Async name="async" bufferSize="262144" includeLocation="true">
            <AppenderRef ref="RollingFileDebug_${thread:threadName}"/>
            <AppenderRef ref="RollingFileInfo_${thread:threadName}"/>
            <AppenderRef ref="RollingFileError_${thread:threadName}"/>
            <!-- 只要是级别比ERROR高的,包括ERROR就输出到控制台 -->
            <AppenderRef ref="ConsolePrint" level="ERROR"/>
        </Async>
    </Appenders>
    <Loggers>
        <!--Logger用于定义log的level以及所采用的appender,如果无需自定义,可以使用root解决,root标签是log的默认输出形式-->
        <!-- 级别顺序(低到高):TRACE < DEBUG < INFO < WARN < ERROR < FATAL -->
        <Root level="DEBUG" includeLocation="true">
            <!--AppenderRef中的ref值必须是在前面定义的appenders-->
            <AppenderRef ref="async"/>
        </Root>
    </Loggers>
</Configuration>

 

log4j2.component.propertiesThreadLookup类不变,依赖的JAR包和上一节一样。测试类如下

package com;

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

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

    }
}

该程序会输出六个日志文件,分别是

testLog_Thread-2_debug.log
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

至此,就实现了不同线程不同级别的日志输出到不同文件中的功能。

如何启用All Loggers Asynchronous

为了使得所有的Loggers都是异步的,除了添加一个新的配置文件,就是log4j2.component.properties外,还有其它方式吗?有的,仅列举如下

  • 例如【IntelliJ IDEA】中使用Gradle构建项目,那么可以在Settings | Build, Execution, Deployment | Build Tools | Gradle | Gradle VM options中填入

     

    1

     

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

  • 另一种就是在前面提到的ThreadLookup类中,添加静态代码块

     

    1

    2

    3

     

    static {

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

    }

根据参考手册,有一点需要注意的就是,要使用<Root><Logger>标签,而不是<asyncRoot><asyncLogger>,原文如下:

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.

混合使用Synchronous和Asynchronous Loggers

需要disruptor-3.0.0.jar或更高版本的jar包,不需要设置系统属性Log4jContextSelector,在配置中可以混合使用Synchronousasynchronous loggers,使用<AsyncRoot>或者<AsyncLogger>去指定需要异步的Loggers,<AsyncLogger>元素还可以包含<Root><Logger>用于同步的Loggers。注意如果使用的是<AsyncRoot>或者<AsyncLogger>,那么就无需设置系统属性Log4jContextSelector了。

一个混合了同步和异步的Loggers配置如下:

<?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>

 

 

 

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: Spring Boot可以很方便地集成SLF4JLog4j2。以下是集成步骤: 1. 在pom.xml文件中添加以下依赖: ``` <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> </dependency> ``` 2. 在application.properties文件中配置日志级别和日志输出路径: ``` logging.level.root=INFO logging.file=/var/log/myapp.log ``` 3. 在代码中使用SLF4J进行日志记录: ``` import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class MyClass { private static final Logger logger = LoggerFactory.getLogger(MyClass.class); public void doSomething() { logger.info("Doing something..."); } } ``` 以上就是Spring Boot集成SLF4JLog4j2的简单步骤。 ### 回答2: Spring Boot是一个开发web应用和微服务的框架,而SLF4JLog4j2是一种记录日志信息的工具。将SLF4JLog4j2这两种工具集成到Spring Boot应用中可以方便地进行日志记录。 SLF4J是一个抽象的日志接口,它允许您在不更改应用程序代码的情况下更改底层日志记录器。Log4j2是一个快速、灵活且配置方便的日志组件,它可以完全控制日志记录和输出。 在Spring Boot集成SLF4JLog4j2之前,需要在pom.xml文件中添加以下依赖项: ``` <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </dependency> ``` 以上依赖项将引入Spring Boot和Log4j2所需的库。 在应用程序的配置文件application.yaml中,需要添加以下内容: ``` logging: config: log4j2.xml ``` 以上内容将告诉Spring Boot使用Log4j2记录日志,并使用log4j2.xml文件来配置Log4j2。 在log4j2.xml文件中,需要定义一个或多个logger,例如: ``` <Logger name="com.example.myapp" level="info"> <AppenderRef ref="stdout" /> </Logger> ``` 通过上述配置,当com.example.myapp的日志级别为info时,日志信息将被输出到stdout。 除此之外,还需要在应用程序的Java类中添加日志记录代码,例如: ``` import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class MyApp { private static final Logger logger = LoggerFactory.getLogger(MyApp.class); public void myMethod() { logger.info("This is an info message"); logger.error("This is an error message"); } } ``` 在上述代码中,使用LoggerFactory获取Logger对象,然后使用.info()和.error()方法记录日志信息。在日志信息中,可以添加变量,例如: ``` logger.info("This is a message with variables: {} {} {}", var1, var2, var3); ``` 上述代码中,var1、var2和var3是将被替换为实际值的变量。 总之,Spring Boot集成SLF4JLog4j2可以方便地进行日志记录,只需要在配置文件application.yaml和log4j2.xml中进行相应的配置即可。同时,在Java类中添加日志记录代码可以很容易地实现记录日志的功能。 ### 回答3: Spring Boot是一款非常流行的Java开发框架,而Slf4jLog4j2则是用于Java项目中的日志库。这两个库的集成是非常常见的需求,因为它们可以协同工作,帮助我们记录日志和排查问题。 首先,在pom.xml文件中添加Slf4jLog4j2的依赖项。这个过程非常简单,只需要在<dependencies>标签内添加以下代码段即可: ``` <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.30</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> <version>2.13.3</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.13.3</version> </dependency> ``` 这里我们添加了Spring Boot Web启动器,以及Slf4jLog4j2的相关依赖项。我们还需要在application.properties文件中设置日志级别和输出格式。 在application.properties文件中添加以下代码: ``` logging.level.root=INFO logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss.SSS} %5p [%t] - %m%n ``` 这里我们设置root日志的级别为INFO,表示只输出INFO级别及以上的日志信息。同时,我们设置日志输出格式,包括日期、日志级别、线程名和消息等。你可以根据项目的需求来自定义日志输出格式。 现在,我们已经完成了Slf4jLog4j2的集成工作。在我们的Java代码中,可以通过Logger接口来记录日志信息,如下所示: ``` import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Controller public class HomeController { private static final Logger logger = LoggerFactory.getLogger(HomeController.class); @GetMapping("/") public String home() { logger.info("Welcome home!"); return "home"; } } ``` 这里我们使用LoggerFactory类来获取Logger对象,Logger对象可以使用info()、debug()、error()等方法来记录不同级别的日志信息。在这个例子中,我们在HomeController类的home()方法中记录了一条info级别的日志信息。这个日志信息将会被Slf4j记录下来,并使用Log4j2将它输出到终端上。 以上就是Spring Boot集成Slf4jLog4j2的基本步骤。这个过程非常简单,只需要几步就可以完成。如果你想深入了解Slf4jLog4j2的更多特性,可以查看官方文档或其他相关资料。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Sunday_ding

一分钱也是爱

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值