欢迎您光临本小站。希望您在这里可以找到自己想要的信息。。。

Log4j2异步日志

java water 1937℃ 0评论

Log4j 2的异步日志详细介绍:Async Loggers

Apache Log4j 2是对Log4j的升级,它比其前身Log4j 1.x提供了重大改进,并提供了Logback中可用的许多改进,同时修复了Logback架构中的一些固有问题。Log4jApache Log4j 2

Log4j 2基于LMAX Disruptor库,实现了一个高性能的异步记录器。在多线程场景中,异步记录器的吞吐量比Log4j 1.x和Logback高18倍,延迟低。

项目中引入Log4j2依赖

我创建的是Maven项目,按需要在pom文件中添加如下依赖:
pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!– Log4j2 API接口 –>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>${log4j2.version}</version>
</dependency>
<!– Log4j2 API实现 –>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${log4j2.version}</version>
</dependency>
<!– Log4j2 web项目支持 –>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-web</artifactId>
<version>${log4j2.version}</version>
</dependency>
<!– Apache Commons Logging桥接适配器 –>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-jcl</artifactId>
<version>${log4j2.version}</version>
</dependency>
<!–SLF4J桥接适配器–>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>${log4j2.version}</version>
</dependency>

异步日志

异步日志介绍

异步日志的实现思路是在单独的线程中执行I/O操作,在调用形如Logger.log的API时,能够更快的返回到主程序中,以提高应用程序的性能。Log4j 2在异步日志领域,做了很多优化和改进。而我们只需要在配置文件中,做一个简单的配置,就可以获得异步日志记录的功能特性。

Log4j 2设计了两种异步日志:

  1. Async Appender。内部使用的一个队列(ArrayBlockingQueue)和一个后台线程,日志先存入队列,后台线程从队列中取出日志。阻塞队列容易受到锁竞争的影响,当更多线程同时记录时性能可能会变差。
  2. Async Logger。内部使用的是LMAX Disruptor技术,Disruptor是一个无锁的线程间通信库,它不是一个队列,不需要排队,从而产生更高的吞吐量和更低的延迟。

我们采用第二种,也是官方推荐的Async Logger的方式,进行异步日志的配置。

异步日志配置

项目中引入Disruptor依赖

因为Async Logger使用了Disruptor技术,需要在pom文件中添加如下依赖:
pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
<!– Disruptor –>
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.3.7</version>
</dependency>
<!– 如果你想使用Async Appender,可以考虑引入这个包替代ArrayBlockingQueue –>
<dependency>
<groupId>com.conversantmedia</groupId>
<artifactId>disruptor</artifactId>
<classifier>jdk7</classifier>
<version>1.2.10</version>
</dependency>

注意,Log4j-2.9+需要disruptor-3.3.4.jar或更高版本。在Log4j-2.9之前,需要disruptor-3.0.0.jar或更高版本。

全异步

设置一个环境变量,用来开启全异步:全异步

1log4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector

开启全异步时,日志配置中需要使用普通的Root和Logger元素。如果使用了AsyncRoot或AsyncLogger,将产生不必要的开销。

同步异步混合

全异步是官方推荐的,也是性能最佳的方式,但同步异步混合使用,能够提供更大的灵活性。使用AsyncRoot、AsyncLogger、Root、Logger混合配置,可以实现同步异步混合。但是需要注意,配置中只能有一个root元素,也就是只能使用AsyncRoot或Root中的一个。

实际配置

自定义插件

Log4j使用插件模式配置组件。因此,无需编写代码来创建和配置Appender,Layout,Pattern Converter等,Log4j自动识别并使用插件。

因此,我们可以基于插件模式,方便的扩展现有功能特性。

我有两个需求:

  1. 日志文件做分包处理,按照”应用编码/用户标识/8位年月日.log”的规则细化。其中应用编码和用户标识,在应用启动阶段,是空的,而应用启动阶段是需要输出日志的。因此,我希望在应用编码和用户标识为空时,log4j能识别到这种情况,并给它们赋一个默认值。
  2. 日志文件的根路径是可配的,但是我又不希望可配置的参数分散到不同的配置文件中,所以希望日志根路径能够到其他配置文件中读取。

为实现以上需求,我编写了一个自定义插件,基于Log4j提供的上下文解析插件,进行了扩展:ContextMapWithDefValLookup

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Plugin(name = “ctxdefval”, category = “Lookup”)
public class ContextMapWithDefValLookup extends ContextMapLookup {
public static final String DEFAULT_VALUE_PREFIX = “no”;
@Override
public String lookup(String key) {
return this.lookupWithDefaultValue(key, super.lookup(key));
}
@Override
public String lookup(LogEvent event, String key) {
return this.lookupWithDefaultValue(key, super.lookup(event, key));
}
private String lookupWithDefaultValue(String key, String value) {
// 上下文中没有该字段的情况
if (StringUtils.isEmpty(value)) {
// 从统一配置文件中获取,如果获取不到,使用no+key作为默认值(noappid)
value = ReadProperty.getPlatString(key, DEFAULT_VALUE_PREFIX + key);
}
return value;
}
}

配置文件

log4j2.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?xml version=”1.0″ encoding=”UTF-8″?>
<Configuration status=”debug” packages=”com.yondervision.plat.common.log.lookup”>
<Appenders>
<!–打印日志到控制台–>
<Console name=”stdout” target=”SYSTEM_OUT”>
<PatternLayout pattern=”%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %msg %equals{%x}{[]}{}%xEx%n”/>
</Console>
<Routing name=”routing”>
<Routes pattern=”$${ctxdefval:appid}$${ctxdefval:userid}$${date:yyyy-MM-dd}”>
<Route>
<!– 输出到日志文件 –>
<RollingRandomAccessFile name=”RollingFile”
fileName=”${ctxdefval:logpath}/${ctxdefval:appid}/${date:yyyy-MM-dd}/${ctxdefval:userid}.log”
filePattern=”${ctxdefval:logpath}/${ctxdefval:appid}/${date:yyyy-MM-dd}/${ctxdefval:userid}-%i.log.gz”>
<PatternLayout pattern=”%d{HH:mm:ss.SSS} [%-5level] [%tid.%tn] %msg %equals{%x}{[]}{}%xEx%n”/>
<Policies>
<!– 日志达到50MB时打包 –>
<SizeBasedTriggeringPolicy size=”50 MB”/>
</Policies>
</RollingRandomAccessFile>
</Route>
</Routes>
</Routing>
</Appenders>
<Loggers>
<AsyncRoot level=”debug”>
<AppenderRef ref=”stdout” level=”debug”/>
<AppenderRef ref=”routing” level=”info”/>
</AsyncRoot>
</Loggers>
</Configuration>

说明:

  1. 如果Layout中配置成与%C or $class, %F or %file, %l or %location, %L or %line, %M or %method中的一个位置相关的属性,Log4j 2将获取堆栈的快照,并遍历堆栈跟踪以查找位置信息。这是一项昂贵的操作,同步日志中,在获取此堆栈快照时可能会长时间等待。如果不需要位置,则不会获取快照。而异步日志需要在将日志消息传递给另一个线程之前做出此决定,在该点之后,位置信息将丢失。对于异步日志来说,获取堆栈跟踪快照的性能影响甚至更高。因此,默认情况下,异步记录器和异步追加器不包含位置信息。可以通过指定includeLocation =“true”来覆盖异步日志的默认行为。
  2. RollingRandomAccessFileAppender类似于标准的RollingFileAppender,内部使用的是ByteBuffer和RandomAccessFile。由于RandomAccessFile可以自由访问文件的任意位置,日志记录通常是向已存在的文件后追加内容,所以使用RandomAccessFile能获得更好的性能。

转载请注明:学时网 » Log4j2异步日志

喜欢 (6)or分享 (0)

您必须 登录 才能发表评论!