一文带你彻底掌握Log4j2

一:Log4j2简介

    Apache Log4j 2 是对Log4j的升级,它比其前身Log4j 1.x提供了重大改进,并参考了Logback中优秀的设计,同时修复了Logback架构中的一些问题。被誉为是目前最优秀的Java日志框架;企业中通常使用SLF4j门面+Log4j2来记录日志

异常处理:在logback中,Appender中的异常不会被应用感知到,但是在log4j2中,提供了一些异常处理机制。
性能提升:log4j2相较于log4j和logback都具有很明显的性能提升,在多线程方案中,异步记录器的吞吐量比Log4j 1.x和Logback高10倍
自动重载配置:参考了logback的设计,当然会提供自动刷新参数配置,最实用的就是我们在生产上可以动态的修改日志的级别而不需要重启应用
无垃圾机制:log4j2在大部分情况下,都可以使用其设计的一套无垃圾机制,避免频繁的日志收集导致的jvm gc。
支持自定义日志级别:可以在代码或配置中定义自定义日志级别。

  注:日志级别请参考上面的Log4j日志级别,因为级别一样

二:Log4j2入门案例

  目前市面上最主流的日志门面就是SLF4J,虽然Log4j2也是日志门面,但是它的日志实现功能非常强大,性能优越。所以大家一般还是将Log4j2看作是日志的实现,Slf4j + Log4j2应该是未来的大势所趋。不过在学习中我将使用纯Log4j2,后面介绍SLF4J + Log4j2

<!--Log4j2自带的日志门面-->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.17.1</version>
</dependency>
<!--Log4j2具体的日志实现-->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.17.1</version>
</dependency>
@Test
    public void demoA() {
        //import org.apache.logging.log4j.LogManager;  Log4j2的日志管理器
        //import org.apache.logging.log4j.Logger;      Log4j2的日志记录器
        //通过日志管理器获取Logger对象
        Logger logger = LogManager.getLogger(Log4j2Demo.class);
        //打印日志
        logger.fatal(" 严重错误,一般造成系统崩溃并终止运行");
        logger.error(" 错误信息,不会影响系统运行");         //默认级别
        logger.warn(" 警告信息,可能会发生问题");
        logger.info(" 运行信息,数据连接,网络连接,IO操作等");
        logger.debug(" 调试信息,一般在开发中使用,记录程序变量传递信息等等");
        logger.trace(" 追踪信息,记录程序所有的流程信息");
    }
    
    //打印记录
    //11:12:55.378 [main] FATAL cn.xw.Log4j2Demo -  严重错误,一般造成系统崩溃并终止运行
    //11:12:55.384 [main] ERROR cn.xw.Log4j2Demo -  错误信息,不会影响系统运行

  从上面案例来看,即使不指定Log4j2的配置文件也可以进行日志的打印(这一点就比Log4j 1.x好),带有默认的日志配置;

  下面我将带大家详细说明配置文件,因为它才是重点,以上面的入门案例的Java来进行测试!!

三:配置文件类型及加载顺序

Log4j2配置文件的四种方式:
    ①:通过XML、JSON、YAML或者properties格式的配置文件;
    ②:通过创建一个 ConfigurationFactory 和 Configuration 接口的实现
    ③:调用 Configuration 接口暴露的方法来在默认配置的基础上添加其他组件
    ④:通过在内部 Logger 类上调用方法

Log4j2包含4种 ConfigurationFactory 的实现,分别适用于JSON、YAML、properties和XML配置文件。
在Log4j2启动时可以按照以下顺序自动加载配置文件:
①:查找log4j.configurationFile系统属性所指定的配置文件名,若该系统属性值存在,就尝试使用相应文件扩展名的ConfigurationFactory
    来加载指定的配置文件。通过在代码中调用 System.setProperties("log4j.configurationFile","FILE_PATH") 或者
    将 -Dlog4jconfigurationFile=file://C:/configuration.xml参数传递给JVM
②:若没找到,则properties ConfigurationFactory就在classpath中寻找 log4j2-test.properties 配置文件
③:若没找到,则YAML ConfigurationFactory就在 classpath 中寻找log4j2-test.yaml 或 log4j2-test.yml配置文件
④:若没找到,则JSON ConfigurationFactory就在 classpath 中寻找 log4j2-test.json 或 log4j2-test.json 配置文件
⑤:若没找到,则XML ConfigurationFactory 就在 classpath 中寻找log4j2-test.xml配置文件
⑥:若没找到测试配置文件,则properties ConfigurationFactory就在 classpath 中寻找log4j2.properties配置文件
⑦:若没找到,则YAML ConfigurationFactory就在 classpath 中寻找 log4j2.yaml 或 log4j2.yml 配置文件
⑧:若没找到,则JSON ConfigurationFactory就在 classpath 中寻找 log4j2.json 或 log4j2.jsn 配置文件
⑨:若没找到,则XML ConfigurationFactory 就在 classpath 中寻找 log4j2.xml 配置文件 (生产环境常用)
⑩:如果上面的配置文件都没有找到,就使用默认的 DefaultConfiguration 配置

四:创建控制台及文件输出配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!--monitorInterval属性值(秒数)为一个非零值来让Log4j每隔指定的秒数来重新读取配置文件,可以用来动态应用Log4j配置-->
<Configuration status="debug" monitorInterval="30">
    <!--用来自定义一些变量-->
    <Properties>
        <!--变量定义-->
        <Property name="myPattern" value="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        <Property name="dir_url">d:/logs</Property>
    </Properties>
    <!--使用Appenders元素可以将日志事件数据写到各种目标位置-->
    <Appenders>
        <!-- 默认打印到控制台 -->
        <Console name="ConsoleAppend" target="SYSTEM_OUT">
            <!-- 默认打印格式 -->
            <PatternLayout pattern="${myPattern}"/>
        </Console>
        <!-- 打印到日志文件上 -->
        <File name="FileAppend" fileName="${dir_url}/fileLog.log" bufferedIO="true" immediateFlush="true">
            <PatternLayout>
                <pattern>${myPattern}</pattern>
            </PatternLayout>
        </File>
    </Appenders>
    <!--定义logger,只有定义了logger并引入的appender,appender才会生效-->
    <Loggers>
        <!-- 默认打印日志级别为 error -->
        <Root level="INFO">
            <AppenderRef ref="ConsoleAppend"/>
            <AppenderRef ref="FileAppend"/>
        </Root>
    </Loggers>
</Configuration>

五:Configuration元素配置

Configuration元素可以使用以下属性:
advertiser:
    可选的Advertiser插件名,用来通知个别FileAppender或SocketAppender的配置。目前唯一可用Advertiser名为"multicastdns"
dest:
    err(将输出到stderr上)或一个文件路径或一个URL;
monitorInterval:
    检查配置文件是否有更新的间隔秒数;
name:
    配置的名称;
packages:
    逗号分隔的用于搜索插件的包名列表。插件只会被每个类加载器加载一次,所以仅重新配置该项不会生效;
schema:
    为类加载器定位XML Schema位置以验证配置。仅当strict属性设置为true时该属性才有效,如果不设置该属性,则不会验证Schema
shutdownHook:
    设置当 JVM 关闭时 log4j 是否也自动关闭。默认是启用的,也可以设置该属性为 disable 来禁用该关闭钩子;
shutdownTimeout:
    设置当 JVM 关闭后 Appender 和后台任务超时多少毫秒才关闭。默认为0,表示每个Appender使用其默认的超时,不等待后台任务。
    这仅是个提示,而不能保证关闭进程不会花费更长的时间。将该值设置过小可能增加日志事件在还未输出到最终位置之前就丢失的风险。
    如果 shutdownHook 属性未设置,那么将不会使用该属性;
status:
    应该打印到控制台的内部 Log4j 日志事件的级别,可设置的值有 trace、debug、info、warn、error 和 fatal,Log4j 将会打
    印出内部初始化等事件的详细信息(在发现配置文件之后)。设置该属性为 trace 是查找 Log4j 故障的第一手工具。也可以通过设
    置 log4j2.debug 系统属性来输出 Log4j 内部日志,包括配置文件加载前的内部日志(从 log4j 2.9 开始)
strict:
    使用严格的 XML 格式, JSON 格式的配置文件不支持该属性
verbose:
    加载插件时是否显示诊断信息

六:Appenders元素配置

  Log4j使用Appender将日志事件数据写到各种目标位置(目前可以为控制台文件多种数据库 API、远程套接字服务器、Apache Flume、JMS、远程 UNIX Syslog daemon)。

  在大多数情况下,Appender将格式化事件的责任委托给Layout。一些Appender包装其它Appender,以便它们可以修改LogEvent、处理Appender中的失败、根据高级筛选条件将事件路由到下级Appender,或者提供不直接格式化事件的类似功能。

  Appender可以通过特定的Appender插件名或appender元素(带有指定Appender插件名的type属性)来配置。另外每个Appender都必须要有一个name属性,用来指定一个去别区其它Appender的唯一标识,该标识的值在Logger中通过AppenderRef来引用,从而将该Appender配置到该 Logger 中。

  每个Appender都必须实现Appender接口,多数Appender都扩展自AbstractAppender,该抽象类添加了Lifecycle和Filterable支持。Lifecycle可以对日志组件在配置加载后进行初始化和在关闭时进行清理等操作。Filterable可以让日志组件绑定用于处理日志事件的过滤器。代表不同目标位置的各种Appender也具有其功能所需的其他属性和子元素。这里选择常用的Appender加以介绍。

1:ConsoleAppender控制台输出

  ConsoleAppender会将输出写入System.out(默认目标位置)或 System.err 中。必须提供一个 Layout 来格式化LogEvent 

参数名(类型):介绍说明
filter(Filter):
    指定一个过滤器来决定是否将日志事件传递给Appender处理。可以指定为一个CompositeFilter来使用多个过滤器。
layout(Layout):
    指定格式化LogEvent的Layout。如果没有指定Layout,则默认使用 %m%n 格式。
follow(boolean):
    在配置好之后,是否可以通过System.setOut或System.setErr来重新指定输出位置为System.out或System.err。
    不能在 Windows 下用于Jansi,也不能和direct属性一起使用。
direct(boolean):
    绕开java.lang.System.out/.err直接写入java.io.FileDescriptor。当输出重定向到文件或其他目标时可以
    节约10倍的性能消耗。不能在Windows下用于 Jansi,也性不能和 follow 属性一起使用。在多线程应用中,
    输出不会遵循 java.lang.System.setOut()/.setErr() ,而是可能会和其他输出纠缠在一起输出到
    java.lang.System.out/.err。该属性是 2.6.2 版本新增的,目前仅在Linux和Windows下的Oracle JVM环境中测试过。
name(String):
    必选的 Appender 的名称,表示区别于其他Appender的唯一标识。
ignoreExceptions(boolean):
    默认为 true,表示当输出事件时出现的异常将会被内部记录而忽略。 当设置为 false 时,则会将异常传播给调用者。
    当将该Appender包装成FailoverAppender时,必须设置为 false。
target(String):
    SYSTEM_OUT 或 SYSTEM_ERR。默认为 SYSTEM_OUT。
<?xml version="1.0" encoding="UTF-8" ?>
<Configuration status="error" monitorInterval="30">
    <Properties>
        <Property name="myPattern" value="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
    </Properties>
    <Appenders>
        <Console name="ConsoleAppend" target="SYSTEM_ERR">
            <PatternLayout pattern="${myPattern}"/>
        </Console>
    </Appenders>
    <Loggers>
        <Root level="WARN">
            <AppenderRef ref="ConsoleAppend"/>
        </Root>
    </Loggers>
</Configuration>
<!--日志输出-->
16:42:41.556 [main] FATAL cn.xw.Log4j2Demo -  严重错误,一般造成系统崩溃并终止运行
16:42:41.559 [main] ERROR cn.xw.Log4j2Demo -  错误信息,不会影响系统运行
16:42:41.559 [main] WARN  cn.xw.Log4j2Demo -  警告信息,可能会发生问题

2:RollingFileAppender文件输出

  RollingFileAppender会将输出到fileName参数指定的文件中,且需要指定 TriggeringPolicyRolloverStrategy。其中TriggeringPolicy决定是否生成新的日志文件,RolloverStrategy决定如何生成新的日志文件。如果没有配置RolloverStrategy,则会使用DefaultRolloverStrategy。从Log4j 2.5开始,可以在DefaultRolloverStrategy中配置一个自定义的删除动作。从 Log4j2.8开始,如果没有指定文件名,则会使用DirectWriteRolloverStrategy来代替DefaultRolloverStrategy。从Log4j 2.9开始,可以在DefaultRolloverStrategy中配置一个自定义的POSIX文件属性查看动作,如果没有定义该动作,则将会使用从RollingFileAppender继承的POSIX。RollingFileAppender不支持文件锁。

参数名(类型):介绍说明
append(boolean):
    当为 true(默认)时,日志记录将会添加到文件末尾。设置为false时,日志记录写入文件之前会清空文件。
bufferedIO(boolean):
    当为 true(默认)时,日志记录将会写入一个缓冲区,当缓冲区满或设置了immediateFlush,日志记录才会写入磁盘中。
    该属性不能使用文件锁。缓冲 I/O 能够显著提高性能,即使启用了immediateFlush。
immediateFlush(boolean):
    当为 true(默认)时,每次写入都会跟随一次flush 。这将保证数据写入磁盘,但会影响性能。仅当使用异步Logger的Appender
    时设置每次写入都进行 flush 才有用。异步的 Logger 和 Appender 会在一批次的日志事件末尾自动 flush ,即使该属性设置
    为 false ,这种方式也可以保证数据写入磁盘,但是更有效率。
bufferSize(int):
    当bufferedIO为true时,该属性用来设置缓冲区的大小,默认为8192字节。
createOnDemand(boolean):
    Appender是按需(on-demand)创建文件的。仅当日志事件通过所有过滤器并到达Appender时,该Appender才会创建文件。默认为false。
filter(Filter):
    指定一个过滤器来决定是否将日志事件传递给Appender处理。可以指定为一个CompositeFilter来使用多个过滤器。
fileName(String):
    设置写入日志记录的文件名。如果该文件或其父目录不存在,则会自动创建。
filePattern(String):
    归档日志文件的文件名模式(pattern)。该模式依赖于所用的 RolloverPolicy 。DefaultRolloverPolicy可以接受兼容
    SimpleDateFormat的日期/时间或表示一个整数的计数器的 %i 。该模式也支持运行时插入值(interpolation),故任何
    Lookup(比如 DateLookup)都可以包含该模式。
layout(Layout):
    指定格式化 LogEvent 的 Layout 。如果没有指定 Layout ,则默认使用 %m%n 格式。
name(String):
    必选的 Appender 的名称,表示区别于其他 Appender 的唯一标识。
policy(TriggeringPolicy):
    设置确定是否创建文件的规则(policy)。
strategy(RolloverStrategy):
    设置确定归档文件的文件名和位置的策略(strategy)。
ignoreExceptions(boolean):
    默认为true ,表示当输出事件时出现的异常将会被内部记录而忽略。 当设置为false时,则会将异常传播给调用者。当将该Appender
    包装成 FailoverAppender 时,必须设置为 false。
filePermissions(String):
    设置每当创建文件时所用的 POSIX 格式的文件属性权限。底层文件系统应该支持 POSIX 格式的文件属性视图。
    比如: rw------- 或 rw-rw-rw- 等。
fileOwner(String):
    指定每次创建文件的属主。由于权限原因,可能不允许更改文件属主,这时会抛出 IOException 。仅当有效的目标用户 ID 和文件
    的用户 ID 相同,或目标用户 ID 具有修改文件属主的权限(如果 _POSIX_CHOWN_RESTRICTED 在日志文件路径下有效)时才会
    处理。底层文件系统应该支持 owner 文件属性视图。
fileGroup(String):
    指定每次创建文件的属组。底层文件系统应该支持 POSIX 格式的文件属性视图。

触发规则

组合触发规则
    CompositeTriggeringPolicy组合了多个触发规则(policy),如果配置的任意规则返回true时,
    则CompositeTriggeringPolicy也返回true,也发生了触发。CompositeTriggeringPolicy通过将其规则
    包装进Policies元素即可配置。
    下面XML片段定义了一个日志滚动规则:当JVM启动时、日志文件大小达到20MB且当前日期不再匹配日志开始日期时就滚动日志
<!-- 打印到日志文件上并拆分归档 -->
<RollingFile name="rollingFile" fileName="${dir_url}/rollFileLog.log"
             filePattern="${dir_url}/$${date:yyyy_MM_dd}/appLog_%d{yyyy_MM_dd_HH_mm_ss}_%i.log.gz">
    <PatternLayout pattern="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n"/>
    <Policies>
        <OnStartupTriggeringPolicy />
        <SizeBasedTriggeringPolicy size="2MB"/>
        <TimeBasedTriggeringPolicy/>
    </Policies>
    <!--当前日志文件夹下的归档的文件个数,此时为2个文件,超出则覆盖之前文件,默认7-->
    <DefaultRolloverStrategy max="4"/>
</RollingFile>

①:启动触发规则:
    OnStartupTriggeringPolicy在日志文件比当前JVM启动时间较早时进行日志滚动,同时遵循下面的minSize属性规则。
    OnStartupTriggeringPolicy属性如下表所示:
    minSize(类型long):
        日志文件滚动的最小字节。大小为0表示不管文件大小为多少字节都滚动。默认值1将阻止对空文件进行滚动。
②:基于文件大小的触发规则:
    SizeBasedTriggeringPolicy在文件字节达到指定的大小时进行日志滚动。文件大小可以使用KB、MB、GB等后缀
③:基于时间的触发规则:
    TimeBasedTriggeringPolicy 只要日期/时间模式(pattern)不再应用于当前文件时就进行日志滚动。
    这种规则通过interval和modulate属性来配置。
    TimeBasedTriggeringPolicy属性如下表所示:
    interval(类型integer):
        基于日期/时间模式中的最小的时间单位多久滚动一次。例如,filePattern 参数中使用使用小时作为最小的时间单位时
        (比如 /appData/logs/myApp/$${date:yyyy-MM-dd}/myApp-%d{yyyy-MM-dd HH}-%i.log),该参数值为4,
        则表示每 4 小时滚动一次。默认值为1 。
    modulate(类型boolean):
        是否调整 interval 属性值,以便下次滚动发生在 interval 边界处。
        例如,如果时间单位为小时,当前时间为早上3点,间隔为4小时,则第一次滚动将发生在早上4点时(而不是早上7点),
        后续滚动将发生在 早上 8 点、中午 12 点、下午 4 点等时刻。
    maxRandomDelay(类型integer):
        滚动操作随机延迟的最长秒数。默认0表示无延迟。该设置在有多个应用同时滚动日志的服务器上很有用,可以扩宽滚动日
        志的的负载时间范围,避免某一个时刻由于滚动日志造成高 I/O 压力。
④:Cron触发规则:
    CronTriggeringPolicy 基于 cron 表达式来滚动日志。
    CronTriggeringPolicy 属性如下表所示:
    schedule(类型String):
        和 Quartz 调度器一样的 cron 表达式。参考 CronExpression 来查看关于该表达式的详细描述。
    evaluateOnStartup(类型boolean):
        在启动时,该 cron 表达式将基于文件的上次修改时间戳来求值。

滚动策略

默认滚动规则
    DefaultRolloverStrategy使用一种基于时间和固定窗口(fixed window,窗口在这里的意思大致是对数量的限制,
    参考TCP中的Window Scale概念)的组合策略。如果配置了时间模式,那么将会使用时间间隔来计算用于filePattern
    参数值的时间。如果文件模式还包含了一个整数替换符,那么在时间模式的匹配结果改变之前,该整数值将会在每次滚动时递增。
    以.gz、.zip、.bz2、deflate、pack200或xz,则最终归档文件将以后缀对应的格式进行压缩。bzip2、Deflate、Pack200
    和XZ需要 Apache Commons compress。另外,XZ 需要 XZ for Java。
    DefaultRolloverStrategy 参数如下表所示:
    fileIndex(类型String):
        如果设置为 max(默认),则具有更大索引的文件比具有更小索引的文件内容更新。
        如果设置为 min,文件将重命名且计数器将遵循前面介绍的 Fixed Window 策略。
    min(类型Integer):
        计数器的最小值,默认为 1。
    max(类型Integer):
        计数器的最大值。一旦计数器达到了最大值,最早的归档将会在每次滚动时被删除。默认值为 7。
    compressionLevel(类型Integer):
        设置压缩级别,0 - 9。0 = none,1 = best speed,9 = best compression。仅应用于 ZIP 文件。
    tempCompressedFilePattern(类型String):
        压缩时归档文件的文件名模式。

默认的滚动策略支持三种递增计数器。为了演示 Fixed Window 策略是如何工作的,这里假定 min 属性设置为 1,
max 属性设置为 3,文件名为foo.log,文件名模式为foo-%i.log
0~4:滚动数
foo.log:当前目标文件
foo-1.log, foo-2.log, foo-3.log 归档文件
0    foo.log     -
    所有日志都写入初始文件。
1    foo.log     foo-1.log
    第一次滚动时,foo.log文件重命名为 foo-1.log,创建一个新的 foo.log 文件来开始写入日志
2    foo.log     foo-1.log, foo-2.log
    第二次滚动时,foo-1.log文件重命名为 foo-2.log,foo.log文件重命名为foo-1.log,创建一个新的foo.log文件来开始写入日志
3    foo.log     foo-1.log, foo-2.log, foo-3.log
    第三次滚动时,foo-2.log文件重命名为foo-3.log,foo-1.log文件重命名为foo-2.log,创建一个新的foo.log文件来开始写入日志
4    foo.log     foo-1.log, foo-2.log, foo-3.log
    在第四次以及后续的滚动时,foo-3.log 会被删除,foo-2.log重命名为 foo-3.log,foo-1.log 重命名为 foo-2.log,
    创建一个新的 foo.log 文件来开始写入日志。

作为对比,fileIndex属性设置为 max,而所有其他设置和上面都一样。
0    foo.log     -
    所有日志都写入初始文件。
1    foo.log     foo-1.log
    第一次滚动时,foo.log 文件重命名为 foo-1.log,创建一个新的 foo.log 文件来开始写入日志。
2    foo.log     foo-1.log, foo-2.log
    第二次滚动时,foo.log 文件重命名为 foo-2.log,创建一个新的 foo.log 文件来开始写入日志。
3    foo.log     foo-1.log, foo-2.log, foo-3.log
    第二次滚动时,foo.log 文件重命名为 foo-3.log,创建一个新的 foo.log 文件来开始写入日志。
4    foo.log     foo-1.log, foo-2.log, foo-3.log
    在第四次以及后续的滚动时,foo-1.log 会被删除,foo-2.log 重命名为 foo-1.log,foo-3.log
    重命名为foo-2.log,创建一个新的foo.log文件来开始写入日志。可以将max属性设置为一个很大的值来避免重名批量文件。
上面两种方式都有可能导致批量重命名日志文件,但自 2.8 版本开始,如果 fileIndex 属性设置为 nomax,则 min 和 max
属性值都将会被忽略,文件编号将每次递增 1,每次滚动都会递增到更大的值,且没有最大文件编号的限制。


直接滚动规则
    DirectWriteRolloverStrategy将日志事件直接写入filePattern参数值表示的文件。该策略不进行文件重命名。如果基于大小
    的触发规则要在特定时间段内写入多个文件,这些文件编号将从1开始持续递增,直到出现基于时间的滚动。
    警告:如果 filePattern 参数值中有一个表示压缩格式的后缀以进行文件压缩,当应用关闭时当前文件并不会进行压缩。此外,
    如果由于时间改变造成 filePattern 不在匹配当前文件了,那么当前文件在下次启动时也不会进行压缩。
    DirectWriteRolloverStrategy属性如下表所示:
    maxFiles(String):
        匹配 filePattern 期间所允许的最大文件数。如果文件数超过了该属性值,则最早文件编号的文件将会删除。如果指
        定了该属性值,那么必须要大于 1。如果该属性值小于 0 或省略掉了,则将不会再限制文件编号。
    compressionLevel(Integer):
        设置压缩级别,0 - 9。0 = none,1 = best speed,9 = best compression。仅应用于 ZIP 文件
    tempCompressedFilePattern(String):
        压缩时归档文件的文件名模式

测试:
    下面的 RollingFileAppender 配置同时指定了基于时间和大小的触发规则,将会一天最多创建7个归档文件
    (未配置 DefaultRolloverStrategy 时,其max属性默认为7)存放在当前年月目录下,每个归档文件使用gzip进行压缩:
<?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>
下面的 RollingFileAppender 配置同时指定了基于时间和大小的触发规则,将会一天最多创建 7 个归档文件,存放在当前年月目录下,
每个归档文件使用 gzip 进行压缩,每 6 个小时滚动一次(小时数能被 6 整除):
<?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{yyyy-MM-dd-HH}-%i.log.gz">
      <PatternLayout>
        <Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
      </PatternLayout>
      <Policies>
        <TimeBasedTriggeringPolicy interval="6" modulate="true"/>
        <SizeBasedTriggeringPolicy size="250 MB"/>
      </Policies>
    </RollingFile>
  </Appenders>
  <Loggers>
    <Root level="error">
      <AppenderRef ref="RollingFile"/>
    </Root>
  </Loggers>
</Configuration>
下面的 RollingFileAppender 同时使用了基于 cron 表达式和大小的触发规则,直接将日志以编号无限制的方式写入的归档文件中。
cron 触发器会每小时滚动一次,而每个日志文件的大小限制在 250 MB,超过此大小就会滚动(新文件的编号递增):
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
  <Appenders>
    <RollingFile name="RollingFile" filePattern="logs/app-%d{yyyy-MM-dd-HH}-%i.log.gz">
      <PatternLayout>
        <Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
      </PatternLayout>
      <Policies>
        <CronTriggeringPolicy schedule="0 0 * * * ?"/>
        <SizeBasedTriggeringPolicy size="250 MB"/>
      </Policies>
    </RollingFile>
  </Appenders>
  <Loggers>
    <Root level="error">
      <AppenderRef ref="RollingFile"/>
    </Root>
  </Loggers>
</Configuration>
下面的配置和上面的基本一样,但是限制每小时里文件编号最大为 10:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
  <Appenders>
    <RollingFile name="RollingFile" filePattern="logs/app-%d{yyyy-MM-dd-HH}-%i.log.gz">
      <PatternLayout>
        <Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
      </PatternLayout>
      <Policies>
        <CronTriggeringPolicy schedule="0 0 * * * ?"/>
        <SizeBasedTriggeringPolicy size="250 MB"/>
      </Policies>
      <DirectWriteRolloverStrategy maxFiles="10"/>
    </RollingFile>
  </Appenders>
  <Loggers>
    <Root level="error">
      <AppenderRef ref="RollingFile"/>
    </Root>
  </Loggers>
</Configuration>


日志归档保留规则:滚动时删除
Log4j 2.5 引入了删除动作(Delete action)。在滚动删除旧的日志文件时,相比使用 DefaultRolloverStrategy 的 max 属性,
该功能可以让用户拥有更多的删除控制。删除动作可以让用户配置若干个条件来删除相对于基准目录的文件。
注意:该功能可以删除非日志文件,使用时一定要小心。可以通过 testMode 属性来测试配置是否会错删文件。
删除属性如下表所示:
    basePath(String)
        指定扫描要删除文件的基准目录。必选。
    maxDepth(int)
        指定扫描的目录的最大层级。0 值表示仅能访问基准目录(安全限制不能访问的情况除外)。Integer.MAX_VALUE 值表示
        可以访问所有层级。默认值为 1,表示仅扫描基准目录下的文件。
    followLinks(boolean)
        设置是否跟随符号链接。默认为 false。
    testMode(boolean)
        如果设置为 true,文件不会实际删除,而是在 status logger 打印一条 INFO 级别的消息。可以使用该功能来测试是
        否会错删文件。默认为 false。
    pathSorter(PathSorter)
        设置一个实现了 PathSorter 接口的插件来在选择删除文件之前排列文件。默认优先排列最近改动的文件。
    pathConditions(PathCondition[])
        如果没有指定 ScriptCondition 则必须指定一个或多个 PathCondition 元素。如果指定了不止一个条件,则这些条件
        都需要在删除之前接受某个路径。这些条件可以嵌套,只有外部条件接受某个路径之后,其内部条件才会决定是否接受该路径。
        如果这些条件没有嵌套,则它们的执行顺序是任意的。这些条件也可以通过使用 IfAll, IfAny 和 IfNot 等组合条件进行
        AND、OR 和 NOT 等逻辑运算。用户也可以创建自定义条件或使用内置条件:IfFileName (接受匹配正则表达式或glob的文
        件路径),IfLastModified(接受比指定时段早或一样早的文件),IfAccumulatedFileCount (在遍历文件树时文件总
        数超过文件数上限后接受路径),IfAccumulatedFileSize (在遍历文件树时文件总大小超过上限后接受路径),IfAll(
        如果所有内嵌条件都接受了某个路径才会接受该路径,相当于 AND 逻辑。内嵌条件的执行顺序是任意的),IfAny(如果任意
        一个内嵌条件接受了某个目录就接受该目录,相当于 OR 逻辑。内嵌条件的执行顺序是任意的,IfNot(如果内嵌条件不接受某
        个路径就接收该路径,相当于 NOT 逻辑)。
    scriptCondition(ScriptCondition)
        如果没有指定 PathConditions 则必须指定该属性。ScriptCondition 元素应该通过包含 Script, ScriptRef 或
        ScriptFile 元素来指定一个脚本
IfFileName 条件属性如下表所示:
    glob(String)
        如果 regex 属性没有指定则必须指定该属性。使用受限的模式语言(类似于正则表达式但语法更简单)来匹配相对路径(基于基准路径)。
    regex(String)
        如果 glob 属性没有指定则必须指定该属性。使用 Pattern 类定义的正则表达式来匹配相对路径(基于基准路径)。
    nestedConditions(PathCondition[])
        可选的内嵌 PathCondition 结合。
IfLastModified 属性如下表所示:
    age(String)
        必须指定一个时段。该条件接受比指定时段早或一样早的文件。
    nestedConditions(PathCondition[])
        可选的内嵌 PathCondition 结合。
IfAccumulatedFileCount 属性如下表所示:
    exceeds(int)
        必须指定一个文件总数上限值。如果文件数超过了该上限值则删除文件。
    nestedConditions(PathCondition[])
        可选的内嵌 PathCondition 结合。
IfAccumulatedFileSize 属性如下表所示:
    exceeds(String)
        必须指定一个文件总大小上限值。如果文件累计总大小超过了该上限值则删除文件。该值可以通过 KB、MB 或 GB 等后缀来指定,例如 50GB。
    nestedConditions(PathCondition[])
        可选的内嵌 PathCondition 结合。

下面配置中的 RollingFileAppender 使用 cron 触发规则,每天晚上触发一次。归档文件存放在当前年月目录。所有位于基准目录下匹配
 */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>

下面配置中的 RollingFileAppender 使用基于时间和文件大小的触发规则,每天最多生成 100 个归档日志文件,使用 gzip 压缩,
存放在当前年月目录,每小时都会滚动。每次滚动都会删除匹配 */app-*.log.gz 、达到或超过 30 天的文件,但会保留最近 100 GB
的文件或最近的 1000 个文件(这两个条件哪个先满足就使用哪一个条件)。
<?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-HH}-%i.log.gz">
      <PatternLayout pattern="%d %p %c{1.} [%t] %m%n" />
      <Policies>
        <TimeBasedTriggeringPolicy />
        <SizeBasedTriggeringPolicy size="250 MB"/>
      </Policies>
      <DefaultRolloverStrategy max="100">
        <!--
        Nested conditions: the inner condition is only evaluated on files
        for which the outer conditions are true.
        -->
        <Delete basePath="${baseDir}" maxDepth="2">
          <IfFileName glob="*/app-*.log.gz">
            <IfLastModified age="30d">
              <IfAny>
                <IfAccumulatedFileSize exceeds="100 GB" />
                <IfAccumulatedFileCount exceeds="1000" />
              </IfAny>
            </IfLastModified>
          </IfFileName>
        </Delete>
      </DefaultRolloverStrategy>
    </RollingFile>
  </Appenders>
  <Loggers>
    <Root level="error">
      <AppenderRef ref="RollingFile"/>
    </Root>
  </Loggers>
</Configuration>

日志归档文件属:滚动时自定义文件属性
Log4j 2.9 引入了一个 PosixViewAttribute 动作,用户可以更好地控制选择文件的属主、属组、权限等属性中的哪一个来作
为滚动依据。PosixViewAttribute 动作让用户配置一个或多个条件来筛选相对于基准目录的文件
    basePath(String)
        必选的用于扫描文件的基本目录。
    maxDepth(int)
        指定扫描的目录的最大层级。0 值表示仅能访问基准目录(安全限制不能访问的情况除外)。Integer.MAX_VALUE
        值表示可以访问所有层级。默认值为 1,表示仅扫描基准目录下的文件。
    followLinks(boolean)
        设置是否跟随符号链接。默认为 false。
    pathConditions(PathCondition[])
        参考 DeletePathCondition 。
    filePermissions(String)
        设置每当创建文件时所用的 POSIX 格式的文件属性权限。底层文件系统应该支持 POSIX 格式的文件属性视图。
        比如: rw------- 或 rw-rw-rw- 等。
    fileOwner(String)
        指定每次创建文件的属主。由于权限原因,可能不允许更改文件属主,这时会抛出 IOException 。仅当有效的目标
        用户 ID 和文件的用户 ID 相同,或目标用户 ID 具有修改文件属主的权限(如果 _POSIX_CHOWN_RESTRICTED
        在日志文件路径下有效)时才会处理。底层文件系统应该支持 owner 文件属性视图。
    fileGroup(String)
        指定每次创建文件的属组。底层文件系统应该支持 POSIX 格式的文件属性视图。

下面配置中的 RollingFileAppender 为当前和已滚动日志文件定义了不同的 POSIX 文件属性视图。
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="trace" 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{yyyyMMdd}.log.gz"
                 filePermissions="rw-------">
      <PatternLayout pattern="%d %p %c{1.} [%t] %m%n" />
      <CronTriggeringPolicy schedule="0 0 0 * * ?"/>
      <DefaultRolloverStrategy stopCustomActionsOnError="true">
        <PosixViewAttribute basePath="${baseDir}/$${date:yyyy-MM}" filePermissions="r--r--r--">
            <IfFileName glob="*.gz" />
        </PosixViewAttribute>
      </DefaultRolloverStrategy>
    </RollingFile>
  </Appenders>
  <Loggers>
    <Root level="error">
      <AppenderRef ref="RollingFile"/>
    </Root>
  </Loggers>
</Configuration>

3:RollingRandomAccessFileAppender文件输出

  RollingRandomAccessFileAppender 基本上与 RollingFileAppender 相同,只是 RollingRandomAccessFileAppender 的缓冲是不可关闭的,它使用 ByteBuffer + RandomAccessFile 来代替  BufferedOutputStream。实际测试表明,使用 RollingRandomAccessFileAppender 比使用设置了 bufferedIO=true RollingFileAppender 有提高 20-200% 的性能提升。

4:JDBCAppender数据库输出

  JDBCAppender可以使用标准的JDBC将日志事件写入关系型数据库表中。它可以通过使用JNDI数据源或自定义的工厂方法(都支持数据库连接池)来获取JDBC连接。如果配置的JDBC驱动支持批处理语句(batch statement),且bufferSize设置为一个正数,日志事件就可以分批处理。从 Log4j 2.8 开始,有两种配置日志事件的数据列映射:仅允许字符串和时间戳的原始ColumnConfig风格,和使用Log4j内置的类型转换(支持更多数据类型)的新的 ColumnMapping 插件。

JDBCAppender属性如下表所示:
    name(String)
        必选的Appender的名称。
    ignoreExceptions(boolean)
        默认为true,表示当输出事件时出现的异常将会被内部记录而忽略。 当设置为false时,则会将异常传播给调用者。
        当将该Appender包装成FailoverAppender时,必须设置为false 。
    filter(Filter)
        指定一个过滤器来决定是否将日志事件传递给Appender处理。可以指定为一个CompositeFilter来使用多个过滤器。
    bufferSize(int)
        如果设置的整数值大于0,Appender将会缓冲日志事件,当缓冲区达到指定大小时flush。
    connectionSource(ConnectionSource)
        必选。设置获取数据库连接的连接源。
    tableName(String)
        必选。设置写入日期事件的数据库表。
    columnConfigs(ColumnConfig[])
        必选(和/或 columnMappings)。通过多个Column元素来设置写入日志数据的数据列以及如何写入日志数据。
    columnMappings(ColumnMapping[])
        必选(和/或 columnConfigs)。设置数据列映射列表。每一列必须指定一个列名,每一列都可以通过指定全限定
        类名来设置转换类型。如果配置的类型是ReadOnlyStringMap/ThreadContextMap或ThreadContextStack
        赋值兼容的(assignment-compatib),那么该列将会分别使用MDC或NDC (取决于数据库如何处理 Map 或
        List 类型的值)来填充。如果配置的类型是 java.util.Date 类型赋值兼容的,那么日志时间戳将转换为配置的
        日期类型。如果配置的类型是java.sql.Clob或java.sql.NClob兼容的,那么格式化的事件将分别设置为Clob
        或NClob(与传统的 ColumnConfig 插件类似)。如果指定了literal属性,那么其值将会被用于INSERT查询
        而不会转译。否则,指定的布局或模式(layout 或 pattern)将会转换为配置的类型,存储在该列中。

注:当配置了JDBCAppender,必须指定一个ConnectionSource实现来获取JDBC连接。必须指定以下某个内嵌元素:
    <DataSource> :使用 JNDI;
    <ConnectionFactory> :指向提供 JDBC 连接的类方法对;
    <DriverManager> :不使用连接池,这是一种快速但不推荐的脏方法;
    <PoolingDriver> :使用 Apache Commons DBCP 提供连接池。

DataSource元素属性如下表所示:
    jndiName(String)
        必选。设置 javax.sql.DataSource 绑定的完整 JNDI 前缀,比如 java:/comp/env/jdbc/LoggingDatabase 。
        DataSource必须支持连接池,否则,日志记录会很慢。
ConnectionFactory元素属性如下表所示:
    class(Class)
        必选。设置一个全限定类名,该类含有一个获取 JDBC 连接的静态工厂方法。
    method(Method)
        必选。设置获取JDBC连接的静态工厂方法名。该方法不能有参数,且其返回值必须得是java.sql.Connection或DataSource。
        如果该方法返回的是数据库连接,那么数据库连接必须是连接池提供的,不然日志记录就会很慢。如果该方法返回的是数据源,
        那么该数据源只需要获取一次,出于同样的原因,该数据源也需要支持连接池。
DriverManager元素属性如下表所示:
    connectionString(String)
        必选。设置基于特定驱动的 JDBC 连接字符串。
    userName(String)
        数据库用户名。不能同时中指定 properties 属性和用户名或密码。
    password(String)
        数据库用户名。不能同时指定 properties 属性和用户名或密码。
    driverClassName(String)
        JDBC 驱动类名。某些老的 JDBC 驱动只能显式通过类名来加载。
    properties(Property[])
        属性列表。 不能同时指定 properties 属性和用户名或密码。
PoolingDriver(Apache Commons DBCP) 元素属性如下表所示:
    DriverManager(parameters)
        DriverManager parameters    连接源从 DriverManager 连接源继承来所有参数。
    poolName(String)
        JDBC连接池的名称。默认为example。可以使用JDBC连接前缀jdbc:apache:commons:dbcp:(Apache Commons DBCP)
        后面跟上连接池的名称,例如: jdbc:apache:commons:dbcp:example。
    PoolableConnectionFactory(PoolableConnectionFactory)
        元素定义一个PoolableConnectionFactory。
PoolableConnectionFactory (Apache Commons DBCP)参数列表:
    下面参数请参考:See Apache Commons DBCP PoolableConnectionFactory.
        autoCommitOnReturn(boolean)
        cacheState(boolean)
        ConnectionInitSqls(Strings)
        defaultAutoCommit(boolean)
        defaultCatalog(String)
        defaultQueryTimeoutSeconds(integer)
        defaultReadOnly(boolean)
        defaultTransactionIsolation(integer)
        disconnectionSqlCodes(Strings)
        fastFailValidation(boolean)
        maxConnLifetimeMillis(long)
        maxOpenPreparedStatements(integer)
        poolStatements(boolean)
        rollbackOnReturn(boolean)
        validationQuery(String)
        validationQueryTimeoutSeconds(integer)
当配置JDBCAppender时,使用内置的<Column>元素来指定写入数据库表中的哪些列,以及如何写入这些列。JDBCAppender使用带有
占位符的PreparedStatement 来插入 SQL 记录。
Column 元素属性如下表所示:
    name(String):
        必需。数据库表的列名。
    pattern(String):
        此属性可以通过使用PatternLayout模式在本列中插入一个或多个来自日志事件的值。只需在此属性中指定任何合法模式。
        必须指定这个属性、或 literal 或 isEventTimestamp="true",但不能多于一个。
    literal(String):
        使用该属性为该列插入一个文本值。该值将直接包含在Insert SQL语句中,不包含任何引号(这意味着如果你想将其作为字
        符串,你的值应该包含像这样的单引号:literal="'Literal String'")。 这对于不支持自动生成主键值的数据库特别
        有用。 比如,如果你使用Oracle,你可以指定literal="NAME_OF_YOUR_SEQUENCE.NEXTVAL"在主键列中插入一个唯一
        的ID。 必须指定这个属性、或 pattern 或 isEventTimestamp="true",但不能多于一个。
    parameter(String):
        使用此属性在这一列里插入一个带有 ? 参数的表达式。该值将直接包含在 Insert SQL 语句中,
        不包含任何引号(这意味着如果你想将其作为字符串,
        你的值应该包含像这样的单引号:<ColumnMapping name="instant" parameter="TIMESTAMPADD('MILLISECOND', ?,
        TIMESTAMP '1970-01-01')"/>。只能指定 literal 或 parameter 中的一个。
    isEventTimestamp(boolean):
        使用此属性将事件时间戳插入到此列中,该列应该是一个 SQL 日期时间。该值将按照java.sql.Types.TIMESTAMP类型插入。
        必须指定这个属性、或literal 或 pattern,但不能多于一个。
    isUnicode(boolean):
        除非指定了pattern ,否则该属性将被忽略。 如果 true 或省略(默认),该值将按Unicode插入
        (setNString或setNClob),否则,该值将会以非 Unicode 插入(setString 或  setClob)。
    isClob(boolean):
        除非指定了 pattern ,否则该属性将被忽略。使用该属性表明该列是否用来存储Character Large Objects(CLOBs)。
        如果设置为 true,该值将被插入为 CLOB(setClob 或 setNClob),如果设置为 false 或忽略(默认),该值将被插入
        为VARCHAR或 NVARCHAR(setString 或 setNString)。
ColumnMapping 元素属性如下表所示:
    name(String):
            必需。数据库表的列名。
    pattern(String):
            此属性可以通过使用PatternLayout模式在本列中插入一个或多个来自日志事件的值。只需在此属性中指定任何合法模式。
             必须指定这个属性、或 literal 或 isEventTimestamp="true",但不能多于一个。
    literal(String):
            使用该属性为该列插入一个文本值。该值将直接包含在Insert SQL语句中,不包含任何引号(这意味着如果你想将其作为
            字符串,你的值应该包含像这样的单引号:literal="'Literal String'")。 这对于不支持自动生成主键值的数据库
            特别有用。 比如,如果你使用 Oracle,你可以指定 literal="NAME_OF_YOUR_SEQUENCE.NEXTVAL" 在主键列中插
            入一个唯一的 ID。 必须指定这个属性、或 pattern 或 isEventTimestamp="true",但不能多于一个。
    layout(Layout):
            用来格式化 LogEvent 的 Layout。
    type(String):
            转换类型名称,是一个全限定名。

这里是一对个实例的JDBCAppender配置:
<!--JDBC往数据库存储-->
<JDBC name="databaseAppender" tableName="log">
    <!--指定获取数据库连接的静态方法 后面将给出Druid连接池工具-->
    <ConnectionFactory class="cn.xw.DruidConnectionFactory" method="getConnection"/>
    <Column name="project_name" pattern="log4j_蚂蚁小哥"/>
    <Column name="create_date" pattern="%d{yyyy-MM-dd HH:mm:ss}"/>
    <Column name="level" pattern="%p"/>
    <Column name="category" pattern="%c"/>
    <Column name="file_name" pattern="%F"/>
    <Column name="thread_name" pattern="%t"/>
    <Column name="line" pattern="%L"/>
    <Column name="all_category" pattern="%l"/>
    <Column name="message" pattern="%m"/>
</JDBC>
/**
 * @author AnHui OuYang
 * @version 1.0
 * created at 2022-02-19 17:14
 */
public class DruidConnectionFactory {
    /***
     * 数据库连接对象
     */
    private static DataSource dataSource;

    /***
     * 获取数据库连接池对象
     */
    static {
        try {
            // 获取加载配置文件的对象
            Properties properties = new Properties();
            // 获取类的类加载器
            ClassLoader classLoader = JdbcUtils.class.getClassLoader();
            // 获取druid-1.0.9.properties配置文件资源输入流
            InputStream resourceAsStream = classLoader.getResourceAsStream("druid.properties");
            // 加载配置文件
            properties.load(resourceAsStream);
            // 获取连接池对象
            dataSource = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /***
     * 获取连接池对象
     */
    public static DataSource getDataSource() {
        return dataSource;
    }

    /***
     * 获取数据库连接对象
     */
    public static Connection getConnection() throws Exception {
        return dataSource.getConnection();
    }

    /***
     * 归还连接
     * @param t   要被归还到熟即可连接池对象的数据库连接对象
     * @param <T> 数据库连接对象的类型
     */
    public static <T> void releaseResources(T t) {
        if (t != null) {
            try {
                // 利用反射,获取class对象
                Class<?> aClass = t.getClass();
                // 获取class对象中的方法对象
                Method close = aClass.getMethod("close");
                // 执行方法
                close.invoke(t);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

//druid.properties配置文件放在Resources目录下
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/log4j?useSSL=false
username=root
password=123
# 初始化个数
initialSize=5
# 最大连接数
maxActive=10
# 等待时间,毫秒
maxWait=3000
# 最少连接数
minIdle=3
获取数据库连接的druid工具类
CREATE TABLE `log` (
    `log_id` INT ( 11 ) NOT NULL AUTO_INCREMENT,
    `project_name` VARCHAR ( 255 ) DEFAULT NULL COMMENT '项目名称',
    `create_date` VARCHAR ( 255 ) DEFAULT NULL COMMENT '创建时间',
    `level` VARCHAR ( 255 ) DEFAULT NULL COMMENT '优先级',
    `category` VARCHAR ( 255 ) DEFAULT NULL COMMENT '所在类的全名',
    `file_name` VARCHAR ( 255 ) DEFAULT NULL COMMENT '输出日志消息产生时所在的文件名称 ',
    `thread_name` VARCHAR ( 255 ) DEFAULT NULL COMMENT '日志事件的线程名',
    `line` VARCHAR ( 255 ) DEFAULT NULL COMMENT '行号',
    `all_category` VARCHAR ( 255 ) DEFAULT NULL COMMENT '日志事件的发生位置',
    `message` VARCHAR ( 4000 ) DEFAULT NULL COMMENT '输出代码中指定的消息',
    PRIMARY KEY ( `log_id` ) 
);
MySQL数据库创建表语句
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.21</version>
</dependency>
maven添加的坐标

七:Layout日志格式化

  Appender在将日志数据写入目标位置之前,一般会将日志数据通过Layout进行格式化。Layout也可以通过特定的Layout插件名或layout元素(带有指定Layout插件名的type属性)来配置。Log4j提供了多种不同的Layout来适用于多种形式的输出,如JSON、XML、HTML和Syslog(包括最新的RFC 5424版本),其中,PatternLayout可以使用与C语言printf函数类似的转换模式来指定输出格式,PatternLayout可以使用一些%开头的转换说明符(conversion specifier),每个转换说明符包含一个可选的格式修饰符(format modifier)和一个转换字符(conversion character)来格式化日志事件的数据输出。格式修饰符(format modifier)用于指定字段宽度、填充、左右对齐等。 如"%-5p [%t]: %m%n"表明日志级别(%p)是向左(-5则向左对齐,5则向右对齐)对齐,宽度为5个字符,可能的日志输出内容为:[WARN ] [main]: 这个一个Message警告信息

常见格式转换说明:
%c{precision},%logger{precision}:
    logger名称,precision可以是一个正整数、负整数、"1."、"1.1.."、"."等格式,用于指定输出的logger的名称的层级和详细程度
        转换              原全限定类名                        转换结果
        %c{1}        org.apache.commons.Foo            Foo
        %c{2}        org.apache.commons.Foo            commons.Foo
        %c{10}        org.apache.commons.Foo            org.apache.commons.Foo
        %c{-1}        org.apache.commons.Foo            apache.commons.Foo
        %c{-2}        org.apache.commons.Foo            commons.Foo
        %c{-10}        org.apache.commons.Foo            org.apache.commons.Foo
        %c{1.}        org.apache.commons.Foo            o.a.c.Foo
        %c{1.1..}    org.apache.commons.test.Foo        o.a...Foo
        %c{.}        org.apache.commons.test.Foo        ....Foo
%C{precision},%class{precision}:
    输出调用者的权限定类名,precision的规则与logger名称的用法相同。Log4j在输出类名时会检查堆栈信息,是耗时的操作,
    建议使用%c{precision}或%logger{precision}代替。
%d{pattern},%date{pattern}:
    输出日志事件的时间,pattern经常包含若干对包含时间/日期格式(SimpleDateFormat)的花括号。
    预置的时间/日期格式示例如下:
            转化模式                                    转化结果
        %d{DEFAULT}                         2012-11-02 14:34:02,123
        %d{DEFAULT_MICROS}                  2012-11-02 14:34:02,123456
        %d{DEFAULT_NANOS}                   2012-11-02 14:34:02,123456789
        %d{ISO8601}                         2012-11-02T14:34:02,781
        %d{ISO8601_BASIC}                   20121102T143402,781
        %d{ISO8601_OFFSET_DATE_TIME_HH}     2012-11-02'T'14:34:02,781-07
        %d{ISO8601_OFFSET_DATE_TIME_HHMM}   2012-11-02'T'14:34:02,781-0700
        %d{ISO8601_OFFSET_DATE_TIME_HHCMM}  2012-11-02'T'14:34:02,781-07:00
        %d{ABSOLUTE}                        14:34:02,781
        %d{ABSOLUTE_MICROS}                 14:34:02,123456
        %d{ABSOLUTE_NANOS}                  14:34:02,123456789
        %d{DATE}                            02 Nov 2012 14:34:02,781
        %d{COMPACT}                         20121102143402781
        %d{UNIX}                            1351866842
        %d{UNIX_MILLIS}                     1351866842781
    花括号里的内容也可以设置为包含特定时区Id(java.util.TimeZone.getTimeZone)的日期/时间模式的字符串,示例如下:
        转化模式                                                转化结果
        %d{HH:mm:ss,SSS}                                14:34:02,123
        %d{HH:mm:ss,nnnn} to %d{HH:mm:ss,nnnnnnnnn}        14:34:02,1234 to 14:34:02,123456789
        %d{dd MMM yyyy HH:mm:ss,SSS}                    02 Nov 2012 14:34:02,123
        %d{dd MMM yyyy HH:mm:ss,nnnn} to %d{dd MMM yyyy HH:mm:ss,nnnnnnnnn}
                                            02 Nov 2012 14:34:02,1234 to 02 Nov 2012 14:34:02,123456789
        %d{HH:mm:ss}{GMT+0}                                18:34:02
        %d{yyyy-MM-dd HH:mm:ss.SSS}                        2020-04-20 23:30:54.123
    如果未指定日期/时间格式,则使用%d{DEFAULT}。
%L,%line:
    显示日志输出的代码所在的行数。Log4j在输出行号时会检查堆栈信息,是耗时的操作;
%m,%msg:
    输出应用中自定义的日志内容;
%M,%method:
    输出方法名。Log4j在输出行号时会检查堆栈信息,是耗时的操作;
%n:
    输出当前运行平台所用的换行符,一般放在末尾;
%p|level{level=label, level=label, ...},%p|level{length=n},%p|level{lowerCase=true|false}:
    输出日志的级别。可以每个日志级别指定别名,如%level{WARN=W, DEBUG=D, ERROR=E, TRACE=T, INFO=I},
    %level{length=1}也可以实现同样的效果,如果length的值超过了日志级别的名称,那么使用正常的日志级别名称。
    level=label和length=n可以组合使用,如%level{ERROR=Error, length=2}为ERROR级别指定了别名,
    为其他日志级别限定了长度。此外,还可以指定级别的大小写;
%T,%tid,%threadId:
    输出日志的线程号,非常有必要;
%t,%tn,%thread,%threadName:
    输出日志的线程名称,类似于线程号作用相同,可选择其中一个;
%%:
    用于输出一个%。

八:Filter过滤器

除了日志级别的自动过滤,Log4j还提供了Filter(过滤器),Filters 可以配置在以下位置:
①:上下文级别(Context-wide)的Filters直接配置在Configuration元素(如果使用 XML 配置文件)中和Appenders、
    Loggers 、Properties元素同级的位置,这类Filters拒绝的日志事件(Log Events)将不会发送到Loggers进行进一步处理。
    一旦日志事件被上下文范围的过滤器接受,它将不会被任何其他上下文范围的过滤器评估(判断是否过滤该Log Events),
    也不会使用Logger的 level 来过滤事件。不过,事件将由 Logger 和 Appender Filters 进行评估。
②:Logger的过滤器配置在指定的 Logger 上(在 Logger 元素中)。这些过滤器在上下文范围的过滤器和 Logger 的日志级别之后
    再对日志事件进行评估。被这些过滤器拒绝的日志事件将被丢弃,并且无论叠加性(additivity)如何设置(true或false),
    日志事件都不会被传递给父 Logger。
③:ppender Reference 过滤器,在Appender 引用元素中,用于确定Logger是否应该将事件路由到Appender。
④:Appender过滤器,在Appender元素(包括简明格式)中,用于确定该Appender是否对日志事件进行格式化和发布
    (publication,即输出日志)。

在日志事件的路由控制到达LoggerConfig之前(上下文级别的 Filters)、在控制到达LoggerConfig之后但在调用任何Appender
之前(Logger的过滤器)、在控制到LoggerConfig之后但在调用某个特定Appender之前(Appender Reference 过滤器),
以及在每个Appender上(Appender 过滤器)都可以使用Filter进行日志级别的过滤。

类似于防火墙的过滤器一样,每个 Filter 都可以返回 Accept、Deny 和 Neutral(中立)三个值之一:
    Accept:表示日志事件会被立即处理,不会调用其他 Filter;
    Deny:表示日志事件被立即忽略并不再经过其他过滤器;
    Neutral:表示日志事件将传递给其他Filter,如果没有其他Filter,则事件将会被处理。

即使一个事件被某个Filter接受了也不一定会输出。当事件被某个前置LoggerConfig Filter接受了但被后面的
LoggerConfig拒绝了或被所有 Appender 拒绝了就会出现这样的情况。

1:CompositeFilter

  虽然只能配置一个Filter,但可以通过Filters元素表示一个CompositeFilter(组合过滤器) ,该元素不接收任何参数,Filters元素允许包含任意多个Filter子元素。

2:ThresholdFilter(常用)

该Filter是使用最多的过滤器,主要属性如下:
    level(String)
        匹配的可用的日志级别,可以为 OFF、FATAL、ERROR、WARN、INFO、DEBUG、TRACE 、ALL 之一
    onMatch(String)
        当日志级别匹配时匹配时执行的过滤动作,可以为 ACCEPT、DENY、NEUTRAL 之一,默认为 NEUTRAL
    onMismatch(String)
        当日志级别不匹配时执行的过滤动作,可以为 ACCEPT、DENY、NEUTRAL 之一,默认为 DENY

如果LogEvent中的日志级别与比该Filter中level属性中配置的日志级别相同或比该Filter中level属性中的日志级别更高,
则此Filter将返回onMatch结果,否则将返回onMismatch的结果。例如,如果ThresholdFilter的level属性值为Error,
且LogEvent包含Debug级别,那么将返回onMismatch的值,因为Error事件比Debug事件更具体。
<Configuration status="fatal" monitorInterval="30">
    <Properties>
        <Property name="myPattern" value="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
    </Properties>
    <Appenders>
        <Console name="ConsoleAppend" target="SYSTEM_OUT">
            <PatternLayout pattern="${myPattern}"/>
            <!-- 只接受程序中DEBUG级别的日志进行处理-->
            <ThresholdFilter level="DEBUG" onMatch="ACCEPT" onMismatch="DENY"/>
        </Console>
    </Appenders>
    <Loggers>
        <Root level="ALL">
            <AppenderRef ref="ConsoleAppend"/>
        </Root>
    </Loggers>
</Configuration>

九:Logger元素配置

LoggerConfig通过Logger元素进行配置。Logger 元素可用属性如下:
    name:
        必选,用于标识该 logger ;
    level:
        可选,用来设置日志级别。其值可以为TRACE、DEBUG、INFO、WARN、ERROR、ALL或者 OFF。如果没有指定该属性默认为ERROR
    additivity:
        可选的布尔值,用来设置相加性。如果没有指定该属性,则默认为true。

    LoggerConfig(包括 root LoggerConfig)可以通过属性来配置,这些属性将会添加到ThreadContextMap中的属性复本中。
Appender、Filter、Layout等可以引用这些属性,就好像这些属性是ThreadContextMap中的属性一样。这些属性中的变量可以在解析
(parse)配置或动态输出打印日志时计算(resolve)出来。

    LoggerConfig 也可以通过一个或多个AppenderRef元素来配置,每个Appender的引用将会关联到特定的LoggerConfig。如果一个
LoggerConfig 配置了多个 Appender,那么其中的每个 Appender 都会等价地处理日志事件。

    每个配置都必须要有一个root Logger,如果没有显式配置root LoggerConfig,那么默认的root LoggerConfig的日志级别为
ERROR、Appender 为 Console 。root Logger 和其他 Logger 的不同主要在于以下两点:
注:root Logger没有name属性;
注:root Logger 不支持 additivity 属性,因为它没有 parent

Appender Additivity
    某个Logger所允许的每条日志打印请求都会传递给其LoggerConfig中的所有Appender,也会传递给该LoggerConfig的parent
LoggerConfig中的Appender,这种现象称为相加性(Additivity)。也就是说,Appender 会从 LoggerConfig 的继承中继
承相加性。这种特性可以用来汇整某几个 logger 的输出,可以在声明 Logger 的配置文件中设置 additivity="false"
来禁用这种叠加继承。

例如,如果一个ConsoleAppender添加到 root Logger 中,那么所有允许的日志打印请求将至少输出到控制台,如果一个文件
Appender添加到一个LoggerConfig C 中,C 和 C 的children允许的日志打印请求将会输出到文件和控制台。如果LoggerConfig C
中有一个Logger L,那么Logger L的一条日志打印语句将输出到L关联的LoggerConfig C中的所有Appender以及该 LoggerConfig
的所有 ancestor。然而,如果 LoggerConfig C 一个 ancestor P 的叠加标志设置为了 false,那么,L 的输出将直接指向 C
中的所有 Appender 以及 C 的 ancestor 直到 P(包括 P),不会指向到 P 的所有 ancestor 中的 Appender。Logger
的叠加标识默认为 true,表示叠加父级的 Appender。
下面的展示了一个示例:
日志级别的继承是指父级LoggerConfig的日志级别会被子级LoggerConfig所继承,而相加性是指子级Logger的日志时间会传递给
父级Logger,两者刚好相反。
假如你想忽略除了com.foo.Bar以外的所有TRACE日志,仅更改日志级别无法达到目的,解决办法是在配置文件中新建一个logger 定义:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
        </Console>
    </Appenders>
    <Loggers>
        <!-- 新建一个指定名称和指定级别的 logger -->
        <Logger name="com.foo.Bar" level="TRACE" />
        <Root level="ERROR">
            <AppenderRef ref="Console" />
        </Root>
    </Loggers>
</Configuration>
上面的配置记录了com.foo.Bar中的所有级别的日志事件,但其他所有组件的日志事件中只有ERROR级别的才会被记录。
如果我们为com.foo.Bar的logger配置了Appender,如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
        </Console>
    </Appenders>
    <Loggers>
        <!-- 为新建 logger 指定 Appender -->
        <Logger name="com.foo.Bar" level="TRACE">
            <AppenderRef ref="Console" />
        </Logger>
        <Root level="ERROR">
            <AppenderRef ref="Console" />
        </Root>
    </Loggers>
</Configuration>

com.foo.Bar类中的TRACE级别及以上的日志竟然输出了两遍!这是由于名为com.foo.Bar的logger会将其打印事件传递给其parent logger
(这里为 Root ),在 Root logger 中再次执行了打印操作。而案例 2 中的没有重复打印是由于名为 com.foo.Bar 的 logger
没有设置 Appender,它仅负责记录的打印事件,然后将其传递给了 Root logger,所以仅 Root logger 有打印操作。在配置文件中设置
additivity="false" 来禁用这种叠加继承:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
        </Console>
    </Appenders>
    <Loggers>
        <!-- 设置新建 logger 的相加性为 false -->
        <Logger name="com.foo.Bar" level="TRACE" additivity="false">
            <AppenderRef ref="Console" />
        </Logger>
        <Root level="ERROR">
            <AppenderRef ref="Console" />
        </Root>
    </Loggers>
</Configuration>

十:属性替换

  Log4j2支持使用特定符号来引用其它地方定义的属性。某些属性在翻译配置文件时计算(resolve)出来,某些属性传递到组件(component,比如Logger、Appender等元素)中在运行时求值(evaluate)。Log4j使用Apache Commons Lang的StrSubstitutor和StrLookup类来实现这一功能。与Ant或Maven相似的方式,允许使用${name}形式的变量来计算配置中声明(通过 Property 元素)的属性。例如,下面的例子演示了如何声明和使用RollingFile Appender所用的日志输出文件名属性。

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="debug" name="RoutingTest" packages="org.apache.logging.log4j.test">
  <Properties>
    <Property name="filename">target/rolling1/rollingtest-$${sd:type}.log</Property>
  </Properties>
  <ThresholdFilter level="debug"/>
 
  <Appenders>
    <Console name="STDOUT">
      <PatternLayout pattern="%m%n"/>
      <ThresholdFilter level="debug"/>
    </Console>
    <Routing name="Routing">
      <Routes pattern="$${sd:type}">
        <Route>
          <RollingFile name="Rolling-${sd:type}" fileName="${filename}"
                       filePattern="target/rolling1/test1-${sd:type}.%i.log.gz">
            <PatternLayout>
              <pattern>%d %p %c{1.} [%t] %m%n</pattern>
            </PatternLayout>
            <SizeBasedTriggeringPolicy size="500" />
          </RollingFile>
        </Route>
        <Route ref="STDOUT" key="Audit"/>
      </Routes>
    </Routing>
  </Appenders>
 
  <Loggers>
    <Logger name="EventLogger" level="info" additivity="false">
      <AppenderRef ref="Routing"/>
    </Logger>
 
    <Root level="error">
      <AppenderRef ref="STDOUT"/>
    </Root>
  </Loggers>
 
</Configuration>
Log4j也支持${prefix:name}格式的语法,其中prefix标识告诉Log4j该变量应该在特定的上下文中求值(evaluate)。
Log4j内置的上下文如下:

Prefix    Context
    bundle
        Resource bundle 。格式为${bundle:BundleName:BundleKey}。其中,bundle name遵循包名转换,
        例如,${bundle:com.domain.Messages:MyKey} 。
    ctx
        Thread Context Map (MDC)
    date
        使用特定的格式插入当前日期和/或时间。
    env
        系统环境变量。格式为 ${env:ENV_NAME} 和 ${env:ENV_NAME:-default_value} 。
    jndi
        默认JNDI Context中设置的值。
    jvmrunargs
        通过JMX访问的JVM输出参数,但不是主参数。参考RuntimeMXBean.getInputArguments()。Android 上不可用。
    log4j
        Log4j的配置属性。表达式${log4j:configLocation}和${log4j:configParentLocation}分别额提供log4j
        配置文件的绝对路径和其父文件夹。
    main
        通过MapLookup.setMainArguments(String[]) 设置的一个值。
    map
        MapMessage 中的一个值。
    sd
        StructuredDataMessage中的一个值。id 键将返回没有企业号(enterprise number)的StructuredDataId名。
        type键将返回消息类型。其他键将获取Map中独立的元素。
    sys
        系统属性。格式为 ${sys:some.property} 和 ${sys:some.property:-default_value} 。

可以在配置文件中声明一个默认的属性映射。如果一个值在特定查找中无法定位到,那么将会使用默认属性映射中的值。默认属性映射
预填充了一个表示当前系统主机名和 IP 的 hostName 属性 ,以及一个表示当前日志上下文的名为 contextName 属性。

当一个变量引用的开头为多个$的字符时,StrLookup将会去除前面的$字符。在前面的例子中,Routes元素能够在运行时计算
(resolve)出$${sd:type}变量应用中的变量sd:type 。该变量的前缀为两个$字符,配置文件第一次处理时会去掉第一个$字符,
Routes元素在运行时能够求出声明为${sd:type}的变量,就会检查事件的StructuredDataMessage,如果存在,则其type属性将用作
routing key。并不是所有的元素都可以在运行时计算(resolve)变量。

如果在前缀关联的Lookup中没有找到对应的键值,那么将会使用配置文件里属性声明中的键值。如果没有找到值,则将变量声明作为值返回。
配置文件中的默认值可以这样声明:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
  <Properties>
    <Property name="type">Audit</property>
  </Properties>
  ...
</Configuration>
注意:在处理配置文件时,并不会对RollingFile Appender声明中的变量求值。这是因为整个RollingFile元素的解析会推迟到出现匹配时

十一:Async异步日志

1:AsyncAppender有锁异步

  异步日志是通过引用别的Appender来实现的,当有日志事件到达时,会开启另外一个线程来处理它们。需要注意的是,如果在Appender的时候出现异常,对应用来说是无法感知的。AsyncAppender应该在它引用的Appender之后配置,默认使用java.util.concurrent.ArrayBlockingQueue实现而不需要其它外部的类库。当使用此Appender的时候,在多线程的环境下需要注意,阻塞队列容易受到锁争用的影响,这可能会对性能产生影响。这时候,我们应该考虑使用无锁的异步记录器(AsyncLogger)

日志配置文件
<Configuration status="fatal" monitorInterval="30">
    <Properties>
        <Property name="myPattern" value="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
    </Properties>
    <Appenders>
        <Console name="ConsoleAppend" target="SYSTEM_OUT">
            <PatternLayout pattern="${myPattern}"/>
            <!-- 只接受程序中DEBUG级别的日志进行处理-->
            <ThresholdFilter level="DEBUG" onMatch="ACCEPT" onMismatch="DENY"/>
        </Console>
        <!--在Appenders标签内创建Async异步标签-->
        <Async name="AsyncConsoleAppend">
            <!--指定当前Appenders标签内的哪个Appender需要设置异步并通过引用-->
            <AppenderRef ref="ConsoleAppend"/>
        </Async>
    </Appenders>

    <Loggers>
        <Root level="ALL">
            <!--设置异步的Appender-->
            <AppenderRef ref="AsyncConsoleAppend"/>
        </Root>
    </Loggers>
</Configuration>

2:AsyncLogger无锁日志

  AsyncLogger才是log4j2实现异步最重要的功能体现,也是官方推荐的异步方式。它可以使得调用Logger.log返回的更快。你可以有两种选择:全局异步和混合异步。

  全局异步:所有的日志都异步的记录,在配置文件上不用做任何改动,只需要在jvm启动的时候增加一个参数即可实现。

  混合异步:你可以在应用中同时使用同步日志和异步日志,这使得日志的配置方式更加灵活。

  虽然Log4j2提供以一套异常处理机制,可以覆盖大部分的状态,但是还是会有一小部分的特殊情况是无法完全处理的,比如我们如果是记录审计日志(特殊情况之一),那么官方就推荐使用同步日志的方式,而对于其它一些仅仅是记录一个程序日志的地方,使用异步日志将大幅提升性能,减少对应用本身的影响。混合异步的方式需要通过修改配置文件来实现,使用AsyncLogger标记配置。

全局异步:

<!-- 导入所需的依赖坐标 -->
<dependency>
    <groupId>com.lmax</groupId>
    <artifactId>disruptor</artifactId>
    <version>3.4.4</version>
</dependency>

<!-- 在resources资源目录下创建log4j2.component.properties文件并编写配置 -->
Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector

混合异步:

首先要清除全局异步的全部配置

<Configuration status="fatal" monitorInterval="30">
    <Properties>
        <Property name="myPattern" value="%d{HH:mm:ss.SSS} [%t] %L %-5level %logger{36} - %msg%n"/>
    </Properties>
    <Appenders>
        <Console name="ConsoleAppend" target="SYSTEM_OUT">
            <PatternLayout pattern="${myPattern}"/>
            <!-- 只接受程序中DEBUG级别的日志进行处理-->
            <ThresholdFilter level="DEBUG" onMatch="ACCEPT" onMismatch="DENY"/>
        </Console>
    </Appenders>
    <Loggers>
        <!--创建一个自定义的异步Logger-->
        <!--name:对com.xw当前及其子包下的全部类的日志记录走此异步方式-->
        <!--includeLocation:表示去除日志上的行号信息-->
        <!--注:配置多个Logger时需要设置additivty来取消继承RootLogger-->
        <AsyncLoggger name="com.xw" level="INFO" includeLocation="false" additivty="false">
            <AppenderRef ref="ConsoleAppend"/>
        </AsyncLoggger>
        <!--RootLogger-->
        <Root level="ALL">
            <!--设置异步的Appender-->
            <AppenderRef ref="ConsoleAppend"/>
        </Root>
    </Loggers>
</Configuration>

十二:XInclude配置引入

  XML 配置文件通过 XInclude 来引用其他文件。下面演示了 log4j2.xml 文件引用了其他两个文件。

》》》》》log4j2.xml文件内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<configuration xmlns:xi="http://www.w3.org/2001/XInclude" status="warn" name="XIncludeDemo">
  <properties>
    <property name="filename">xinclude-demo.log</property>
  </properties>
  <ThresholdFilter level="debug"/>
  <xi:include href="log4j-xinclude-appenders.xml" />
  <xi:include href="log4j-xinclude-loggers.xml" />
</configuration>
》》》》》log4j-xinclude-appenders.xml 文件内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<appenders>
  <Console name="STDOUT">
    <PatternLayout pattern="%m%n" />
  </Console>
  <File name="File" fileName="${filename}" bufferedIO="true" immediateFlush="true">
    <PatternLayout>
      <pattern>%d %p %C{1.} [%t] %m%n</pattern>
    </PatternLayout>
  </File>
</appenders>
》》》》》log4j-xinclude-loggers.xml 文件内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<loggers>
  <logger name="org.apache.logging.log4j.test1" level="debug" additivity="false">
    <ThreadContextMapFilter>
      <KeyValuePair key="test" value="123" />
    </ThreadContextMapFilter>
    <AppenderRef ref="STDOUT" />
  </logger>
  <logger name="org.apache.logging.log4j.test2" level="debug" additivity="false">
    <AppenderRef ref="File" />
  </logger>
  <root level="error">
    <AppenderRef ref="STDOUT" />
  </root>
</loggers>

十三:SLF4J门面+Log4j2操作

  为什么SLF4J+Log4j2作为现在工作的主流呢?因为SLF4J是当前最好的日志门面,而Log4j2是当前最好的日志实现框架,所以它们两个搭配会对效率有着提高,虽然说Log4j2有自己的日志门面,但是被我们摒弃了;从上面的全部案例中我们都是使用的Log4j2自己的日志门面;

  下面案例中是SLF4J门面调用Log4j2的门面(这当中存在一个适配器),再由自己的Log4j2门面调用Log2j2的日志实现

    <dependencies>
        <!--单元测试依赖坐标-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.1</version>
            <scope>test</scope>
        </dependency>
        <!--Log4j2自带的日志门面-->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.17.1</version>
        </dependency>
        <!--Log4j2具体的日志实现-->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.17.1</version>
        </dependency>
        <!--导入slf4j日志的门面(最终这个案例的日志实现是log4j2) -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.35</version>
        </dependency>
        <!--为slf4j绑定日志实现 log4j2的适配器 -->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>2.17.1</version>
        </dependency>
    </dependencies>
基本坐标导入
@Test
public void demoA() {
    //注意导入的包import org.slf4j.Logger; import org.slf4j.LoggerFactory;
    //使用SLF4J来获取Logger对象
    Logger logger = LoggerFactory.getLogger(Log4j2Demo.class);
    //打印因为SLF4J就这5个级别
    logger.error(" 错误信息,不会影响系统运行");
    logger.warn(" 警告信息,可能会发生问题");
    logger.info(" 运行信息,数据连接,网络连接,IO操作等");
    logger.debug(" 调试信息,一般在开发中使用,记录程序变量传递信息等等"); //SLF4J默认级别
    logger.trace(" 追踪信息,记录程序所有的流程信息");
}
//日志打印信息
21:14:43.383 [main] 26 ERROR cn.xw.Log4j2Demo -  错误信息,不会影响系统运行
21:14:43.392 [main] 27 WARN  cn.xw.Log4j2Demo -  警告信息,可能会发生问题
21:14:43.393 [main] 28 INFO  cn.xw.Log4j2Demo -  运行信息,数据连接,网络连接,IO操作等
21:14:43.394 [main] 29 DEBUG cn.xw.Log4j2Demo -  调试信息,一般在开发中使用,记录程序变量传递信息等等

十四:SpringBoot实现SLF4J+Log4j2整合(重点)

  上面虽然介绍了在普通项目中的SLF4J+Log4j2的整合,但是随着SpringBoot的广泛使用,现在基本都是在SpringBoot里整合SLF4J+Log4j2的应用,废话不说,直接开始。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <!--注意:spring-boot-starter-parent版本若达到3.0.0 则只支持JDK17,而2.7.11是支持JDK8的最后一个版本-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.11</version>
    </parent>

    <groupId>cn.xw</groupId>
    <artifactId>demo1</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <dependencies>
        <!--Spring Boot的核心启动器,包含了自动配置、日志和YAML-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <!--去掉logback配置,要不然冲突-->
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- 引入SpringBoot的log4j2依赖启动坐标;这坐标包含具体的log4j2的坐标和连接Slf4j的适配器 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>
        <!--SpringBootWeb启动依赖坐标-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--Lombok坐标导入-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.26</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!--配置maven编译版本-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source><!--源代码使用的JDK-->
                    <target>1.8</target><!--target需要生成的目标class文件的编译版本-->
                    <encoding>UTF-8</encoding><!--字符集编码,防止中文乱码-->
                    <failOnError>true</failOnError><!--指示即使存在编译错误,构建是否仍将继续-->
                    <failOnWarning>false</failOnWarning><!--指示即使存在编译警告,构建是否仍将继续-->
                    <showDeprecation>false</showDeprecation><!--设置是否显示使用不推荐API的源位置-->
                    <showWarnings>false</showWarnings><!--设为true若要显示编译警告,请执行以下操作-->
                    <meminitial>128M</meminitial><!--编译器使用的初始化内存-->
                    <maxmem>512M</maxmem><!--编译器使用的最大内存-->
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
pom.xml坐标文件
<?xml version="1.0" encoding="UTF-8" ?>
<!--monitorInterval属性值(秒数)为一个非零值来让Log4j每隔指定的秒数来重新读取配置文件,可以用来动态应用Log4j配置-->
<Configuration status="info" monitorInterval="30">
    <!--用来自定义一些变量-->
    <Properties>
        <!--变量定义-->
        <Property name="myPattern" value="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        <!--./logs则会在当前项目的跟目录下创建logs文件夹-->
        <Property name="dir_url">./logs</Property>
    </Properties>
    <!--使用Appenders元素可以将日志事件数据写到各种目标位置-->
    <Appenders>
        <!-- 默认打印到控制台 -->
        <Console name="ConsoleAppend" target="SYSTEM_OUT">
            <!-- 默认打印格式 -->
            <PatternLayout pattern="${myPattern}"/>
        </Console>
        <!-- 打印到日志文件上 -->
        <File name="FileAppend" fileName="${dir_url}/fileLog.log" bufferedIO="true" immediateFlush="true">
            <PatternLayout>
                <pattern>${myPattern}</pattern>
            </PatternLayout>
        </File>
    </Appenders>
    <!--定义logger,只有定义了logger并引入的appender,appender才会生效-->
    <Loggers>
        <!-- 默认打印日志级别为 error -->
        <Root level="INFO">
            <AppenderRef ref="ConsoleAppend"/>
            <AppenderRef ref="FileAppend"/>
        </Root>
    </Loggers>
</Configuration>
log4j2.xml日志配置文件(放到resources目录下)说明:具体可以自己配置,现在这个配置文件以info级别,并且控制台、文件都打印
/**
 * @author AnHui OuYang
 * @version 1.0
 * created at 2023-04-14 13:48
 */
@Slf4j  //使用Slf4j的注解就相当与我们写了 Logger logger = LoggerFactory.getLogger(this.getClass());
@RestController
@RequestMapping("/log")
public class LogController {

    //通过日志管理器获取Logger对象
    //注意导入的包import org.slf4j.Logger; import org.slf4j.LoggerFactory;
    //Logger logger = LoggerFactory.getLogger(this.getClass());

    @GetMapping("/logCatch/{msg}")
    public String logCatch(@PathVariable(value = "msg") String msg) {
        //打印因为SLF4J就这5个级别
        //日志格式具体可以自己调:"%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"
        log.error(" 错误信息,不会影响系统运行");
        log.warn(" 警告信息,可能会发生问题");
        log.info(" 运行信息,数据连接,网络连接,IO操作等"); //当前我们的log4j2.xml设置的级别
        log.debug(" 调试信息,一般在开发中使用,记录程序变量传递信息等等"); //SLF4J默认级别
        log.trace(" 追踪信息,记录程序所有的流程信息");
        log.info("打印请求过来的数据:{}", msg);
        return "请求成功!!";
    }
}

注意:可能有的人会遇到如下问题:

解决方式:

直接排除不需要的:
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
        <!--去掉logback配置,要不然冲突-->
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-logging</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
如何查找哪个依赖的坐标出现冲突:可以通过 Maven Helper 插件快速定位:

.

posted @ 2022-02-21 21:18  蚂蚁小哥  阅读(24710)  评论(3编辑  收藏  举报