Log4j2 自定义appender

Log4j 2介绍:

     Log4j 2是Java的流行日志记录程序包,几乎每个大型应用程序都包含其自己的日志记录或跟踪API,将日志语句插入代码是调试它的低技术方法。这也可能是唯一的方法,因为调试器并不总是可用或不适用。对于多线程应用程序和整个分布式应用程序通常是这种情况。经验表明,日志记录是开发周期的重要组成部分。它提供有关应用程序运行的精确上下文。一旦插入到代码中,就无需人工干预即可生成日志输出。此外,日志输出可以保存在永久性介质中,以便以后进行研究。除了在开发周期中使用它外,足够丰富的日志记录包也可以视为审核工具。

Log4j 2优势:

     Apache Log4j 2是Log4j的升级版,对Log4j的前身Log4j 1.x进行了重大改进.Log4j API是一个日志外观,可以与Log4j实现一起使用,但是也可以在其他日志实现(例如Logback)之前使用。与SLF4J相比,Log4j API具有多个优点:

  1.  Log4j API支持记录消息而不只是字符串,可以参考FormattedMessage自定义消息格式。
  2.  Log4j API支持lambda表达式。
  3.  与SLF4J相比,Log4j API提供了更多的日志记录方法。
  4.  除了SLF4J支持的“参数化日志记录”格式外,Log4j API还支持使用java.text.MessageFormat语法以及printf样式消息的事件。
  5.  Log4j API提供了LogManager.shutdown()方法。基础的日志记录实现必须实现Terminable接口,该方法才能生效。
  6.  完全支持其他结构,例如标记,日志级别和ThreadContext(aka MDC)。
  7.  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,尤其是在多线程应用程序中。

官方性能测试图如下,此图为记录峰值吞吐量

Log4j2 自定义appender

与其他日志框架性能压测对比:

Log4j2 自定义appender

自定义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全栈技术,大厂面试题不定期分享

Log4j2 自定义appender

上一篇:Log4J2 总结


下一篇:log4j2