繁华浮尘,倾听Appenders的故事

孤人 提交于 2019-12-03 21:09:24

#前言

前面一篇博客(《纷乱日志框架,你选谁?》)讲了各种的日志框架的概况,后续的博客将针对log4j 2的配置做一些介绍,最后会去探讨和研究下log4j 2内部的结构以及实现,然后这一系列的博客就结束了。

关于log4j 2的配置,将会从Appenders入手开始,看完官网的相应资料以及自己的动手实验,会感觉log4j 2提供了相当强大的日志活动以及过滤操作的支持,让人情不自禁的想去了解内部是怎样的一种架构。当然心急吃不了热豆腐,让我们先来感受感受log4j 2的Appender强大功能,倾听这位乱世佳人的故事。

#Appenders

Appenders作用是传递日志活动(logEvents)至目标Logger上,每个Appender都实现了Appender接口。大多数Appenders继承了AbstractAppender ,这个抽离类实现了Lifecycle接口和 Filterable接口,因而提供了相应生命周期以及过滤功能的支持。

Appender通常只负责写相关日志数据至目标Logger上,大多数情况下,它还需要格式化相应的日志数据。有些Appender还封装了其它的Appender,以实现可以修改日志活动信息,处理Appender的内部失败,基于高级的过滤规则路由信息至稳定的Appender等功能。

Appender通常会被命名一个名字,用来被Loggers所有引用。

#AsyncAppender

AsyncAppender封装其它Appender,另起一个线程来处理日志的写操作(顾名思义,其实这种情况就是之前提到的封装其它的Appender)。

AsyncAppender默认使用的是java.util.concurrent.ArrayBlockingQueue来存放处理的线程,它的弊端是:在封锁竞争的时候,处理的性能将大打折扣。为了获得更好的性能,可以考虑使用lock-free Async Loggers。 ###配置示例 简单典型的AsyncAppender 配置如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <Configuration status="warn" name="MyApp" packages="">
      <Appenders>
        <File name="MyFile" fileName="logs/app.log">
          <PatternLayout>
                <Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
          </PatternLayout>
        </File>
        <Async name="Async">
            <AppenderRef ref="MyFile"/>
        </Async>
      </Appenders>
      <Loggers>
        <Root level="error">
          <AppenderRef ref="Async"/>
        </Root>
      </Loggers>
    </Configuration>

从Log4j 2.7开始,可以使用BlockingQueueFactory插件来自定义实现具体的BlockingQueue或者TransferQueue,在<Async>标签内配置即可,示例配置如下:

   <Configuration name="LinkedTransferQueueExample">
        <Appenders>
            <List name="List"/>
            <Async name="Async" bufferSize="262144">
                <AppenderRef ref="List"/>
                <LinkedTransferQueue/>
            </Async>
        </Appenders>
        <Loggers>
            <Root>
                <AppenderRef ref="Async"/>
            </Root>
        </Loggers>
   </Configuration>

提供了以下四种具体实现:

queue_implemention

#ConsoleAppender

ConsoleAppender是将 System.out 或者 System.err的日志信息打印至控制台,必须指定相应的日志格式; ###配置示例 简单典型的ConsoleAppender配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
  <Appenders>
    <Console name="STDOUT" target="SYSTEM_OUT">
      <PatternLayout pattern="%m%n"/>
    </Console>
  </Appenders>
  <Loggers>
    <Root level="error">
      <AppenderRef ref="STDOUT"/>
    </Root>
  </Loggers>
</Configuration>

#FailoverAppender

生活中总有些渣男会脚踏两只船甚至多只,有的也甘愿做备胎,FailoverAppender则是采用"备胎"策略,万一之前的Appender失败了,由备份的Appender来处理相应日志信息;primary配置项表示优先处理的Appender,若失败了,则使用secondaries Appender;retryIntervalSeconds配置表示重试primary Appender的时间间隔。

###配置示例 简单典型的FailoverAppender配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
  <Appenders>
    <RollingFile name="RollingFile" fileName="logs/app.log" filePattern="logs/app-%d{MM-dd-yyyy}.log.gz"
             ignoreExceptions="false">
      <PatternLayout>
        <Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
      </PatternLayout>
      <TimeBasedTriggeringPolicy />
    </RollingFile>
    <Console name="STDOUT" target="SYSTEM_OUT" ignoreExceptions="false">
      <PatternLayout pattern="%m%n"/>
    </Console>
    <Failover name="Failover" primary="RollingFile">
      <Failovers>
        <AppenderRef ref="Console"/>
      </Failovers>
    </Failover>
  </Appenders>
  <Loggers>
    <Root level="error">
      <AppenderRef ref="Failover"/>
    </Root>
  </Loggers>
</Configuration>

#FileAppender

FileAppender是一个OutputStreamAppender,将日志信息写至到指定目录以及相应文件名的日志文件中;

###配置示例 简单典型的FileAppender配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
  <Appenders>
    <File name="MyFile" fileName="logs/app.log">
      <PatternLayout>
        <Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
      </PatternLayout>
    </File>
  </Appenders>
  <Loggers>
    <Root level="error">
      <AppenderRef ref="MyFile"/>
    </Root>
  </Loggers>
</Configuration>

#RandomAccessFileAppender

除了一直缓存数据之外,RandomAccessFileAppender和标准的FileAppender是非常类似的,RandomAccessFileAppender内部使用了ByteBuffer + RandomAccessFile来替代BufferedOutputStream.。相对于FileAppender的bufferedIO配置成true的情况,这样的改变,带了 20-200% 的性能提升。了解里面原理的就知道,其实这就是NIO与IO之前的区别导致的,有兴趣的可以自行的去了解NIO的机制,以前看到在ImportNew平台上有关高并发一个系列的博客,非常的不错,后续给大家推荐一下。 ###配置示例 简单典型的RandomAccessFileAppender配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
  <Appenders>
    <RandomAccessFile name="MyFile" fileName="logs/app.log">
      <PatternLayout>
        <Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
      </PatternLayout>
    </RandomAccessFile>
  </Appenders>
  <Loggers>
    <Root level="error">
      <AppenderRef ref="MyFile"/>
    </Root>
  </Loggers>
</Configuration>

#FlumeAppender

Apache Flume是一个分布式、可靠、高可用的系统,它可以高效的收集、整合并移动不同的资源至一个集中式数据存储。

Flume Appender 支持以下三种操作:

  1. 作为远程的Flume客户端发送 Flume events ;
  2. 作为内嵌的Flume代理,直接推送Flume events至Flume进行处理;
  3. 持久化events至本地BerkeleyDB数据存储,再异步推送events至Flume;

###配置示例 FlumeAppender配置主次Flume代理:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
  <Appenders>
    <Flume name="eventLogger" compress="true">
      <Agent host="192.168.10.101" port="8800"/>
      <Agent host="192.168.10.102" port="8800"/>
      <RFC5424Layout enterpriseNumber="18060" includeMDC="true" appName="MyApp"/>
    </Flume>
  </Appenders>
  <Loggers>
    <Root level="error">
      <AppenderRef ref="eventLogger"/>
    </Root>
  </Loggers>
</Configuration>

FlumeAppender配置主次Flume代理并持久化events至本地硬盘:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
  <Appenders>
    <Flume name="eventLogger" compress="true" type="persistent" dataDir="./logData">
      <Agent host="192.168.10.101" port="8800"/>
      <Agent host="192.168.10.102" port="8800"/>
      <RFC5424Layout enterpriseNumber="18060" includeMDC="true" appName="MyApp"/>
      <Property name="keyProvider">MySecretProvider</Property>
    </Flume>
  </Appenders>
  <Loggers>
    <Root level="error">
      <AppenderRef ref="eventLogger"/>
    </Root>
  </Loggers>
</Configuration>

FlumeAppender配置主次Flume代理并推送events至内嵌Flume Agent:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
  <Appenders>
    <Flume name="eventLogger" compress="true" type="Embedded">
      <Agent host="192.168.10.101" port="8800"/>
      <Agent host="192.168.10.102" port="8800"/>
      <RFC5424Layout enterpriseNumber="18060" includeMDC="true" appName="MyApp"/>
    </Flume>
    <Console name="STDOUT">
      <PatternLayout pattern="%d [%p] %c %m%n"/>
    </Console>
  </Appenders>
  <Loggers>
    <Logger name="EventLogger" level="info">
      <AppenderRef ref="eventLogger"/>
    </Logger>
    <Root level="warn">
      <AppenderRef ref="STDOUT"/>
    </Root>
  </Loggers>
</Configuration>

#JDBCAppender

JDBCAppender可以持久化日志信息至关系型数据库中,可以配置使用JNDI或者自定义的工厂方法来获取JDBC连接,无论使用哪种,必须使用连接连接池来提供,否则,日志的性能将受很大影响。 ###配置示例 使用JNDI方式配置:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="error">
  <Appenders>
    <JDBC name="databaseAppender" tableName="dbo.application_log">
      <DataSource jndiName="java:/comp/env/jdbc/LoggingDataSource" />
      <Column name="eventDate" isEventTimestamp="true" />
      <Column name="level" pattern="%level" />
      <Column name="logger" pattern="%logger" />
      <Column name="message" pattern="%message" />
      <Column name="exception" pattern="%ex{full}" />
    </JDBC>
  </Appenders>
  <Loggers>
    <Root level="warn">
      <AppenderRef ref="databaseAppender"/>
    </Root>
  </Loggers>
</Configuration>

使用工厂方法配置:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="error">
  <Appenders>
    <JDBC name="databaseAppender" tableName="LOGGING.APPLICATION_LOG">
      <ConnectionFactory class="net.example.db.ConnectionFactory" method="getDatabaseConnection" />
      <Column name="EVENT_ID" literal="LOGGING.APPLICATION_LOG_SEQUENCE.NEXTVAL" />
      <Column name="EVENT_DATE" isEventTimestamp="true" />
      <Column name="LEVEL" pattern="%level" />
      <Column name="LOGGER" pattern="%logger" />
      <Column name="MESSAGE" pattern="%message" />
      <Column name="THROWABLE" pattern="%ex{full}" />
    </JDBC>
  </Appenders>
  <Loggers>
    <Root level="warn">
      <AppenderRef ref="databaseAppender"/>
    </Root>
  </Loggers>
</Configuration>

工厂方法demo

package net.example.db;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp.DriverManagerConnectionFactory;
import org.apache.commons.dbcp.PoolableConnection;
import org.apache.commons.dbcp.PoolableConnectionFactory;
import org.apache.commons.dbcp.PoolingDataSource;
import org.apache.commons.pool.impl.GenericObjectPool;
public class ConnectionFactory {
    private static interface Singleton {
        final ConnectionFactory INSTANCE = new ConnectionFactory();
    }
    private final DataSource dataSource;
    private ConnectionFactory() {
    Properties properties = new Properties();
    properties.setProperty("user", "logging");
    properties.setProperty("password", "abc123"); // or get properties from some configuration file
    GenericObjectPool<PoolableConnection> pool = new GenericObjectPool<PoolableConnection>();
    DriverManagerConnectionFactory connectionFactory = new DriverManagerConnectionFactory(
            "jdbc:mysql://example.org:3306/exampleDb", properties
    );
    new PoolableConnectionFactory(
            connectionFactory, pool, null, "SELECT 1", 3, false, false, Connection.TRANSACTION_READ_COMMITTED
    );
    this.dataSource = new PoolingDataSource(pool);
}
    public static Connection getDatabaseConnection() throws SQLException {
        return Singleton.INSTANCE.dataSource.getConnection();
    }
}

#KafkaAppender

KafkaAppender 记录日志信息并推送至Kafka的指定topic中, 每条日志信息是以一条无key的Kafka记录方式推送的。 ###配置示例

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
  <Appenders>
    <Kafka name="Kafka" topic="log-test">
      <PatternLayout pattern="%date %message"/>
        <Property name="bootstrap.servers">localhost:9092</Property>
    </Kafka>
  </Appenders>
    <Loggers>
    <Root level="DEBUG">
      <AppenderRef ref="Kafka"/>
    </Root>
    <Logger name="org.apache.kafka" level="INFO" /> <!-- avoid recursive logging -->
  </Loggers>
</Configuration>

#RollingFileAppender

RollingFileAppender是一种OutputStreamAppender,它可以写日志信息至指定名字的日志文件中并根据触发策略(TriggeringPolicy)以及覆盖策略(RolloverStrategy)来处理日志文件。触发策略决定覆盖操作是否执行,而覆盖策略是决定如何进行覆盖操作;若覆盖策略(RollingFileAppender)没有配置,则会采用默覆盖策略。 ###配置示例 采用TimeBasedTriggeringPolicy以及SizeBasedTriggeringPolicy,同时日志文件压缩为gz格式:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
  <Appenders>
    <RollingFile name="RollingFile" fileName="logs/app.log"
             filePattern="logs/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
      <PatternLayout>
        <Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
      </PatternLayout>
      <Policies>
        <TimeBasedTriggeringPolicy />
        <SizeBasedTriggeringPolicy size="250 MB"/>
      </Policies>
    </RollingFile>
  </Appenders>
  <Loggers>
       <Root level="error">
      <AppenderRef ref="RollingFile"/>
    </Root>
  </Loggers>
</Configuration>

当日志文件数量达到20后,进行日志覆盖:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
    <Appenders>
        <RollingFile name="RollingFile" fileName="logs/app.log"
             filePattern="logs/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
            <PatternLayout>
            <Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
            </PatternLayout>
            <Policies>
                <TimeBasedTriggeringPolicy />
                <SizeBasedTriggeringPolicy size="250 MB"/>
            </Policies>
          <DefaultRolloverStrategy max="20"/>
    </RollingFile>
  </Appenders>
  <Loggers>
    <Root level="error">
      <AppenderRef ref="RollingFile"/>
    </Root>
  </Loggers>
</Configuration>

覆盖策略具体例子: 每天凌晨12点触发操作,删除根目录下匹配app-*.log.gz名字并时间超过60天的日志文件.

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
  <Properties>
    <Property name="baseDir">logs</Property>
  </Properties>
  <Appenders>
    <RollingFile name="RollingFile" fileName="${baseDir}/app.log"
      filePattern="${baseDir}/$${date:yyyy-MM}/app-%d{yyyy-MM-dd}.log.gz">
      <PatternLayout pattern="%d %p %c{1.} [%t] %m%n" />
      <CronTriggeringPolicy schedule="0 0 0 * * ?"/>
      <DefaultRolloverStrategy>
        <Delete basePath="${baseDir}" maxDepth="2">
          <IfFileName glob="*/app-*.log.gz" />
          <IfLastModified age="60d" />
        </Delete>
      </DefaultRolloverStrategy>
    </RollingFile>
 </Appenders>
  <Loggers>
    <Root level="error">
      <AppenderRef ref="RollingFile"/>
    </Root>
  </Loggers>
</Configuration>

#SMTPAppender

平常大家肯定有希望系统报严重错误或者异常(errors or fatal errors)的时候,以邮件的形式通知我们,这个SMTPAppender就可以胜任这样的工作,让我们及时发现损害系统的眼中严重错误,并及时的处理解决。配置中BufferSize 选项是用来表示这封邮件内容中发送的最大日志信息数量。 ###配置示例 简单典型的SMTPAppender配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
  <Appenders>
    <SMTP name="Mail" subject="Error Log" to="errors@logging.apache.org" from="test@logging.apache.org"
      smtpHost="localhost" smtpPort="25" bufferSize="50">
    </SMTP>
  </Appenders>
  <Loggers>
    <Root level="error">
      <AppenderRef ref="Mail"/>
    </Root>
  </Loggers>
</Configuration>
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!