Log4j 2介绍:
Log4j 2是Java的流行日志记录程序包,几乎每个大型应用程序都包含其自己的日志记录或跟踪API,将日志语句插入代码是调试它的低技术方法。这也可能是唯一的方法,因为调试器并不总是可用或不适用。对于多线程应用程序和整个分布式应用程序通常是这种情况。经验表明,日志记录是开发周期的重要组成部分。它提供有关应用程序运行的精确上下文。一旦插入到代码中,就无需人工干预即可生成日志输出。此外,日志输出可以保存在永久性介质中,以便以后进行研究。除了在开发周期中使用它外,足够丰富的日志记录包也可以视为审核工具。
Log4j 2优势:
Apache Log4j 2是Log4j的升级版,对Log4j的前身Log4j 1.x进行了重大改进.Log4j API是一个日志外观,可以与Log4j实现一起使用,但是也可以在其他日志实现(例如Logback)之前使用。与SLF4J相比,Log4j API具有多个优点:
- Log4j API支持记录消息而不只是字符串,可以参考FormattedMessage自定义消息格式。
- Log4j API支持lambda表达式。
- 与SLF4J相比,Log4j API提供了更多的日志记录方法。
- 除了SLF4J支持的“参数化日志记录”格式外,Log4j API还支持使用java.text.MessageFormat语法以及printf样式消息的事件。
- Log4j API提供了LogManager.shutdown()方法。基础的日志记录实现必须实现Terminable接口,该方法才能生效。
- 完全支持其他结构,例如标记,日志级别和ThreadContext(aka MDC)。
- Log4j 2 API将提供最佳性能,而Log4j 2提供对Log4j 1.2,SLF4J,Commons Logging和java.util.logging(JUL)API的支持。
性能:
Log4j 2包含基于LMAX Disruptor库的下一代异步记录器。Disrupter是一个开源的并发框架,能够在无锁的情况下实现线程安全高效的并发操作,关于Disrupter框架的原理与使用,会在之后的高并发框架专栏详细剖析。在多线程方案中,与Log4j 1.x和Logback相比,异步Logger的吞吐量高18倍,延迟降低了几个数量级。否则,Log4j 2明显优于Log4j 1.x,Logback和java.util.logging,尤其是在多线程应用程序中。
官方性能测试图如下,此图为记录峰值吞吐量
与其他日志框架性能压测对比:
自定义appender:
Appender负责将LogEvents传递到其目的地。每个Appender必须实现Appender 接口。大多数Appender都会扩展 AbstractAppender ,从而增加生命周期和可过滤支持。生命周期允许组件在配置完成后完成初始化,并在关机期间执行清理。可过滤允许组件具有附加的过滤器,这些过滤器将在事件处理期间进行评估。由于场景需要将消息同步到ELK集群,进行分析,问题排查等操作,官方提供的appender不能满足业务场景,因此需要自定义.
一. maven依赖:
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.14.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.14.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.14.0</version>
</dependency>
</dependencies>
二. KafkaAppender:
@Plugin(name="Kafka", //命名根据项目需要可自定义
category="Core",
elementType="appender",
printObject=true)
public final class KafkaAppender
extends AbstractAppender
...
//创建appender
public static KafkaAppender createAppender(final Layout<? extends Serializable> layout, final Filter filter,
final String name, final boolean ignoreExceptions, final String topic, final Property[] properties,
final Configuration configuration,
final String key) {
if (layout == null) {
AbstractLifeCycle.LOGGER.error("No layout provided for KafkaAppender");
return null;
}
final KafkaManager kafkaManager = KafkaManager.getManager(configuration.getLoggerContext(), name, topic, true,
properties, key);
return new KafkaAppender(name, layout, filter, ignoreExceptions, kafkaManager, null, null);
}
//实现父类append方法,真正将日志发送出去
public void append(final LogEvent event) {
if (event.getLoggerName() != null && event.getLoggerName().startsWith("org.apache.kafka")) {
LOGGER.warn("Recursive logging from [{}] for appender [{}].", event.getLoggerName(), getName());
} else {
try {
tryAppend(event);
} catch (final Exception e) {
if (this.retryCount != null) {
int currentRetryAttempt = 0;
while (currentRetryAttempt < this.retryCount) {
currentRetryAttempt++;
try {
tryAppend(event);
break;
} catch (Exception e1) {
}
}
}
error("Unable to write to Kafka in appender [" + getName() + "]", event, e);
}
}
}
private void tryAppend(final LogEvent event) throws ExecutionException, InterruptedException, TimeoutException {
final Layout<? extends Serializable> layout = getLayout();
byte[] data;
if (layout instanceof SerializedLayout) {
final byte[] header = layout.getHeader();
final byte[] body = layout.toByteArray(event);
data = new byte[header.length + body.length];
System.arraycopy(header, 0, data, 0, header.length);
System.arraycopy(body, 0, data, header.length, body.length);
} else {
data = layout.toByteArray(event);
}
manager.send(data);
}
//注意:log4j2 中kafkaManager初始化kafka的属性配置时序列化方式为org.apache.kafka.common.serialization.ByteArraySerializer
三. 关于配置文件.properties , .xml文件二选一即可,或者根据业务场景自行配置..xml部分配置如下:
<?xml version="1.0" encoding="UTF-8"?>
...
<Appenders>
<Kafka name="Kafka" topic="log-test">
<PatternLayout pattern="%date %message"/>
<Property name="bootstrap.servers">localhost:9092</Property>
</Kafka>
</Appenders>
四. 异步日志处理
为了减小对业务系统的影响,建议采用异步方式处理日志.Log4j-2.9和更高版本要求在类路径上使用disruptor-3.3.4.jar或更高版本。在Log4j-2.9之前,需要使用disruptor-3.0.0.jar或更高版本
同步异步混合配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<!-- No need to set system property "log4j2.contextSelector" 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="Kafka" 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="Kafka"/>
</AsyncLogger>
<Root level="info" includeLocation="true"> //includeLocation 会打印文件行数,影响性能,不推荐
<AppenderRef ref="Kafka"/>
</Root>
</Loggers>
</Configuration>
关注我的公众号 : 宇哥996(id: java_zyh) Java全栈技术,大厂面试题不定期分享