Log4j2日志框架

Log4j2日志框架

1、简介及入门示例

1、背景介绍

官网地址:https://logging.apache.org/log4j/2.x/、Maven 仓库地址:https://search.maven.org/artifact/org.apache.logging.log4j/log4j-api

Log4j2是对Log4j1x的升级版,同时修复与升级了Logback的不足,被誉为是目前最优秀的Java日志框架。Apache Log4j2日志框架的主要特征:

  • 自动重新加载配置:参考了Logback的设计,提供自动刷新参数配置,最实用的就是我们在生产上可以动态的修改日志的级别而无需重启应用
  • 异常处理:在logback中,Appender中的异常不会被应用感知到,但是在Log4j2中,提供了一些异常处理机制
  • 性能提升:log4j2相较于log4j和logback都具有很明显的性能提升,据官方测试,异步记录器的吞吐量比Log4j 1.x 和 Logback高18倍,延迟低
  • 无垃圾机制:(默认开启)log4j2在大部分情况下,都可以使用无垃圾机制【对象重用、内存缓冲】,避免频繁的日志收集导致的 jvm gc
  • 插件架构:Log4j2使用插件模式配置组件。因为无需编写代码来创建和配置Appender、Layout、Pattern Converyer等。在配置了的情况下,Log4j2自动识别插件并使用他们
  • 高级过滤:与Logback一样,Log4j2支持基于Log事件中的上下文数据,标记,正则表达式和其他组件进行过滤。此外,过滤器还可以与记录器关联。与Logback不同,Log4j2可以在任何这些情况下使用通用的Filter类

Log4j2 除了提供日志实现以外,也拥有一套自己的独立的门面。不过目前市面上最主流的日志门面是SLF4J,虽然Log4j2也是日志门面,因为它的日志实现功能非常强大,性能优越。所以大家一般还是将Log4j2看作是日志的实现,SLF4J + Log4j2 的组合是市场上最强大的日志功能实现方式,绝对是主流日志框架。

2、模块介绍

Log4j2中分为:门面(log4j-api.jar)和实现 (log4j-core.jar) 两个模块。门面与SLF4J是一个类型,属于日志抽象门面。而实现部分才是Log4j2的核心:

  • 日志门面:org.apache.logging.log4j » log4j-api
  • 日志实现:org.apache.logging.log4j » log4j-core

Log4j2 重要类的概念:

  • org.apache.logging.log4j.LogManager:日志类工厂,日志管理器,提供静态方法getContext,getFactory,getLogger,可以方便的获取相关信息
  • org.apache.logging.log4j.Logger:日志记录类,用来输出被记录的日志
  • org.apache.logging.log4j.core.LoggerContext:日志上下文,包含已配置的Configuration、Appender,Logger,Filter等信息
  • org.apache.logging.log4j.core.config.Configuration:每一个LoggerContext都有一个有效的Configuration。Configuration包含了所有的Appenders、上下文范围内的过滤器、LoggerConfigs。在重配置期间,新与旧的Configuration将同时存在。当所有的Logger对象都被重定向到新的Configuration对象后,旧的Configuration对象将被停用和丢弃

3、入门案例

1、使用Log4j做日志门面


<dependency>
    <groupId>org.apache.logging.log4jgroupId>
    <artifactId>log4j-apiartifactId>
    <version>2.17.2version>
dependency>

<dependency>
    <groupId>org.apache.logging.log4jgroupId>
    <artifactId>log4j-coreartifactId>
    <version>2.17.2version>
dependency>
package com.xyz;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class Log4j2Test {
    public static void main(String[] args) {
        // 定义日志记录器对象,如下没有使用SLF4J门面技术,完全使用的log4j中的类和方法
        Logger logger = LogManager.getLogger(Log4j2Test.class);
        // log4j2中存在6中日志输出级别
        logger.fatal("fatal");
        logger.error("error");
        logger.warn("warn");
        logger.info("info");
        logger.debug("debug");
        logger.trace("trace");
    }
}
12:10:00.727 [main] FATAL com.xyz.Log4j2Test - fatal
12:10:00.727 [main] ERROR com.xyz.Log4j2Test - error

此时可以发现只输出了error及以上级别的日志,因为Log4j2门面默认是error级别,就说明此时这个日志是使用Log4j2框架实现的。

2、使用SLF4J作为日志的门面


<dependency>
    <groupId>org.slf4jgroupId>
    <artifactId>slf4j-apiartifactId>
    <version>1.7.36version>
dependency>

<dependency>
    <groupId>org.apache.logging.log4jgroupId>
    <artifactId>log4j-slf4j-implartifactId>
    <version>2.17.2version>
dependency>

<dependency>
    <groupId>org.apache.logging.log4jgroupId>
    <artifactId>log4j-apiartifactId>
    <version>2.17.2version>
dependency>

<dependency>
    <groupId>org.apache.logging.log4jgroupId>
    <artifactId>log4j-coreartifactId>
    <version>2.17.2version>
dependency>
package com.xyz;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Log4j2Test {
    public static void main(String[] args) {
        // 注意:此时使用的slf4j的门面技术,没有适应到log4j2,输出的是 slf4j的日志级别(有5个级别)
        Logger logger = LoggerFactory.getLogger(Log4j2Test.class);
        // slf4j中存在5种日志输出级别,此时使用是slf4j的记录器,而不是log4j2的,所以只能输出slf4j中的五种级别
        logger.error("error信息");
        logger.warn("warn信息");
        logger.info("info信息");
        logger.debug("debug信息");
        logger.trace("trace信息");
    }
}
23:11:22.977 [main] ERROR com.xyz.Log4j2Test - error信息

执行结果看到不管是Log4j2自己的门面还是SLF4J门面搭配Log4j2实现默认日志级别都是是error。

2、最牛的功能及性能

1、最强的异步和同步性能

Log4j2在目前JAVA中的日志框架里,异步日志的性能是最高的,没有之一。先来看一下,几种日志框架benchmark对比结果(官方测试结果)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mRGuMCwG-1650171303296)(https://logging.apache.org/log4j/2.x/images/async-throughput-comparison.png)]

从图上可以看出,Log4j2的异步(全异步,非混合模式)下的性能,远超Log4j1和Logback,简直吊打。压力越大的情况下,吞吐上的差距就越大。在64线程测试下,Log4j2的吞吐达到了180w+/s,Llogback/Log4j1只有不到20w,相差近十倍。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AjltEjL4-1650171303297)(https://logging.apache.org/log4j/2.x/images/SyncThroughputLoggerComparisonLinux.png)]

上图是同步测试报告,明显可以看出,与各个日志框架对比而言,无论在同步或者异步情况下,log4j2表现更加优异,而其他日志就显得差强人意了。

2、零GC(Garbage-free)

从Log4j 2.6版本开始(2016年),Log4j2默认就以零GC模式运行了。什么叫零GC呢?就是不会由于Log4j2而导致GC。Log4j2中各种Message对象,字符串数组,字节数组等全部复用,不重复创建,大大减少了无用对象的创建,从而做到“零GC”。

3、更高性能 I/O 写入的支持

Log4j2还提供了一个MemoryMappedFileAppender,I/O 部分使用MemoryMappedFile来实现,可以得到极高的I/O性能。不过在使用MemoryMappedFileAppender之前,得确定你足够了解MemoryMappedFile的相关知识,否则不要轻易使用呦。

4、更强大的参数格式化

Log4j的API与SLF4J相比,提供了更丰富的参数格式化功能。使用 {} 占位符格式化参数。在SLF4J里,我们可以用 {} 的方式来实现“format”的功能(参数会直接toString替换占位符),像下面这样:

org.apache.logging.log4j.Logger logger = LogManager.getLogger("com.xyz");
logger.debug("Logging in user {} with birthday {}", user.getName(), user.getBirthdayCalendar());

5、使用String.format形式格式化参数

Log4j2 中除了支持 {} 的参数占位符,还支持 String.format 的形式

org.apache.logging.log4j.Logger logger = LogManager.getFormatterLogger("com.xyz");
logger.debug("Logging in user %s with birthday %s", user.getName(), user.getBirthdayCalendar());
logger.debug("Logging in user %1$s with birthday %2$tm %2$te,%2$tY", user.getName(), user.getBirthdayCalendar());
logger.debug("Integer.MAX_VALUE = %,d、Long.MAX_VALUE = %,d", Integer.MAX_VALUE, Long.MAX_VALUE);

注意:如果想使用 String.format 的形式,需要使用 LogManager.getFormatterLogger 而不是 LogManager.getLogger

6、使用logger.printf格式化参数

Log4j2的Logger接口中,还有一个logger.printf()方法,无需创建 LogManager.getFormatterLogger,就可以使用 String.format 的形式

org.apache.logging.log4j.Logger logger = LogManager.getLogger("com.xyz");
logger.printf(Level.INFO, "Logging in user %1$s with birthday %2$tm %2$te,%2$tY", user.getName(), user.getBirthdayCalendar());
logger.debug("Opening connection to {}...", someDataSource);

7、“惰性”打日志(lazy logging)

这个功能虽然小,但非常实用。在某些业务流程里,为了留根或追溯问题,需要完整的打印入参,一般是把入参给用JSON序列化后用debug级别打印:

org.apache.logging.log4j.Logger logger = LogManager.getLogger("com.xyz");
logger.debug("入参报文:{}", JSON.toJSONString(policyDTO));

如果需要追溯问题时,会将系统的日志级别调到debug,这样就可以打印。但是这里有个问题,虽然在info级别下debug不会输出内容,但JSON.toJSONString()这个序列化的代码一定会执行,严重影响正常流程下的执行效率。

我们期望的结果是info级别下,连序列化都不执行。这里可以通过logger.isDebugEnable()来判断当前配置下debug级别是否可以输出:

org.apache.logging.log4j.Logger logger = LogManager.getLogger("com.xyz");
if(logger.isDebugEnabled()) {
    logger.debug("入参报文:{}", JSON.toJSONString(policyDTO));
}

这样虽然可以避免不必要的序列化,但每个地方都这么写还是有点难受的,一行变成了三行。

Log4j2的Logger对象,提供了一系列Lambda的支持,通过这些接口可以实现“惰性”打日志:

void fatal(String message, Supplier... paramSuppliers);
void error(String message, Supplier<?>... paramSuppliers);
void warn(String message, Supplier... paramSuppliers);
void info(String message, Supplier<?>... paramSuppliers);
void debug(String message, Supplier<?>... paramSuppliers);
void trace(String message, Supplier<?>... paramSuppliers);

org.apache.logging.log4j.Logger logger = LogManager.getLogger("com.xyz");
// 等同于下面的先判断,后打印
logger.debug("入参报文:{}",() -> JSON.toJSONString(policyDTO));
// 与上面效果一样
if(logger.isDebugEnabled()) {
    logger.debug("入参报文:{}",JSON.toJSONString(policyDTO));
}

这种 Supplier + Lambda 的形式,等同于上面的先判断 logger.isDebugEnable() 然后打印,三行的代码变成了一行。

8、更简化的配置文件

Log4j2同时支持XML/JSON/YML/Properties四种形式的配置文件,不过最主流的还是XML的方式,最直观。来查看logback和log4j2的配置文件对比:

logback.xml


<configuration>
    <appender name = "File" class= "ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs/app.logfile>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>logs/archives/app-%d{yyyy-MM-dd}.log.gzfileNamePattern>
            
            <maxFileSize>1 GBmaxFileSize>
        rollingPolicy>
    appender>
    <root level="info">
        <appender-ref ref="File"/>
    root>
configuration>

log4j2.xml


<Configuration status="warn" name="XInclude" xmlns:xi="http://www.w3.org/2001/XInclude">
    <Appenders>
        <RollingFile name="File" fileName="logs/app.log" filePattern="logs/archives/app-%d{yyyy-MM-dd}-%i.log.gz">
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} %5p [%t] %-40.40c{1.} : %m%n"/>
            <Policies>
                <TimeBasedTriggeringPolicy />
                
                <SizeBasedTriggeringPolicy size="1 GB"/>
            Policies>
        RollingFile>
    Appenders>
    <Loggers>
        <Root level="INFO">
            <AppenderRef ref="File"/>
        Root>
    Loggers>
Configuration>

在log4j2中,appender的配置从使用 Appender 实现名即标签名的形式,语法上更简洁一些:

<RollingFile name="File">RollingFile>

<appender name = "File" class= "ch.qos.logback.core.rolling.RollingFileAppender">appender>

3、组件介绍与配置

1、几大组件的介绍

Log4j2由如下几部分构成:

  1. Logger:负责捕获日志记录,并传递给 Appender,他是日志行为的发起者
  2. Appender:负责将日志事件进行分类处理,将日志发往他应该去的目标去向,因此也可以称为 Handler
  3. Layout:Layout 负责在日志输出前决定日志的格式,因此也可以称为 Fomatter
  4. Filter:是可选的组件,每一个 Logger、Appender 甚至全局都可以配置若干个 Filter,来决定相应的组件对当前的日志时间是否关心
  5. Level:日志级别有:OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL(默认为:ERROR)源码:org.apache.logging.log4j.Level

2、Log4j2默认配置

DefaultConfiguration类中提供的默认配置将设置,通过debug可以在LoggerContext类中发现

package org.apache.logging.log4j.core;

public class LoggerContext extends AbstractLifeCycle 
    implements org.apache.logging.log4j.spi.LoggerContext, AutoCloseable, Terminable, ConfigurationListener, LoggerContextShutdownEnabled {
    /**
     * The Configuration is volatile to guarantee that initialization of the Configuration has completed before the
     * reference is updated.
     */
    private volatile Configuration configuration = new DefaultConfiguration();
}

启动时可以通过debug看到:

  • 默认的root日志的layout:%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n
  • 默认的日志级别:ERROR

实际上也能从默认配置类中看到一些默认的配置:

public abstract class AbstractConfiguration extends AbstractFilterable implements Configuration {
    protected void setToDefault() {
        // LOG4J2-1176 facilitate memory leak investigation
        setName(DefaultConfiguration.DEFAULT_NAME + "@" + Integer.toHexString(hashCode()));
        final Layout<? extends Serializable> layout = PatternLayout.newBuilder()
            .withPattern(DefaultConfiguration.DEFAULT_PATTERN)
            .withConfiguration(this)
            .build();
        final Appender appender = ConsoleAppender.createDefaultAppenderForLayout(layout);
        appender.start();
        addAppender(appender);
        final LoggerConfig rootLoggerConfig = getRootLogger();
        rootLoggerConfig.addAppender(appender, null, null);

        rootLoggerConfig.setLevel(getDefaultLevel());
    }
}

3、自定义配置文件位置

1、Log4j2默认在classpath下查找配置文件,可以修改配置文件的位置。在非web项目中:

ConfigurationSource source = new ConfigurationSource(new FileInputStream("D:/log4j2.xml"));
Configurator.initialize(null, source);
org.apache.logging.log4j.Logger logger = LogManager.getLogger(Log4j2Test.class);

2、如果是在SpringBoot项目,在appliaction.properties 或 application.yml 中配置即可:

logging.config=classpath:log4j2.xml

3、如果是web项目,在web.xml中添加(Listener使用的是Log4j2默认的):

<listener>
    <listener-class>org.apache.logging.log4j.web.Log4jServletContextListenerlistener-class>
listener>
<context-param>
    <param-name>log4jConfigurationparam-name>
    <param-value>/WEB-INF/conf/log4j2.xmlparam-value>
context-param>

4、如果是web项目,在web.xml中添加(使用自定义Listener):


<listener>
    <listener-class>com.xyz.Log4j2ConfigListenerlistener-class>
listener>
<context-param>
    <description>Logging Configuration File Pathdescription>
    <param-name>log4j.configurationFileparam-name>
    <param-value>log4j/log4j2.xmlparam-value>
context-param>
package com.xyz;
import java.util.Enumeration;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import org.apache.logging.log4j.core.config.Configurator;
public class Log4j2ConfigListener implements ServletContextListener {
    private static final String KEY = "log4j.configurationFile";
    @Override
    public void contextDestroyed(ServletContextEvent arg0) {
    }
    @Override
    public void contextInitialized(ServletContextEvent arg0) {
        String fileName = getContextParam(arg0);
        Configurator.initialize("Log4j2", "classpath:" + fileName);
    }
    @SuppressWarnings("unchecked")
    private String getContextParam(ServletContextEvent event) {
        Enumeration<String> names = event.getServletContext().getInitParameterNames();
        while (names.hasMoreElements()) {
            String name = names.nextElement();
            String value = event.getServletContext().getInitParameter(name);
            if(name.trim().equals(KEY)) {
                return value;
            }
        }
        return null;
    }
}

4、XML配置文件模板

模板 1(简约版):


<Configuration monitorInterval="1" status="ERROR" strict="true" name="LogConfig">
    <Properties>
        <Property name="logbasedir">E:/logsProperty>
        <Property name="log.layout">%d %-5p %t (%c:%L) - %m%nProperty>
    Properties>

    
    <Appenders>
        <Appender type="Console" name="STDOUT">
            <Target>SYSTEM_OUTTarget>
            <Layout type="PatternLayout" pattern="${log.layout}"/>
        Appender>
        <Appender type="RollingFile" name="FILE" 
                  fileName="${logbasedir}/jutap-${sys:APPNAME}.log"
                  filePattern = "${logbasedir}/jutap-${sys:APPNAME}-%d{yyyy-MM-dd}.%i.log">
            <Policies>
                <TimeBasedTriggeringPolicy />
                <SizeBasedTriggeringPolicy size="100 MB"/>
            Policies>
            <Layout type="PatternLayout">
                <Charset>GBKCharset>
                <Pattern>${log.layout}Pattern>
            Layout>
        Appender>
        <Appender type="RollingFile" 
                  name="ExceptionLog" 
                  fileName="${logbasedir}/exception-${sys:APPNAME}.log"
                  filePattern="${logbasedir}/exception-${sys:APPNAME}-%d{yyyy-MM-dd}.%i.log">
            <Policies>
                <TimeBasedTriggeringPolicy />
                <SizeBasedTriggeringPolicy size="100 MB"/>
            Policies>
            <Layout type="PatternLayout">
                <Charset>GBKCharset>
                <Pattern>${log.layout}Pattern>
            Layout>
        Appender>
    Appenders>
    <Loggers>
        <Logger name="exception" level="error" additivity="false">
            <AppenderRef ref="ExceptionLog"/>
        Logger>
        <Root level="info">
            <AppenderRef ref="STDOUT"/>
            <AppenderRef ref="FILE"/>
        Root>
        <Logger name="com.garfield.learn" level="debug"/>
        <Logger name="com.garfield.learnp" level="info"/>
    Loggers>
Configuration>

模板 2(复杂版):



<Configuration status="WARN" monitorInterval="600">
    <Properties>
        
        <Property name="LOG_HOME">${sys:catalina.home}/WebAppLogs/SSHExampleProperty>
    Properties>
    <Appenders>
        
        <Console name="console_out_appender" target="SYSTEM_OUT">
            
            <ThresholdFilter level="warn" onMatch="DENY" onMismatch="ACCEPT"/>
            
            <PatternLayout pattern="%5p [%t] %d{yyyy-MM-dd HH:mm:ss} (%F:%L) %m%n"/>
        Console>
        
        <Console name="console_err_appender" target="SYSTEM_ERR">
            
            <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
            
            <PatternLayout pattern="%5p [%t] %d{yyyy-MM-dd HH:mm:ss} (%F:%L) %m%n"/>
        Console>
        
        
        <RollingRandomAccessFile name="trace_appender"
                                 immediateFlush="true" 
                                 fileName="${LOG_HOME}/trace.log"
                                 filePattern="${LOG_HOME}/trace/trace - %d{yyyy-MM-dd HH_mm_ss}.log.gz">
            <PatternLayout>
                <pattern>%5p [%t] %d{yyyy-MM-dd HH:mm:ss} (%F:%L) %m%npattern>
            PatternLayout>
            <Policies>
                
                <SizeBasedTriggeringPolicy size="2MB"/>
            Policies>
            <Filters>
                
                <ThresholdFilter level="debug" onMatch="DENY" onMismatch="NEUTRAL"/>
                <ThresholdFilter level="trace" onMatch="ACCEPT" onMismatch="DENY"/>
            Filters>
        RollingRandomAccessFile>
        
        
        <RollingRandomAccessFile name="debug_appender"
                                 immediateFlush="true" 
                                 fileName="${LOG_HOME}/debug.log"
                                 filePattern="${LOG_HOME}/debug/debug - %d{yyyy-MM-dd HH_mm_ss}.log.gz">
            <PatternLayout>
                <pattern>%5p [%t] %d{yyyy-MM-dd HH:mm:ss} (%F:%L) %m%npattern>
            PatternLayout>
            <Policies>
                
                <SizeBasedTriggeringPolicy size="2MB"/>
                
                
            Policies>
            <Filters>
                
                <ThresholdFilter level="info" onMatch="DENY" onMismatch="NEUTRAL"/>
                <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
            Filters>
        RollingRandomAccessFile>
        
        <RollingRandomAccessFile name="info_appender"
                                 immediateFlush="true" 
                                 fileName="${LOG_HOME}/info.log"
                                 filePattern="${LOG_HOME}/info/info - %d{yyyy-MM-dd HH_mm_ss}.log.gz">
            <PatternLayout>
                <pattern>%5p [%t] %d{yyyy-MM-dd HH:mm:ss} (%F:%L) %m%npattern>
            PatternLayout>
            <Policies>
                <SizeBasedTriggeringPolicy size="2MB"/>
            Policies>
            <Filters>
                <ThresholdFilter level="warn" onMatch="DENY" onMismatch="NEUTRAL"/>
                <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
            Filters>
        RollingRandomAccessFile>
        
        <RollingRandomAccessFile name="warn_appender"
                                 immediateFlush="true" 
                                 fileName="${LOG_HOME}/warn.log"
                                 filePattern="${LOG_HOME}/warn/warn - %d{yyyy-MM-dd HH_mm_ss}.log.gz">
            <PatternLayout>
                <pattern>%5p [%t] %d{yyyy-MM-dd HH:mm:ss} (%F:%L) %m%npattern>
            PatternLayout>
            <Policies>
                <SizeBasedTriggeringPolicy size="2MB"/>
            Policies>
            <Filters>
                <ThresholdFilter level="error" onMatch="DENY" onMismatch="NEUTRAL"/>
                <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
            Filters>
        RollingRandomAccessFile>
        
        <RollingRandomAccessFile name="error_appender"
                                 immediateFlush="true" 
                                 fileName="${LOG_HOME}/error.log"
                                 filePattern="${LOG_HOME}/error/error - %d{yyyy-MM-dd HH_mm_ss}.log.gz">
            <PatternLayout>
                <pattern>%5p [%t] %d{yyyy-MM-dd HH:mm:ss} (%F:%L) %m%npattern>
            PatternLayout>
            <Policies>
                <SizeBasedTriggeringPolicy size="2MB"/>
            Policies>
            <Filters>
                <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
            Filters>
        RollingRandomAccessFile>
    Appenders>
    <Loggers>
        
        <root level="trace">
            <appender-ref ref="console_out_appender"/>
            <appender-ref ref="console_err_appender"/>
            <appender-ref ref="trace_appender"/>
            <appender-ref ref="debug_appender"/>
            <appender-ref ref="info_appender"/>
            <appender-ref ref="warn_appender"/>
            <appender-ref ref="error_appender"/>
        root>
        
        <logger name="org.springframework.core" level="info"/>
        <logger name="org.springframework.beans" level="info"/>
        <logger name="org.springframework.context" level="info"/>
        <logger name="org.springframework.web" level="info"/>
        <logger name="org.jboss.netty" level="warn"/>
        <logger name="org.apache.http" level="warn"/>
    Loggers>
Configuration>

5、动态切换日志级别

package com.xyz;

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;

public class Log4j2Test {
    private static  final Logger logger = LogManager.getLogger(Log4j2Test.class);
    public static void main(String[] args) {
        logger.fatal("fatal...");
        logger.error("error...");

        // LoggerContext getContext(final boolean currentContext):获取log4j日志上下文,false表示返回合适调用方的上下文
        LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false);
        // 返回当前配置,发生重新配置时,将替换该配置。
        Configuration configuration = loggerContext.getConfiguration();
        // 查找记录器名称的相应 LoggerConfig
        LoggerConfig loggerConfig = configuration.getLoggerConfig(LogManager.ROOT_LOGGER_NAME);
        // 设置日志级别,如果 level 值不属于 ALL、TRACE、DEBUG、INFO、WARN、ERROR、FATAL、OFF,则默认会设置为 DEBUG
        loggerConfig.setLevel(Level.toLevel("FATAL"));
        // 根据当前配置更新所有记录器
        loggerContext.updateLoggers();
        // 查询 root(根)日志输出级别结果返回
        System.out.println("设置后的Root日志级别: " + LogManager.getRootLogger().getLevel().name());

        logger.fatal("fatal...");
        logger.error("error...");
    }
}
15:27:25.768 [main] FATAL com.xyz.Log4j2Test - fatal...
15:27:25.768 [main] ERROR com.xyz.Log4j2Test - error...
设置后的Root日志级别: FATAL
15:27:25.778 [main] FATAL com.xyz.Log4j2Test - fatal...

4、自定义配置文件

Log4j2配置文件通常为:log4j2.xml和log4j2-test.xml,加载顺序为:log4j2-test.xml » log4j2.xml。

XML配置文件语法

;
<Configuration>
    <Properties>
        <Property name="name1">valueproperty>
        <Property name="name2" value="value2"/>
    Properties>
    <filter  ... />
    <Appenders>
        <appender ... >
            <filter  ... />
        appender>
        ...
    Appenders>
    <Loggers>
        <Logger name="name1" level="level">
            <filter  ... />
        Logger>
        ...
        <Root level="level">
            <AppenderRef ref="name"/>
        Root>
    Loggers>
Configuration>
节点名称 含义 配置项
Configuration 配置的根节点,有两个子节点:Appenders和Loggers(表明可以定义多个Appender和Logger) 该节点有两个配置项status:用于指定log4j2本身的日志打印级别,日志级别从低到高分为TRACE、DEBUG、INFO、WARN、ERROR、FATAL,如果设置为WARN,则低于WARN的信息都不会输出。monitorinterval:用于指定log4j自动重新配置的监测间隔时间,单位是秒(s),最小是5s
Appenders 负责将日志输出到目的地(这个目的地可以是控制台,文件,数据库,甚至是邮件),定义输出内容,输出格式,输出方式,日志保存策略等 log4j2支持的appender种类非常多,完整的appender可通过官方网站进行查询。其中,比较常见的appender有:ConsoleAppender、FileAppender、RandomAccessFileAppender、RollingFileAppender、RollingRandomAccessFileAppender,另外,还提供异步的AsyncAppender,实现异步处理日志事件
Loggers LogEvent生产的源头,只有定义了logger并引入的appender,appender才会生效 常见的有两种:Root和Logger

1、Configuration

1、Configuration

根节点Configuration中有两个常用的属性:status、monitorterval、name。示例及解释如下:

Configuration元素的属性:

  • status:用于指定Log4j2日志的输出级别,会打印Log4j2加载、运行等信息。 有如下值:trace, debug, info, warn, error, fatal
  • monitorterval:用于指定自动重新检测读取配置内容的间隔时间,单位为秒(s),最小值为5秒
  • name:配置的名称,用的也不太多

<Configuration status="WARN" monitorInterval="30" name="xyzConfiguration">
Configuration>

2、Properties

子节点元素Properties是用来定义常量,以便在其他配置项中引用,该配置是可选的,例如定义日志的存放位置

<Properties>
    
    <Property name="nowDate">%date{yyyy-MM-dd}Property>
    <Property name="logDir">${sys:catalina.home}/logs/Property>
    <Property name="logPath">./logsProperty>
    <property name="pattern_format">[%d{HH:mm:ss:SSS}] [%-5p] - %l -- %m%nproperty>
Properties>
Prefix Context
base64 Base64编码数据,格式为 b a s e 64 : B a s e 6 4 e n c o d e d d a t a , 如 : {base64:Base64_encoded_data},如: base64:Base64encodeddata{base64:SGVsbG8gV29ybGQhCg==} 输出 Hello World!
bundle 资源绑定。格式为: b u n d l e : B u n d l e N a m e : B u n d l e K e y , 示 例 如 : {bundle:BundleName:BundleKey},示例如: bundle:BundleName:BundleKey{bundle:com.domain.Messages:MyKey}
ctx 线程上下文映射(MDC),如:$${ctx:loginId}
date 插入指定的时间格式。如:$${date:MM-dd-yyyy}
env 系统环境变量,格式为${env:ENV_NAME} 或者 e n v : E N V N A M E : − d e f a u l t v a l u e , 如 : {env:ENV_NAME:-default_value} ,如: env:ENVNAME:defaultvalue e n v : U S E R , {env:USER}, env:USER${env:USER:-jdoe}
jndi 设置JNDI上下文,如:$${jndi:logging/context-name}。注意:在Android下面不能使用
sys 系统属性,格式为${sys:some.property}或者 s y s : s o m e . p r o p e r t y : − d e f a u l t v a l u e , 如 : {sys:some.property:-default_value},如: sys:some.property:defaultvalue{sys:logPath}
jvmrunargs 通过 JMX 访问的 JVM 输入参数,但不是主参数; 请参阅 RuntimeMXBean.getInputArguments()。 在Android上不可用
log4j Log4j配置属性。 表达式 ${log4j:configLocation} 和 ${log4j:configParentLocation} 分别提供了log4j配置文件及其父文件夹的绝对路径
main 使用 MapLookup.setMainArguments(String[]) 设置的值
map 来自 MapMessage 的值

3、Loggers

Loggers 定义日志输出位置,包含Logger和Root结点。Root用来指定项目的根日志,若没有单独指定Logger,则会默认使用该Root日志输出

Root:每个配置都必须有一个根记录器Root。如果未配置,则将使用默认根LoggerConfig,其级别为ERROR且附加了ConsoleAppender。根记录器和其他记录器之间的主要区别是:1.根记录器只有level属性。2.根记录器没有name属性。3.根记录器不支持additivity属性,因为它没有父级

  • 属性:level:日志输出级别,共有8个级别,按照从低到高为:All < Trace < Debug < Info < Warn < Error < Fatal < OFF
  • 属性:includeLocation:如果你的layouts或custom过滤器需要location信息,你需要在所有相关日志记录器(包括根日志记录器)的配置中设置“includeLocation=true”,默认为false
  • 子节点:AppenderRef:Root的子节点,用来指定该日志输出到哪个Appender

Logger:用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。Logger元素必须有一个name属性,每个Logger可以使用TRACE,DEBUG,INFO,WARN,ERROR,ALL或OFF之一配置级别。如果未指定级别,则默认为ERROR。可以为additivity属性分配值true或false。如果省略该属性,则将使用默认值true。子节点AppenderRef用于指定日志输出到哪个Appender,若没有指定,默认集成自Root

  • 属性:name:用来指定该Logger所适用的类或包全路径,继承自Root节点。一般是项目或者框架的包名,如:com.xyz、org.springframework
  • 属性:level:日志输出级别,共有8个级别,按照从低到高为:All < Trace < Debug < Info < Warn < Error < Fatal < OFF
  • 属性:additivity:表示需不需要打印此logger的父logger,如果是false则只打印当前logger;如果是true则继续打印上一层的logger,直到root
  • 属性:includeLocation:如果你的layouts或custom过滤器需要location信息,需要在所有相关日志记录器(包括根)中设置includeLocation为true,默认false
  • 子节点:AppenderRef:Logger的子节点,用来指定该日志输出到哪个Appender,如果没有指定,就会默认继承自Root。如果指定了,那么会在指定的这个Appender和Root的Appender中都会输出,此时我们可以设置Logger的additivity="false"只在自定义的Appender中进行输出

AsyncLogger:异步日志,Log4j2有三种模式:全异步日志,混合模式,同步日志,性能从高到底,线程越多效率越高,也可以避免日志卡死线程情况发生

  • includeLocation:如果你的layouts或custom过滤器需要location信息,需要在所有相关日志记录器(包括根)中设置includeLocation为true,默认false
<Configuration>
    
    <Loggers>
        
        
        
        <Logger name="org.springframework" level="INFO">logger>
        <Logger name="org.mybatis" level="INFO">logger>
        <Logger name="com.xyz"  level="false" additivity="false">
            <AppenderRef ref="consoleAppender" />
        Logger>

        
        <Root level="info">
            
            <AppenderRef ref="consoleAppender" />
            <AppenderRef ref="fileAppender" />
        Root>

        
        
        <AsyncLogger name="AsyncLogger" level="trace" includeLocation="true" additivity="false">
            <appender-ref ref="RollingFileError"/>
        AsyncLogger>
    Loggers>
Configuration>

4、XInclude

可以使用这个标签来引入外部xml文件

<configuration status="warn" name="XIncludeDemo">
    <xi:include href="log4j-xinclude-appenders.xml" />
    <xi:include href="log4j-xinclude-loggers.xml" />
configuration>

2、Appenders

参考官网:http://logging.apache.org/log4j/2.x/manual/appenders.html

Appenders是输出源,用于定义日志输出的地方。Log4j2支持的输出源有很多:ConsoleAppender、FileAppender、RandomAccessFileAppender、RollingFileAppender、RollingRandomAccessFileAppender、AsyncAppender 等,如下是 Appender 所有分类(参考官网):

名称 描述
AsyncAppender 使用一个单独的线程记录日志,实现异步处理日志时间
CassandraAppender 将日志信息输出到一个Apache的Cassandre数据库
ConsoleAppender 将日志信息输出到控制台
FailoverAppender 包含其他Appenders,按照顺序尝试,直至成功或结尾
FileAppender 一个OutputStreamAppenders,将日志输出到文件
FlumeAppender 将日志输出到Apache Flume系统
JDBCAppender 将日志通过JDBC输出到关系型数据库
JMSAppender 将日志输出到JMS(Java Message Service)
JPAAppender 将日志输出到JPA框架
HTTPAppender 通过HTTP输出到日志
KafkaAppender 将日志输出到Apache Kafka
MemoryMappedFileAppender 将日志输出到一块文件关联的内存
NoSQLAppender 将日志输出到NoSQL数据库,如MongoDB、CouchDB
OutputStreamAppender 将日志输出到一个OutputStream
RandomAccessFileAppender 性能比FileAppender高20%~200%的文件输出Appender
RewriteAppender 允许对日志信息进行加工掩码输出
RollingFileAppender 按日志文件最大长度限度生成新文件
RollingRandomAccessFileAppender 添加了缓存的RollingFileAppender
RoutingAppender 将日志事件分类,允许通过规则路由日志到不同的输出地(子Appender)
SMTPAppender 将日志输出到邮件
ScriptAppenderSelectorAppender 使用自定义脚本的形式来输出日志
SocketAppender 将日志输出到Socket
SyslogAppender 是一个SocketAppender,将日志输出到远程系统日志
ZeroMQ/JeroMQAppender 使用JeroMQ库将日志输出到ZeroMQ终端

1、Appender常用模板


<Console name="consoleAppender" target="SYSTEM_OUT">
    <PatternLayout pattern="%date %logger %processId %threadId %method %class %file %highlight{%level} : %green{%msg} %n" />
Console>


<File name="fileAppender" fileName="log4j2Study_${date:yyyy-MM}.log">
    <PatternLayout pattern="%date %logger %level : %msg%n" />
File>


<RandomAccessFile  name="randomFileAppender" fileName="log4j2_randomFileAppender.log">
    <PatternLayout pattern="%date %logger %level : %msg%n" />
RandomAccessFile >


<RollingFile name="rollingFileAppender" fileName="log4j2_rollingFileAppender.log"
             filePattern="$${date:yyyy-MM}/%d{MM-dd-yyyy}-%i.log.gz">
    <PatternLayout pattern="%date %logger %level : %msg%n" />
    
    <Policies>
        
        <CronTriggeringPolicy schedule="0 0 * * * ?"/>
        
        <TimeBasedTriggeringPolicy />
        
        <SizeBasedTriggeringPolicy size="100 MB" />
    Policies>

    
    <DefaultRolloverStrategy max="7" />

    
    <DefaultRolloverStrategy>
        
        <Delete>
            
            <IfFileName glob="*.log.gz" />
            
            <IfLastModified age="60d" />
        Delete>
    DefaultRolloverStrategy>
RollingFile>


<RollingRandomAccessFile>RollingRandomAccessFile>


<Async name="fileAsync" >
    <AppenderRef ref="fileAppender" />
Async>


<Cassandra>Cassandra>


<JDBC>JDBC>


<JMS>JMS>


<Kafka>Kafka>


<NoSql>NoSql>


<SMTP>SMTP>


<Socket>Socket>


<Http>Http>


<Rewrite>Rewrite>

2、ConsoleAppender

ConsoleAppender:使用该Appender可以将日志信息输出到控制台,参数与配置如下

Console 元素的属性:

  • name:Appender的名字
  • target:输出方法,SYSTEM_OUT 或 SYSTEM_ERR。默认值:SYSTEM_OUT

Console 元素的节点:

  • PatternLayout:输出格式,有一个pattern属性,不设置默认为:%m%n
  • Filter:设置过滤器,可以为单个Appenders设置,也可以全局设置,后面会详细介绍

<Configuration status="warn" monitorinterval="10" name="myApp">
    <Appenders>
        <Console name="consoleAppender" target="SYSTEM_OUT">
            <PatternLayout pattern="%m%n"/>
        Console>
    Appenders>
    <Loggers>
        <Root level="error">
            <AppenderRef ref="consoleAppender"/>
        Root>
    Loggers>
Configuration>

3、FileAppender

FileAppender:用于将LogEvent写入到一个文件中,是文件输出源,用于将日志写入到指定的文件,其底层是一个OutputStreamAppender

File 元素的属性

  • name:Appender的名字
  • fileName:指定写入的log文件的名称
  • append:指定是否是追加写入(append=true,默认情况),还是覆盖写入(append=false)
  • bufferedIO:是否对数据进行缓冲到缓冲区满后再写入。测试显示,即使在启用immediateFlush的情况下,设置bufferedIO=true也能提高性能。
  • bufferSize:当bufferedIO=true时,缓冲区的大小,默认是8192bytes
  • locking:是否对文件上锁,当有多个线程可能同时写该文件时需要考虑上锁(在《异常处理反模式》中就提到要把在一起的日志输出语句写到一句,而不是拆成几句来避免并发线程导致的日志语句之间的错位)。但对文件上锁会影响系统的性能,所以需要谨慎使用。默认值是false
  • immediateFlush:是否立即将数据刷入磁盘,能够最大程度保证数据的完整性,但是对性能会有一定的影响,默认值为true

File 元素的节点

  • PatternLayout:用于指定输出格式,不设置的话,默认为:%m%n
  • Filter:设置过滤器,可以为单个Appenders设置,也可以全局设置,后面会详细介绍

<Configuration status="warn" monitorinterval="10" name="myApp">
    <Appenders>
        <File name="fileAppender" fileName="logs/app.log" append="true">
            <PatternLayout>
                <Pattern>%d %p %c{1.} [%t] %m%nPattern>
            PatternLayout>
        File>
    Appenders>
    <Loggers>
        <Root level="error">
            <AppenderRef ref="fileAppender"/>
        Root>
    Loggers>
Configuration>

4、RandomAccessFileAppender

RandomAccessFile参考:https://www.cnblogs.com/jyy599/p/12076662.html

与FileAppender很相似,除了将BufferdOutputStream替换为了ByteBuffer + RandomAccessFile,还使用了缓存,它的性能比FileAppender高20%~200%

RandomAccessFile 元素的属性

  • 配置基本上与FileAppender相同,见上文,主要区别在于bufferSize
  • bufferSize:缓存空间大小,默认为256M

RandomAccessFile 元素的节点

  • PatternLayout:用于指定输出格式,不设置的话,默认为:%m%n
  • Filter:设置过滤器,可以为单个Appenders设置,也可以全局设置,后面会详细介绍

<Configuration status="warn" monitorinterval="10" name="myApp">
    <Appenders>
        <RandomAccessFile name="randomAccessFile" fileName="logs/app.log">
            <PatternLayout>
                <Pattern>%d %p %c{1.} [%t] %m%nPattern>
            PatternLayout>
        RandomAccessFile>
    Appenders>
    <Loggers>
        <Root level="error">
            <AppenderRef ref="randomAccessFile"/>
        Root>
    Loggers>
Configuration>

5、RollingFileAppender(最常用)

RollingFile用于实现日志文件动态更新的Appender,当满足条件(日志大小、指定时间等)重命名或打包原日志文件进行归档生成新文件,比File更强大

RollingFileAppender是一个OutputStreamAppender,先写入指定文件,并根据TriggeringPolicy和RolloverPolicy滚动文件。RollingFile需要两个策略的支持,分别是TriggeringPolicy和RolloverPolicy。TriggeringPolicy定义何时应该生成新的日志文件而RolloverPolicy则决定如何生成新的日志文件。RollingFile不支持文件锁

RollingFile 元素的属性:

  • name:用于指定Appender的名称
  • fileName:用于指定日志文件的全路径
  • filePattern:用于指定分割文件的日志全路径(命名规则)

RollingFile 元素的节点:

  • PatternLayout:用于指定输出格式,不设置的话,默认为:%m%n`
  • Policies :设置日志文件切割参数
    • OnStartupTriggeringPolicy:Policies的子节点,每次JVM启动,都滚动到新的日志文件开始记录,一般不怎么使用
    • SizeBasedTriggeringPolicy:Policies的子节点,按照日志文件大小进行触发滚动策略,size属性表示分割文件大小,单位 KB、MB、GB 或 TB
    • TimeBasedTriggeringPolicy:Policies的子节点,根据日期时间间隔触发进行滚动,interval属性用于指定滚动时间间隔,默认是1小时,modulate属性是用于对interval进行偏移调节,默认为false。若为true,则第一次触发时是第一个小时触发,后续以interval间隔触发
    • CronTriggeringPolicy:Policies的子节点,用于设置基于Cron表达式触发的滚动策略
    • 等等还有许多,后面会单独介绍,也可以参考官网
  • DefaultRolloverStrategy:设置默认策略设置
  • Filter:设置过滤器,可以为单个Appenders设置,也可以全局设置,后面会详细介绍

如下配置:有三个Trigger,表示每秒生成一个文件或者每小时生成一个文件或者当文件大小达到250MB时生成一个新的文件;有两个Strategy,表示最多保持文件数量为10,达到最大值后,旧的文件将被删除,或者文件名符合.log.gz后缀 or 超过60天 也会删除旧文件。


<Configuration status="warn" monitorinterval="0" name="myApp">
    <Appenders>
        <RollingFile name="rollingFile"
                     fileName="logs/app.log"
                     filePattern="logs/app-%d{yyyy-MM-dd_HH-mm-ss}-%i.log.zip">
            <PatternLayout pattern="%d %p %c{1.} [%t] %m%n"/>
            
            <Policies>
                
                
                <TimeBasedTriggeringPolicy />
                
                <CronTriggeringPolicy schedule="0 0 * * * ?"/>
                
                <SizeBasedTriggeringPolicy size="250 MB"/>
            Policies>
            
            <DefaultRolloverStrategy max="10"/>
            
            <DefaultRolloverStrategy>
                
                <Delete basePath="logs/" maxDepth="2">
                    
                    <IfFileName glob="logs/*.log.zip"/>
                    
                    <IfLastModified age="60d"/>
                Delete>
            DefaultRolloverStrategy>
        RollingFile>
    Appenders>
    <Loggers>
        <Root level="error">
            <AppenderRef ref="rollingFile"/>
        Root>
    Loggers>
Configuration>

如下单独使用三个示例进行详细讲解:

(1)基于大小的滚动策略

日志先写入logs/app.log中,每当文件大小达到100MB时或经过1天,按照在logs/2020-09/目录下以app-2020-09-09-1.log.gz格式对该日志进行压缩重命名并归档,并生成新的文件app.log进行日志写入。其中,filePattern属性的文件格式中%i就类似于一个整数计数器,受到控制,要特别注意的是:当文件个数达到10个的时候会循环覆盖前面已归档的1-10个文件。若不设置该参数,默认为7。


<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%nPattern>
            PatternLayout>
            <Policies>
                <TimeBasedTriggeringPolicy />
                <SizeBasedTriggeringPolicy size="100 MB"/>
            Policies>
            <DefaultRolloverStrategy max="10"/>
        RollingFile>
    Appenders>
    <Loggers>
        <Root level="error">
            <AppenderRef ref="RollingFile"/>
        Root>
    Loggers>
Configuration>

(2)基于时间间隔的滚动策略

日志先写入logs/app.log中,每当文件的时间间隔到达6小时(由%d{yyyy-MM-dd-HH}决定,也可以设置成%d{yyyy-MM-dd-HH-mm},则间隔为分钟级别),触发rollover操作。如下配置设置好后,10点的日志开始重启服务,则从11点触发一次rollover操作,生成2022-04-10-10.log.gz对该日志进行压缩重命名并归档,并生成新的文件app.log进行日志写入;然后,每间隔6小时,则下一次是17点触发一次,生成2022-04-10-17.log.gz对该日志进行压缩重命名并归档,并生成新的文件app.log进行日志写入。


<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}.log.gz">
            <PatternLayout>
                <Pattern>%d %p %c{1.} [%t] %m%nPattern>
            PatternLayout>
            <Policies>
                
                
                <TimeBasedTriggeringPolicy interval="6" modulate="true"/>
            Policies>
        RollingFile>
    Appenders>
    <Loggers>
        <Root level="error">
            <AppenderRef ref="RollingFile"/>
        Root>
    Loggers>
Configuration>

(3)基于时间间隔和文件大小的滚动策略

日志先写入logs/app.log中,每当文件大小达到100MB或者当时间间隔到达6小时(由%d{yyyy-MM-dd-HH}决定),触发rollover操作,按照在logs/2022-04/目录下以app-2022-04-1-1.log.gz格式对该日志进行压缩重命名并归档,并生成新的文件app.log进行日志写入。


<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%nPattern>
            PatternLayout>
            <Policies>
                <TimeBasedTriggeringPolicy interval="6" modulate="true"/>
                <SizeBasedTriggeringPolicy size="100 MB"/>
            Policies>
        RollingFile>
    Appenders>
    <Loggers>
        <Root level="error">
            <AppenderRef ref="RollingFile"/>
        Root>
    Loggers>
Configuration>

6、RollingRandomAccessFileAppender

RollingRandomAccessFile类似于标准的RollingFile,除了它总是被缓冲(且不能被关闭),并且在内部它使用 ByteBuffer + RandomAccessFile 而不是BufferedOutputStream。与RollingFileAppender相比,当 bufferedIO = true 时,性能提升 20-200%。

RollingFile 元素的属性:

  • name:指定Appender的名字。
  • fileName 指定当前日志文件的位置和文件名称
  • filePattern 指定当发生Rolling时,文件的转移和重命名规则
  • immediateFlush 设置为true时 - 默认值,每次写入后都会进行刷新。这将保证数据写入磁盘,但可能会影响性能。
  • bufferSize 缓冲区大小,默认为256M 或 262,144字节(256 * 1024)

RollingFile 元素的节点:

  • PatternLayout:用于指定输出格式,不设置的话,默认为:%m%n`
  • Policies:指定滚动日志的策略,就是什么时候进行新建日志文件输出日志
    • SizeBasedTriggeringPolicy:指定当文件大小大于size指定的值时,触发Rolling
    • TimeBasedTriggeringPolicy:这个配置需要和filePattern结合使用,日期格式精确到哪一位,interval也精确到哪一个单位。注意filePattern中配置的文件重命名规则是${FILE_NAME}-%d{yyyy-MM-dd HH-mm-ss}-%i,最小的时间粒度是ss,即秒钟。TimeBasedTriggeringPolicy默认的size是1,结合起来就是每1秒钟生成一个新文件。如果改成%d{yyyy-MM-dd HH},最小粒度为小时,则每一个小时生成一个文件
  • DefaultRolloverStrategy:指定最多保存的文件个数
  • Filter:设置过滤器,可以为单个Appenders设置,也可以全局设置,后面会详细介绍

<RollingRandomAccessFile>RollingRandomAccessFile>

7、AsyncAppender(异步)

异步输出日志,默认使用 java.util.concurrent.ArrayBlockingQueue 来实现的,不需要其他的外部库,这个配置在多线程的情况下需要小心,可能会出现锁竞争,如果在多线程下,可以使用Disruptor库(一个无锁的线程间通信库,而不是队列,从而产生更高的吞吐量和更低的延迟),可以使用asyncRoot或者asyncLogger来进行配置。

<Async name="Async">
    <AppenderRef ref="RollingFileAppender" />
    <AppenderRef ref="Console" />
    <LinkedTransferQueue/>
Async>

3、Filters

官网地址:https://logging.apache.org/log4j/2.x/manual/filters.html

Filter用于过滤日志(是可选的)Log4j2会在日志产生时自动调用预先配置的Filter的filter方法进行过滤,以便获得是否允许打印的标识。

是否允许打印的标识是一个 Result 类型的枚举,他的值有三种:ACCEPT、DENY、NEUTRAL

  • ACCEPT:(直接接受)日志会被立即处理,不再经过剩余过滤器
  • DENY:(直接拒绝)日志将立即被抛弃不再经过其他过滤器
  • NEUTRAL:(中立,不做处理,交由后面代码处理)有序列表里的下个过滤器过接着处理日志

重点讲解 NEUTRAL,如果只有一个 Filter,那么 NEUTRAL 与 ACCEPT 没有任何区别,只有在多个 Filter 级联使用时,NEUTRAL 才有意义,他表示由下一个 filter 决定是否 ACCEPT。

通常 filter 并不直接决定最终的结果,因为不同的场景下,filter 命中后的行为并不一定相同,因此 filter 只返回命中或未命中,然后由业务具体需要决定是否允许打印相应的日志是更好的选择。Log4j2 的 Filter 就是基于上述原则创建的,他提供了 onMatch(命中)与 onMisMatch(未命中)两个参数供用户配置,filter 值返回当前场景命中或未命中

Log4j2 允许你将 Filter 配置为全局有效或对某个 Appender 生效。

1、Filters常用模板

常用的过滤器如下几类,解析请查看注释


<BurstFilter level="info" rate="16" maxBurst="100"  onMatch="NEUTRAL" onMismatch="DENY"  />


<DynamicThresholdFilter key="tcKey" defaultThreshold="info" onMatch="ACCEPT" onMismatch="DENY" >
    
    <KeyValuePair key="tcVal1" value="info" />
    <KeyValuePair key="tcVal2" value="error" />
DynamicThresholdFilter>


<MapFilter onMatch="ACCEPT" onMismatch="DENY" >
    <KeyValuePair key="mmKey1" value="mmVal1" />
    <KeyValuePair key="mmKey2" value="mmVal2" />
MapFilter>


<MarkerFilter marker="mk1" onMatch="ACCEPT" onMismatch="DENY" />


<RegexFilter regex=".*or.*" onMatch="ACCEPT" onMismatch="DENY" />


<ContextMapFilter onMatch="ACCEPT" onMismatch="DENY" operator="or">
    
    <KeyValuePair key="tcKey" value="tcVal1" />
ContextMapFilter>


<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY" />


<TimeFilter start="00:00:00" end="01:00:00" onMatch="ACCEPT" onMismatch="DENY" />


<Filters>Filters>

2、BurstFilter

BurstFilter 可以控制每秒日志量,对于超过数量的日志进行丢弃。实际就是:控制日志打印速度

  • level:筛选消息级别。如果超过maxBurst,则将过滤掉此级别或以下的任何内容。默认WARN,任何高于WARN的日志都被记录,无论Burst大小如何
  • rate:参数表示每秒最大日志数
  • maxBurst:参数则表示在开始过滤前允许多少条日志请求,默认是rate的10倍
  • onMatch:过滤器匹配时要采取的操作。可以是ACCEPT、DENY、NEUTRAL,默认是NEUTRAL
  • onMismatch:当过滤器不匹配时要采取的操作。可以是ACCEPT、DENY、NEUTRAL,默认是DENY
<RollingFile name="RollingFile" fileName="logs/app.log" filePattern="logs/app-%d{MM-dd-yyyy}.log.gz">
    
    <BurstFilter level="INFO" rate="16" maxBurst="100"/>
    <PatternLayout pattern="%d %p %c{1.} [%t] %m%n" />
    <TimeBasedTriggeringPolicy />
RollingFile>

3、TimeFilter

TimeFilter 是限制时间的Filter,允许只在一天中的指定时间进行日志记录:

  • start:开始时间,格式为:HHss
  • end:结束时间,格式为:HHss。如果结束时间小于开始时间,那么将没有日志输出
  • onMatch:过滤器匹配时要采取的操作。可以是ACCEPT、DENY、NEUTRAL,默认是NEUTRAL
  • onMismatch:当过滤器不匹配时要采取的操作。可以是ACCEPT、DENY、NEUTRAL,默认是DENY
<RollingFile name="RollingFile" fileName="logs/app.log" filePattern="logs/app-%d{MM-dd-yyyy}.log.gz">
    <TimeFilter start="05:00:00" end="05:30:00" onMatch="ACCEPT" onMismatch="DENY"/>
    <PatternLayout pattern="%d %p %c{1.} [%t] %m%n" />
    <TimeBasedTriggeringPolicy />
RollingFile>

4、MarkerFilter

MarkerFilter:过滤不同类型的日志。有的时候,我们希望根据日志中的标记来决定不同的日志输出到不同的位置。MarkerFilter 实现了这一功能:

  • marker:要比较的标记名称
  • onMatch:过滤器匹配时要采取的操作。可以是ACCEPT、DENY、NEUTRAL,默认是NEUTRAL
  • onMismatch:当过滤器不匹配时要采取的操作。可以是ACCEPT、DENY、NEUTRAL,默认是DENY
<RollingFile name="RollingFile" fileName="logs/app.log" filePattern="logs/app-%d{MM-dd-yyyy}.log.gz">
    
    <MarkerFilter marker="FLOW" onMatch="ACCEPT" onMismatch="DENY"/>
    <PatternLayout pattern="%d %p %c{1.} [%t] %m%n" />
    <TimeBasedTriggeringPolicy />
RollingFile>
// MarkerFilter中使用
org.apache.logging.log4j.Logger log4j = org.apache.logging.log4j.LogManager.getLogger(Log4j2Test.class);
Marker mk = MarkerManager.getMarker("mk1");
logger.info(mk,"info log by markerFilter");

5、MapFilter

MapFilter允许对MapMessage中的数据元素进行过滤

  • operator:如果配置为or,那么只要有一个匹配就可以了。否则所有的key/value都要匹配
  • onMatch:过滤器匹配时要采取的操作。可以是ACCEPT、DENY、NEUTRAL,默认是NEUTRAL
  • onMismatch:当过滤器不匹配时要采取的操作。可以是ACCEPT、DENY、NEUTRAL,默认是DENY
  • keyValuePair 子元素:可以定义一个或多个KeyValuePair元素,这些元素定义映射中的键和要匹配的值。如果同一个key被多次指定,那么对该key的检查将自动成为“or”,因为映射只能包含一个值

<MapFilter onMatch="ACCEPT" onMismatch="NEUTRAL" operator="or">
    <KeyValuePair key="eventId" value="Login"/>
    <KeyValuePair key="eventId" value="Logout"/>
MapFilter>
<Root level="error">
    <MapFilter onMatch="ACCEPT" onMismatch="NEUTRAL" operator="or">
        <KeyValuePair key="eventId" value="Login"/>
        <KeyValuePair key="eventId" value="Logout"/>
    MapFilter>
    <AppenderRef ref="RollingFile">
    AppenderRef>
Root>
<Root level="error">
    <AppenderRef ref="RollingFile">
        <MapFilter onMatch="ACCEPT" onMismatch="NEUTRAL" operator="or">
            <KeyValuePair key="eventId" value="Login"/>
            <KeyValuePair key="eventId" value="Logout"/>
        MapFilter>
    AppenderRef>
Root>
// MapFilter中使用
org.apache.logging.log4j.Logger log4j = org.apache.logging.log4j.LogManager.getLogger(Log4j2Test.class);
MapMessage mm = new MapMessage();
mm.put("mmKey1","mmVal1");
mm.put("val","this info msg");
logger.info(mm);

6、DynamicThresholdFilter

DynamicThresholdFilter:动态日志级别设置。很多时候,我们需要借助更多的日志来进行问题排查,但过多的日志又势必会对线上服务的性能以及磁盘等资源造成压力,此时有一个好的选择,那就是打印丰富的 debug 级别的日志,而 logger 的 level 至少定义在 info 级别以上,这样实际上在生产环境中,这些 debug 级别的日志并不会被打印出来,而在测试环境中,只需要改变 logger 的 level 就可以打开这些 debug 日志的打印,方便我们排查问题。

有时我们更想要知道线上场景下究竟发生了什么,但现实情况我们又不能让所有人都打印出 debug 级别的日志,有什么办法只让符合条件的请求打印出 debug 级别的日志吗?log4j2 用 DynamicThresholdFilter 解决了这一问题。

  • key:去ThreadContext中比较的key
  • defaultThreshold:需要被过滤的消息等级。当指定的key不在ThreadContext中时,使用该配置。
  • onMatch:过滤器匹配时要采取的操作。可以是ACCEPT、DENY、NEUTRAL,默认是NEUTRAL
  • onMismatch:当过滤器不匹配时要采取的操作。可以是ACCEPT、DENY、NEUTRAL,默认是DENY
  • keyValuePair 子元素:可以定义多个KeyValuePair属性。通过该属性可以对指定用户设置日志级别。KeyValuePair的key为ThreadContext中获取的value值,KeyValuePair的value为日志的级别,如


<DynamicThresholdFilter key="loginId" defaultThreshold="ERROR" onMatch="ACCEPT" onMismatch="NEUTRAL">
    <KeyValuePair key="user1" value="DEBUG"/>
    <KeyValuePair key="user2" value="INFO"/>
DynamicThresholdFilter>
// 定义ThreadContext值,可用于在生产环境中,对指定用户或场景进行跟踪
org.apache.logging.log4j.Logger log4j = org.apache.logging.log4j.LogManager.getLogger(Log4j2Test.class);
ThreadContext.put("loginId", "user1");
logger.debug("{} log", "info1");
ThreadContext.put("loginId", "user2");
logger.info("{} log", "info2");

这个配置表示:默认日志级别为 ERROR 级别,但符合ThreadContext.get("loginId")MDC.get("loginId") 为 user1 的请求日志级别为 DEBUG。这样,我们只需要在日志打印前执行ThreadContext.put("loginId", "user1")MDC.put("loginId", "user1") 就可以实现动态改变本次请求的日志级别了,这对于线上 vip 用户问题的排查是十分方便的

7、CompositeFilter

CompositeFilter 复合过滤器提供了一种指定多个过滤器的方法。他以Filters元素加入到配置中,元素里面可以配置多个过滤器。该元素不支持添加参数

<Filters>
    <Marker marker="EVENT" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
    <DynamicThresholdFilter key="loginId" defaultThreshold="ERROR" onMatch="ACCEPT" onMismatch="NEUTRAL">
        <KeyValuePair key="user" value="DEBUG"/>
    DynamicThresholdFilter>
Filters>

8、OtherFilter

  • StringMatchFilter:如果格式化后(即:最终)的日志信息中包含${指定的字符串},则onMatch,否则onMismatch
    即: msg.contains(this.text) ? onMatch : onMismatch;
  • LevelRangeFilter:若${maxLevel} <= 日志级别 <= ${minLevel}, 则onMatch,否则onMismatch
    如: 即为只记录日志info及warn级别的日志
  • RegexFilter:如果日志信息匹配${指定的正则表达式},则onMatch,否则onMismatch
    注:可通过useRawMsg属性来控制这个日志信息是格式化处理后(即:最终)的日志信息,还是格式化处理前(即:代码中输入)的日志信息
  • ThresholdFilter:若日志级别 >= ${指定的日志级别}, 则onMatch,否则onMismatch
  • LevelMatchFilter:如果日志级别等于${指定的日志级别},则onMatch,否则onMismatch
  • ThreadContextMapFilter:通过context(可以理解为一个Map)中对应的key-value值进行过滤。注:上下文默认是ThreadContext,也可以自定义使用ContextDataInjectorFactory配置ContextDataInjector来指定
  • DynamicThresholdFilter:若上下文中包含指定的key,则触发DynamicThresholdFilter生效;若该key对应的value值等于任意一个我们指定的值,那么针对本条日志,可记录日志级别的约束下限调整为指定的级别。注:上下文默认是ThreadContext,也可以自定义使用ContextDataInjectorFactory配置ContextDataInjector来指定
  • CompositeFilter:组合过滤器,即:按照xml配置中的配置,一个过滤器一个过滤器的走,如果在这过程中,任意一个过滤器ACCEPT或DENY了,那么就不会往后走了,直接返回对应的结果
  • TimeFilter:如果记录日志时的当前时间落在每天指定的时间范围[start, end]内,则onMatch,否则onMismatch
    如:
  • ScriptFilter:是否匹配取决于指定的脚本返回值是否为true
  • DenyAllFilter:This filter causes all logging events to be dropped
  • BurstFilter:对低于或等于${指定日志级别}的日志,进行限流控制
  • NoMarkerFilter:如果从对应事件对象获取(LogEvent#getMarker)到的marker为null, 则onMatch,否则onMismatch
  • MarkerFilter:如果从对应事件对象获取(LogEvent#getMarker)到的marker为null, 则onMatch,否则onMismatch
  • MapFilter:如果从对应事件对象获取(LogEvent#getMarker)到的marker的name值为等于${指定的值}, 则onMatch,否则onMismatch
  • The MapFilter allows filtering against data elements that are in a MapMessage.注:需要使用org.apache.logging.log4j.Logger进行记录,且记录org.apache.logging.log4j.message.MapMessage日志,才会生效。注:因为暂时不兼容Slf4j这里不多作说明
  • StructuredDataFilter:The StructuredDataFilter is a MapFilter that also allows filtering on the event id, type and message.注:需要使用org.apache.logging.log4j.Logger进行记录,且记录
    org.apache.logging.log4j.core.filter.StructuredDataFilter日志,才会生效。注:因为暂时不兼容Slf4j这里不多作说明

4、Layout

官网地址:http://logging.apache.org/log4j/2.x/manual/layouts.html

1、PatternLayout

PatternLayout:用于指定输出日志的格式,控制台或文件输出源(Console、File、RollingRandomAccessFile)都必须包含一个PatternLayout节点,用于指定输出的格式(如 日志输出的时间 文件 方法 行数 等格式),例如 pattern="%d{yyyy-MM-dd HH:mm:ss,SSS} [%t] %-5level %logger{0} - %msg%n"


<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss,SSS} [%t] %-5level %logger{0} - %msg%n" charset="UTF-8"/>

<PatternLayout charset="UTF-8">
    <Pattern>%d{yyyy-MM-dd HH:mm:ss,SSS} [%t] %-5level %logger{0} - %msg%nPattern>
PatternLayout>

具体详情可以参考官网或本人的Logback中的记录:

Conversation Word Effect
变量名
%logger、%c 当前日志名称,如: Slf4jAndLogbackMain
%class、%C 日志调用所在类,如: com.dragon.study.log.Slf4jAndLogbackMain
%file、%F 日志调用所在文件,如: com.dragon.study.log.Log4j2Main Log4j2Main.java
%method、%M 日志所在方法,如: main
%thread、%t 日志调用所有线程序,如:main
%level、%p 日志级别,如:INFO
%date、%d 日期,如: 2018-12-15 21:40:12,890
%msg、%m 日志记录内容
%processId、%pid 进程号
%threadId、%tid 线程号
%exception、%ex 异常记录
%n 行分隔
特殊占位符
%X{user} 表示可以获取外部自定义传入的值, 如:%X{user}=》org.slf4j.MDC.put("user", "xx-yy-zz");
宽度设置 可以限制上面的ConversationWord的宽度和左右对齐
%20logger 最小宽度20,当小于20时,则左侧留空白。右对齐
%-20logger 最小宽度20,当小于20时,则右侧留空白。左对齐
%.30logger 最大宽度30,超出时从头部开始截断。如:%.2、test=》st
%.-30logger 最大宽度30,超出时从末尾开始截断。如:%.-2、test=》te
%20.30logger 最小20,最大30,小于20的时候右对齐,大于30的时候开始处截断
%-20.30logger 最小20,最大30,小于20的时候左对齐,大于30的时候开始处截断
显示设置
%highlight() 突出显示,如:%highlight(%-5level)
%green(%red、%blue、%white) 字体显示为指定颜色
{length} 可指定长度,如:%logger{36}

2、JSONLayout

JSONLayout 表示日志以JSON的格式输出,如下是常用参数,其他参数请参考官网

  • complete:如果配置了complete=“true”,那么JSON将以完整的形式输出,默认为false
  • compact:是否对JSON进行压缩,如果为 true,则附加程序不使用行尾和缩进。 默认为false
  • eventEol:如果为 true,则 appender 在每条记录之后附加一个行尾。 默认 false。与 eventEol=true 和 compact=true 一起使用以获取每行一条记录
  • locationInfo:如果为 true,则附加程序在生成的 JSON 中包含位置信息。 默认 false
<RollingFile name="RollingFile" fileName="logs/app.log" filePattern="logs/app-%d{MM-dd-yyyy}.log.gz">
    <JsonLayout />
    <TimeBasedTriggeringPolicy />
RollingFile>
{
  "timeMillis" : 1550798980964,
  "thread" : "main",
  "level" : "INFO",
  "loggerName" : "com.cimu.Log4j2Test",
  "message" : "log4j2",
  "endOfBatch" : true,
  "loggerFqcn" : "org.apache.logging.log4j.spi.AbstractLogger"
}
<RollingFile name="RollingFile" fileName="logs/app.log" filePattern="logs/app-%d{MM-dd-yyyy}.log.gz">
    <JSONLayout complete="true" compact="true" eventEol="true" locationInfo="true" />
    <TimeBasedTriggeringPolicy />
RollingFile>
{"timeMillis":1550799247113,"thread":"main","level":"INFO","loggerName":"com.cimu.Log4j2Test","message":"log4j2","endOfBatch":true,"loggerFqcn":"org.apache.logging.log4j.spi.AbstractLogger"}

注意:提醒使用JsonLayout是需要依赖Jackson包的(如何使使用SpringBoot的话web中自带了Jackson包)

3、OtherLayout

还有其他Lyaout就不一一介绍了,具体用到可以参考官网

  • CSVLayouts:以逗号分隔值(CSV)记录日志,需要Apache Commons CSV 1.4及以上。可以使用CsvParameterLayout或CsvLogEventLayout来配置
  • GELFLayout:以扩展的日志格式(GELF)显示日志
  • HTMLLayout:以HTML的形式输出
  • JSONLayout:以JSON格式输出
  • PatternLayout:以自定义格式输出
  • RFC5424Layout:以标准的RFC5424进行日志输出
  • SerializedLayout:以JAVA序列进行日志输出
  • SyslogLayout:以BSD Syslog进行日志输出
  • XMLLayout:以XML格式输出
  • YAMLLayout:以YAML格式输出

5、Policy & Strategy

Log4j2的Policy触发策略与Strategy滚动策略配置详解

1、Policy触发策略

  • SizeBasedTriggeringPolicy:基于日志文件大小的触发策略。单位有:KB,MB,GB
  • CronTriggeringPolicy:基于Cron表达式的触发策略,很灵活
  • TimeBasedTriggeringPolicy:基于时间的触发策略。该策略主要是完成周期性的log文件封存工作
    1. interval:integer型,指定两次封存动作之间的时间间隔。这个配置需要和filePattern结合使用,filePattern日期格式精确到哪一位,interval也精确到哪一个单位。注意filePattern中配置的文件重命名规则是%d{yyyy-MM-dd HH-mm-ss}-%i,最小的时间粒度是ss,即秒钟。
      TimeBasedTriggeringPolicy默认的size是1,结合起来就是每1秒钟生成一个新文件。如果改成%d{yyyy-MM-dd HH},最小粒度为小时,则每一个小时生成一个文件
    2. modulate:boolean型,说明是否对封存时间进行调制。若modulate=true, 则封存时间将以0点为边界进行偏移计算。比如,modulate=true,interval=4hours, 那么假设上次封存日志的时间为03:00,则下次封存日志的时间为04:00, 之后的封存时间依次为08:00,12:00,16:00
  • OnStartupTriggeringPolicy:每次JVM启动,都滚动到新的日志文件开始记录
  • 等等其他Policy,可以参考官网

2、Strategy滚动策略

  • DefaultRolloverStrategy:默认滚动策略。常用参数:max,保存日志文件的最大个数,默认是7,大于此值会删除旧的日志文件
  • DirectWriteRolloverStrategy:日志直接写入由文件模式表示的文件

这两个Strategy都是控制如何进行日志滚动的,平时大部分用DefaultRolloverStrategy就可以了

5、Log4j2异步日志

官网详细介绍:http://logging.apache.org/log4j/2.x/performance.html

Log4j2最大的特点就是异步日志,其性能的提升主要也是从异步日志中受益。上述学习日志默认都是同步的,同步就是当输出日志时,必须等待日志输出语句执行完毕后,才能执行后面的业务逻辑语句。异步日志就是分配单独的线程做日志记录。

  • 同步日志:

    请求进入 »»» 全局filter进行Level过滤 »»» Logger自身filter进行Level过滤 »»» 生成日志输出Message
                                                                                   ↓              
    输出日志 ««« PatternLayout格式化LogEvent ««« Logger自身filter进行Level过滤 ««« 生成Logevent
    
  • 异步日志:

    请求进入 »»» 全局filter进行Level过滤 »»» Logger自身filter进行Level过滤 »»» 生成日志输出Message
              输出日志   «««   Appender ──┐                                        ↓
              输出日志   «««   Appender ──┤   «««  BlockingQueue  «««  AsyncAppender
              输出日志   «««   Appender ──┘
    

1、异步组件简介

Log4j2 异步提供了两种实现日志的方式,一个是通过AsyncAppender【几乎没人用】,一个是通过AsyncLogger【主要是这个】,分别对应前面我们说的Appender组件和Logger组件。【注意:AsyncLogger是基于Disruptor实现,所以必须引入com.lmax.disruptor依赖】

这是两种不同的实现方式,在设计和源码上都是不同的体现,可以将这两种实现日志的方式看做完全不一样。

Log4j2 异步日志在如上两种实现方式的基础上,又分为全局异步和混合异步2种方式

  • 全局异步:所有日志都异步记录
  • 混合异步:在应用种同时使用同步日志和异步日志,这样的日志配置更加灵活

1、AsyncAppender 方式(实际中用的少,因为性能低下)

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

2、AsyncLogger 方式(实际中用的多,因为性能高)

  • AsyncLogger才是log4j2实现异步最重要的功能体现,也是官方推荐的异步方式
  • 它可以使调用Logger.log返回更快。其中包括全局异步和混合异步
  • 全局异步:所有的日志都异步的记录,在配置文件上下不用做任何的改动,只需要在jvm启动的时候增加一个参数即可实现,实际开发中使用较少。
  • 混合异步:你可以同时在应用中使用同步日志和异步日志,这使得日志的配置方式更加的灵活。混合日志需要修改配置文件来实现,使用AsyncLogger标记配置,实际开发中使用较多。

2、AsyncAppender

AsyncAppender :性能提升较小,不推荐使用

1、全局异步实现

使用AsyncAppender方式实现的全局异步日志输出

1、在 log4j.xml配置文件的 Appenders标签中,对于异步进行配置,使用Async标签


<Configuration status="warn" monitorinterval="10" name="myApp">
    <Appenders>
        <Console name="consoleAppender" target="SYSTEM_OUT">
            <PatternLayout pattern="%m%n"/>
        Console>
        
        <Async name="asyncConsole">
            
            <AppenderRef ref="consoleAppender"/>
        Async>
    Appenders>
    <Loggers>
        <Root level="error">
            <AppenderRef ref="asyncConsole"/>
        Root>
    Loggers>
Configuration>

2、编写测试代码,测试全局异步输出日志信息

package com.xyz;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Log4j2Test {
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger(Log4j2Test.class);
        // 日志的记录
        for (int i = 0; i < 3; i++) {
            logger.error("error信息");
            logger.warn("warn信息");
            logger.info("info信息");
            logger.debug("debug信息");
            logger.trace("trace信息");
        }
        // 系统业务逻辑
        for (int i = 0; i < 3; i++) {
            // 这里必须使用System.out输出,如果单独使用logger.xx输出看不出来效果
            logger.error("-----start-----");
            System.out.println("-----sys-----"); // 实际上只有这一行是业务代码
            logger.error("-----end-----");
        }
    }
}

3、输出结果(观察结果可以发现,此时实现了异步日志的输出)

-----sys-----
-----sys-----
-----sys-----
error信息
error信息
error信息
-----start-----
-----end-----
-----start-----
-----end-----
-----start-----
-----end-----

4、实际上可以得出结论:Log4j2的异步只是把打印日志单独提出一个线程(日志的打印还是顺序的),而业务的代码在主线程上先执行了。

2、混合异步实现

使用AsyncAppender方式实现同时在应用中使用同步日志和异步日志输出。

1、在上面的基础上修改log4j.xml配置文件,自定义Logger-com.xyz,让其引用同步Appender,rootLogger依旧为异步的AsyncAppender


<Configuration status="warn" monitorinterval="10" name="myApp">
    <Appenders>
        <Console name="consoleAppender" target="SYSTEM_OUT">
            <PatternLayout pattern="%m%n"/>
        Console>
        
        <Async name="asyncConsole">
            <AppenderRef ref="consoleAppender"/>
        Async>
    Appenders>
    <Loggers>
        
        <Logger name="com.xyz" level="error" includeLocation="false" additivity="false">
            
            <AppenderRef ref="consoleAppender"/>
        Logger>
        <Root level="error">
            
            <AppenderRef ref="asyncConsole"/>
        Root>
    Loggers>
Configuration>

2、编写测试代码,测试混合异步(同步和异步同时存在)输出日志信息(实际只测试了同步,想测试异步修改Logger对象包即可)

package com.xyz;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Log4j2Test {
    public static void main(String[] args) {
        // 当前测试的是自定义的com.xyz的同步logger,如果想测试异步只需修改成其他名称即可
        Logger logger = LoggerFactory.getLogger(Log4j2Test.class);
        // 日志的记录
        for (int i = 0; i < 3; i++) {
            logger.error("error信息");
            logger.warn("warn信息");
            logger.info("info信息");
            logger.debug("debug信息");
            logger.trace("trace信息");
        }
        // 系统业务逻辑
        for (int i = 0; i < 3; i++) {
            // 这里必须使用System.out输出,如果单独使用logger.xx输出看不出来效果
            logger.error("-----start-----");
            System.out.println("-----sys-----"); // 实际上只有这一行是业务代码
            logger.error("-----end-----");
        }
    }
}

3、输出结果(观察结果可以发现,此时打印结果是按顺序执行的,这就代表实现了混合异步输出,这里没有重复再次测试异步输出,可自行测试)

error信息
error信息
error信息
-----start-----
-----sys-----
-----end-----
-----start-----
-----sys-----
-----end-----
-----start-----
-----sys-----
-----end-----

3、AsyncLogger

AsyncLogger:性能提升较大,推荐使用

1、全局异步实现

1、全局异步 :所有的日志都是异步的日志记录,在配置文件上不用做任何的改动。有如下三种配置方式。

  1. 全局启用异步Logger方案一 :JVM启动加上如下参数

    -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
    
  2. 全局启用异步Logger方案二:classpath中添加文件log4j2.component.properties,文件增加以下内容:

    Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
    
  3. 全局启用异步Logger方案三:如果是SpringBoot项目可以改为在启动类的地方修改

    @SpringBootApplication
    public class Log4j2Application {
        public static void main(String[] args) {
            System.setProperty("log4j2.contextSelector", "org.apache.logging.log4j.core.async.AsyncLoggerContextSelector");
            SpringApplication.run(Log4j2Application.class, args);
        }
    }
    

2、使用AsyncLogger实现异步日志必须要要引入Disruptor依赖,不然开启全局异步或者使用到AsyncLogger的时候都会报错。



<dependency>
    <groupId>com.lmaxgroupId>
    <artifactId>disruptorartifactId>
    <version>3.3.7version>
dependency>

3、正常在log4j.xml配置文件增加日志输出配置(配置文件与同步配置无区别)


<Configuration status="warn" monitorinterval="10" name="myApp">
    <Appenders>
        <Console name="consoleAppender" target="SYSTEM_OUT">
            <PatternLayout pattern="%m%n"/>
        Console>
    Appenders>
    <Loggers>
        <Root level="error">
            <AppenderRef ref="consoleAppender"/>
        Root>
    Loggers>
Configuration>

4、编写测试代码,测试全局异步输出日志信息

package com.xyz;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Log4j2Test {
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger(Log4j2Test.class);
        // 日志的记录
        for (int i = 0; i < 3; i++) {
            logger.error("error信息");
            logger.warn("warn信息");
            logger.info("info信息");
            logger.debug("debug信息");
            logger.trace("trace信息");
        }
        // 系统业务逻辑
        for (int i = 0; i < 3; i++) {
            // 这里必须使用System.out输出,如果单独使用logger.xx输出看不出来效果
            logger.error("-----start-----");
            System.out.println("-----sys-----"); // 实际上只有这一行是业务代码
            logger.error("-----end-----");
        }
    }
}

5、输出结果(观察结果可以发现,此时实现了异步日志的输出)

-----sys-----
-----sys-----
-----sys-----
error信息
error信息
error信息
-----start-----
-----end-----
-----start-----
-----end-----
-----start-----
-----end-----

2、混合异步实现

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

  • 需求:自定义一个异步Logger–com.xyz(当前包下的日志输出都为异步的),设置rootLogger是同步的(其他都为同步输出)

  • 注意:在使用混合异步模式时,一定要将全局的异步配置注释掉(resources下的properties)

    #Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
    

1、修改log4j2.xml文件中的配置信息,增加异步Logger(AsyncLogger),如下配置:com.xyz 日志是异步的, root 日志是同步的


<Configuration status="warn" monitorinterval="10" name="myApp">
    <Appenders>
        <Console name="consoleAppender" target="SYSTEM_OUT">
            <PatternLayout pattern="%m%n"/>
        Console>
    Appenders>
    <Loggers>
        
        <AsyncLogger name="com.xyz" level="error" includeLocation="false" additivity="false">
            <AppenderRef ref="consoleAppender"/>
        AsyncLogger>
        
        <Root level="error">
            <AppenderRef ref="consoleAppender"/>
        Root>
    Loggers>
Configuration>

2、异步日志输出:编写测试代码,测试混合异步输出日志信息,这里先测试异步日志输出,使用对应设置的Logger即可

package com.xyz;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Log4j2Test {
    public static void main(String[] args) {
        // 当前测试的是自定义的com.xyz的异步logger,如果想测试同步只需修改成其他名称即可
        Logger logger = LoggerFactory.getLogger("com.xyz");
        // 日志的记录
        for (int i = 0; i < 3; i++) {
            logger.error("error信息");
            logger.warn("warn信息");
            logger.info("info信息");
            logger.debug("debug信息");
            logger.trace("trace信息");
        }
        // 系统业务逻辑
        for (int i = 0; i < 3; i++) {
            // 这里必须使用System.out输出,如果单独使用logger.xx输出看不出来效果
            logger.error("-----start-----");
            System.out.println("-----sys-----");
            logger.error("-----end-----");
        }
    }
}

3、输出结果(观察结果可以发现,此时实现了异步日志的输出)

-----sys-----
-----sys-----
-----sys-----
error信息
error信息
error信息
-----start-----
-----end-----
-----start-----
-----end-----
-----start-----
-----end-----

4、同步日志输出:编写测试代码,测试混合异步输出日志信息,这里测试同步日志输出,使用RootLogger即可(实际就修改定义的Logger)

// 当前测试使用的是RootLogger,RootLogger设置的是同步日志输出
Logger logger = LoggerFactory.getLogger("com");

5、输出结果(观察结果可以发现,此时实现了同步日志的输出)

error信息
error信息
error信息
-----start-----
-----sys-----
-----end-----
-----start-----
-----sys-----
-----end-----
-----start-----
-----sys-----
-----end-----

4、AsyncRoot

AsyncRoot 实际就是根 AsyncLogger,其用法及功能与 AsyncLogger 一摸一样(全局混合异步),参考上面的 AsyncLogger 即可。

总结一下Log4j2 异步化日志的三种方式:

  1. 全局启用异步Logger方案一:JVM启动参数(boot.ini)加上如下参数

    -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
    
  2. 全局启用异步Logger方案二:classpath中添加文件“log4j2.component.properties”,文件增加以下内容:

    Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
    
  3. 手工指定部分Logger采用异步方式:log4j2.xml配置文件中使用AsyncRoot 和 AsyncLogger 替代 Root 和 Logger

  4. (上述3种方式任选其一即可,不要同时采用)

5、异步配置注意

使用异步日志需要注意的问题:

  • 如果使用异步日志,AsyncAppender、AsyncLogger 和 全局日志,不要同时出现。性能会和AsyncAppender 一致,降至最低。
  • 需要设置 includeLocation=false,否则打印位置信息会急剧降低异步日志的性能,比同步日志还要慢。
  • 当配置AsyncLoggerContextSelector作为(全局)异步日志时,请确保在配置中使用普通的Root和Logger元素。AsyncLoggerContextSelector将确保所有记录器都是异步的,使用的机制与配置AsyncRoot> 或AsyncLogger时的机制不同

通过log.info("是否为全局异步日志:{}", AsyncLoggerContextSelector.isSelected());可以查看是否为全局异步日志

import org.apache.logging.log4j.core.async.AsyncLoggerContextSelector;
import org.slf4j.LoggerFactory;

LoggerFactory.getLogger("com").error("com是否为异步日志:{}", AsyncLoggerContextSelector.isSelected());
LoggerFactory.getLogger("com.xyz").error("xyz是否为异步日志:{}", AsyncLoggerContextSelector.isSelected());

Log4j2 最牛的地方在于异步输出日志时的性能表现,Log4j2 在多线程的环境下吞吐量与 Log4j 和 Logback 的比较可查官网图片。三种模式对比:

  1. 全局使用异步模式(Log4j2 的性能较之 Log4j 和Logback有很大的优势)
  2. 部分Logger采用异步模式(Log4j2 的性能较之 Log4j 和Logback有很大的优势)
  3. 异步Appenderf(Log4j2 对比 Logback性能上差不多)
日志输出类型 日志输出方式
sync 同步打印日志,日志输出与业务逻辑在同一线程内,当日志输出完毕,才能进行后续业务逻辑操作
AsyncAppender 异步打印日志,内部采用ArrayBlockingQueue,对每个AsyncAppender创建一个线程用于处理日志输出
AsyncLogger 异步打印日志,采用了高性能并发框架Disruptor,创建一个线程用于处理日志输出(要引入com.lmax.disruptor)

6、异步日志原理

  • 应用程序的主线程调用打印记录日志的方法后,传递到logEvent对象
  • event将对象加入到一个阻塞队列(ArrayBlockingQueue)中
  • 此时,主线程的工作就已经完成了,其余工作交给log异步线程就可以了
  • 阻塞队列(ArrayBlockingQueue)收到日志消息后,立马将消息发给Appender对象
  • Appender收到消息后开始打输出消息

6、SprintBoot集成Log4j2

1、SpringBoot日志依赖关系

  1. sping-boot-starter 与 spring-boot-starter-web 都依赖 sping-boot-starter-looging

  2. 而SpringBoot底层默认使用 SLF4J+ Logback 方式进行日志记录,SpringBoot 使用中间替换包把其的日志框架都替换成了SLF4J

  3. SpringBoot 能自动适配所有的日志框架,引入其他框架时,只需要把sping-boot-starter-looging依赖的日志框架排除掉即可

    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starterartifactId>
        <exclusions>
            <exclusion> 
                <artifactId>spring-boot-starter-loggingartifactId>
                <groupId>org.springframework.bootgroupId>
            exclusion>
        exclusions>
    dependency>
    
    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
        <exclusions>
            
            <exclusion>
                <artifactId>spring-boot-starter-loggingartifactId>
                <groupId>org.springframework.bootgroupId>
            exclusion>
        exclusions>
    dependency>
    
    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-log4j2artifactId>
    dependency>
    

2、SpringBoot全局日志设置

官网配置详解(适用Logback与Log4j2)https://docs.spring.io/spring-boot/docs/2.5.0/reference/htmlsingle/#application-properties.core

  1. 日志的级别由低到高分别为:trace(跟踪) < debug(调试) < info(信息) < warn(警告) < error(错误)

  2. 在配置文件中调整输出的日志级别,日志就只会在这个级别及以后的高级别生效,SpringBoot 默认使用 INFO 级别(Log4j2 默认 ERROR)

  3. SpringBoot 的全局配置文件 “application.properties” 或 “application.yml” 中可以修改日志配置项:

    # 默认名log4j2-spring.xml,如果要设置其他名称则需要如下配置
    # logging.config=classpath:log4j2-spring.xml
    # 设置根节点的日志级别输出,root表示整个项目
    logging.level.root=INFO
    # 指定特定包及类的日志输出级别,未指定就按root设置的级别输出,如果root也未指定则按SpringBoot的默认级别info输出
    logging.level.org.springframework.web=DEBUG
    logging.level.org.hibernate=ERROR
    logging.level.com.xyz=DEBUG
    
    # 配置日志输出的文件,这两个选一个配置就可以了,一起配置的话,name的生效. 每次启动都是追加日志
    # .name=具体文件, 写入指定的日志文件.文件名可以是确切的位置,也可以是相对于当前目录的位置
    # .path=具体目录, 写入spring.log文件指定目录.目录名可以是确切的位置,也可以是相对于当前目录的位置
    logging.file.name=logs/log4j2.log
    logging.file.path=logs/
    
    # 指定控制台输出的日志格式,例如: %d{yyyy-MM-dd HH:mm:ss} -- [%thread] %-5level %logger{50} %msg%n
    # 1、%d 表示日期时间,
    # 2、%thread 表示线程名,
    # 3、%‐5level 级别从左显示5个字符宽度
    # 4、%logger{50} 表示logger名字最长50个字符,否则按照句点分割。
    # 5、%msg 日志消息,
    # 6、%n 换行符
    # 7、%line 显示日志输出位置的行号,方便寻找位置
    # 8、%X 特殊占位符,%X{uu_id}是获取uu_id的值,代码中设置uu_id值:org.slf4j.MDC.put("uuid", "xx-yy-zz");
    # 配置日志输出格式, .file是配置输出到文件的日志格式, .console是配置输出到控制台的日志格式
    logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} %-5level [%thread] %logger{50}:%line %msg%n
    logging.pattern.file=%d{yyyy-MM-dd HH:mm} -- [%thread] %-5level %logger{50} %msg%n
    
    # 设置日志记录器组,将相关的记录器组合在一起,然后设置日志记录器组的日志级别为TRACE
    logging.group.tomcat=org.apache.catalina, org.apache.coyote, org.apache.tomcat
    logging.level.tomcat=TRACE
    # SpringBoot包括以下预定义的日志记录组,可以开箱即用
    # web: org.springframework.core.codec, org.springframework.http, org.springframework.web
    # sql: org.springframework.jdbc.core, org.hibernate.SQL
    logging.level.web=INFO
    logging.level.sql=DEBUG
    
    • 默认情况,日志文件超过10M时,会新建文件进行递增,如logback.log、logback1.log、logback2.log,使用logging.file.max-size更改大小限制
    • 默认情况,日志只记录到控制台,不写入日志文件,如果要在控制台输出之外写入到日志文件,则需要设置 .file 或 .path 属性
    • 默认情况,logging.file.* 等配置使用的都是RollingFileAppender

3、SpringBoot + Log4j2示例

1、导入log4j2依赖

SpringBoot默认是用Logback日志框架,需要排除Logback,不然会出现jar依赖冲突报错(spring-boot 2.5.2 + log4j 2.14.1 + JDK 1.8)


<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-webartifactId>
    <exclusions>
        <exclusion>
            <artifactId>spring-boot-starter-loggingartifactId>
            <groupId>org.springframework.bootgroupId>
        exclusion>
    exclusions>
dependency>


<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-log4j2artifactId>
dependency>

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-testartifactId>
    <scope>testscope>
dependency>

2、SprintBoot配置文件

如果自定义了Log4j2文件名,需要在application.properties中配置( 默认名log4j2-spring.xml,就省下了在application.yml中配置),如果只引入了 log4j2 依赖,没有提供 log4j2.xml 配置文件,则默认日志输出级别为 INFO,会打印在控制台,但是不会记录到文件中。实际上可以不使用Log4j2配置文件,只用SpringBoot配置,不过这样能使用的功能是非常有限的(如果是这样还不如使用Logback)

logging.config=classpath:log4j2-spring.xml

3、Log4j2配置文件(log4j2.xml)

log4j2-spring.xml(SpringBoot会自动加载-spring的文件,所以可不配置logging.config),如果要自定义名称则需要配置logging.config





<configuration status="WARN" monitorInterval="30">
    
    <properties>
        <property name="LOG_HOME">./WebAppLogs/logsproperty>
    properties>
    
    <appenders>
        
        
        
        
        
        <console name="Console" target="SYSTEM_OUT">
            
            <PatternLayout pattern="[%d{HH:mm:ss:SSS}] - [%t] [%p] - %logger{1.} - %m%n"/>
            
            
        console>
        
        
        <RollingFile name="RollingFileTrace" immediateFlush="true" fileName="${LOG_HOME}/trace.log"
                     filePattern="${LOG_HOME}/trace_%d{yyyy-MM-dd-HH}-%i.log.zip">
            <ThresholdFilter level="trace" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="[%d{HH:mm:ss:SSS}] - [%t] [%p] - %logger{36} - %m%n"/>
            <Policies>
                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
                <SizeBasedTriggeringPolicy size="10 MB"/>
            Policies>
            
            <DefaultRolloverStrategy max="20">
                
                <Delete basePath="${LOG_HOME}" maxDepth="2">
                    <IfFileName glob="trace_*.zip"/>
                    
                    
                    <IfLastModified age="168H"/>
                Delete>
            DefaultRolloverStrategy>
        RollingFile>

        <RollingFile name="RollingFileDebug" immediateFlush="true" fileName="${LOG_HOME}/debug.log"
                     filePattern="${LOG_HOME}/debug_%d{yyyy-MM-dd-HH}-%i.log.zip">
            <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="[%d{HH:mm:ss:SSS}] - [%t] [%p] - %logger{36} - %m%n"/>
            <Policies>
                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
                <SizeBasedTriggeringPolicy size="10 MB"/>
            Policies>
            <DefaultRolloverStrategy max="20">
                <Delete basePath="${LOG_HOME}" maxDepth="2">
                    <IfFileName glob="debug_*.zip"/>
                    <IfLastModified age="168H"/>
                Delete>
            DefaultRolloverStrategy>
        RollingFile>

        
        <RollingFile name="RollingFileInfo" immediateFlush="true" fileName="${LOG_HOME}/info.log"
                     filePattern="${LOG_HOME}/info_%d{yyyy-MM-dd-HH}-%i.log.zip">
            
            <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="[%d{HH:mm:ss:SSS}] - [%t] [%p] - %logger{36} - %m%n"/>
            <Policies>
                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
                <SizeBasedTriggeringPolicy size="10 MB"/>
            Policies>
            <DefaultRolloverStrategy max="20">
                <Delete basePath="${LOG_HOME}" maxDepth="2">
                    <IfFileName glob="info_*.zip"/>
                    <IfLastModified age="168H"/>
                Delete>
            DefaultRolloverStrategy>
        RollingFile>

        
        <RollingFile name="RollingFileWarn" immediateFlush="true" fileName="${LOG_HOME}/warn.log"
                     filePattern="${LOG_HOME}/warn_%d{yyyy-MM-dd-HH}-%i.log.zip">
            <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="[%d{HH:mm:ss:SSS}] - [%t] [%p] - %logger{36} - %m%n"/>
            <Policies>
                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
                <SizeBasedTriggeringPolicy size="10 MB"/>
            Policies>
            <DefaultRolloverStrategy max="20">
                <Delete basePath="${LOG_HOME}" maxDepth="2">
                    <IfFileName glob="warn_*.zip"/>
                    <IfLastModified age="168H"/>
                Delete>
            DefaultRolloverStrategy>
        RollingFile>

        
        <RollingFile name="RollingFileError" immediateFlush="true" fileName="${LOG_HOME}/error.log"
                     filePattern="${LOG_HOME}/error_%d{yyyy-MM-dd-HH}-%i.log.zip">
            <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="[%d{HH:mm:ss:SSS}] - [%t] [%p] - %logger{36} - %m%n"/>
            <Policies>
                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
                <SizeBasedTriggeringPolicy size="10 MB"/>
            Policies>
            <DefaultRolloverStrategy max="20">
                <Delete basePath="${LOG_HOME}" maxDepth="2">
                    <IfFileName glob="error_*.zip"/>
                    <IfLastModified age="168H"/>
                Delete>
            DefaultRolloverStrategy>
        RollingFile>
    appenders>
    
    <loggers>
        
        <logger name="org.springframework" level="INFO"/>
        <logger name="org.mybatis" level="INFO"/>
        <root level="all">
            <appender-ref ref="Console"/>
            <appender-ref ref="RollingFileDebug"/>
            <appender-ref ref="RollingFileTrace"/>
            <appender-ref ref="RollingFileInfo"/>
            <appender-ref ref="RollingFileWarn"/>
            <appender-ref ref="RollingFileError"/>
        root>
    loggers>
configuration>

Log4j2配置详解请参考前面的教程【自定义配置文件】

这是转载其他人整理的百分号属性参数说明大全:

    ********************************************************************************************************************
    参数        说明                                         举例                    输出显示媒介
    ********************************************************************************************************************
    %c          列出logger名字空间的全称,如果加上{<层数>},   假设当前logger的命名空间是"a.b.c"
                则表示列出从最内层算起的指定层数的名字空间
                                                            %c                  a.b.c
                                                            %c{2}               b.c
                                                            %20c                (若名字空间长度小于20,则左边用空格填充)
                                                            %-20c               (若名字空间长度小于20,则右边用空格填充)
                                                            %.30c               (若名字空间长度超过30,截去多余字符)
                                                            %20.30c             (若名字空间长度小于20,则左边用空格填充;
                                                                                    若名字空间长度超过30,截去多余字符)
                                                            %-20.30c            (若名字空间长度小于20,则右边用空格填充;
                                                                                    若名字空间长度超过30,截去多余字符)
    ********************************************************************************************************************
    %C          列出调用logger的类的全名(包含包路径)         假设当前类是"org.apache.xyz.SomeClass"
                                                            %C                  org.apache.xyz.SomeClass
                                                            %C{1}               SomeClass
    %class
    ********************************************************************************************************************
    %d          显示日志记录时间,{<日期格式>}使用ISO8601定义的日期格式
                                                            %d{yyyy/MM/dd HH:mm:ss,SSS}     2005/10/12 22:23:30,117
                                                            %d{ABSOLUTE}        22:23:30,117
                                                            %d{DATE}            12 Oct 2005 22:23:30,117
                                                            %d{ISO8601}         2005-10-12 22:23:30,117
    ********************************************************************************************************************
    %F          显示调用logger的源文件名                     %F                   MyClass.java
    ********************************************************************************************************************
    %l          显示日志事件的发生位置,包含包路径、方法名、
                源文件名,以及在代码中的行数                  %l                   com.a.b.MyClass.main(MyClass.java:168)
    ********************************************************************************************************************
    %L          显示调用logger的代码行                       %L                   129
    %line                                                  %line                129
    ********************************************************************************************************************
    %level      显示该条日志的优先级                         %level               INFO
    %p                                                     %p                   INFO
    ********************************************************************************************************************
    %m          显示输出消息                                 %m                  This is a message for debug.
    %message                                                %message            This is a message for debug.
    ********************************************************************************************************************
    %M          显示调用logger的方法名                       %M                   main
    ********************************************************************************************************************
    %n          当前平台下的换行符                           %n                   Windows平台下表示rn,UNIX平台下表示n
    ********************************************************************************************************************
    %p          显示该条日志的优先级                         %p                   INFO
    %level                                                 %level               INFO
    ********************************************************************************************************************
    %r          显示从程序启动时到记录该条日志时已经经过的毫秒数  %r                 1215
    ********************************************************************************************************************
    %t          输出产生该日志事件的线程名                    %t                   http-nio-8080-exec-10
    %thread                                                 %thread              http-nio-8080-exec-10
    ********************************************************************************************************************
    %x          按NDC(Nested Diagnostic Context,线程堆栈)顺序输出日志           假设某程序调用顺序是MyApp调用com.foo.Bar
                                                            %c %x - %m%n        MyApp - Call com.foo.Bar.
                                                                                com.foo.Bar - Log in Bar
                                                                                MyApp - Return to MyApp.
    ********************************************************************************************************************
    %X          按MDC(Mapped Diagnostic Context,线程映射表)
                输出日志。通常用于多个客户端连接同一台服务器,
                方便服务器区分是那个客户端访问留下来的日志。     %X{5}             (记录代号为5的客户端的日志)
    ********************************************************************************************************************
    %%          显示一个百分号                               %%                  %
    ********************************************************************************************************************

4、日志实现-Slf4j(LogFactory)

如下使用 slf4j + log4j2 配合打印日志,当前方式也是比较流行的,slf4j 能适配各种日志框架,迁移起来也会非常方便,功能上会log4j2稍逊。

    @Test
    void slf4jTest() {
        org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger("slf4jTest");
        log.error("hello, I am error!");
        log.warn("hello, I am warn!");
        log.info("hello, I am {}!", "info");
        log.debug("hello, I am debug!");
        log.trace("hello, I am trace!");
    }

日志实现 Slf4j 自定义工具类:

public class Slf4jLogUtil {

    /**
     * debug级别日志输出
     * @param clazz 类
     * @param msg 日志
     * @param params 其他参数
     */
    public static void debug(Class clazz, String msg, Object... params) {
        Logger logger = LoggerFactory.getLogger(clazz.getName());
        logger.debug(msg, params);
    }

    /**
     * trace日志输出
     * @param clazz 类
     * @param msg 日志
     * @param params 其他参数
     */
    public static void trace(Class clazz, String msg, Object... params) {
        Logger logger = LoggerFactory.getLogger(clazz.getName());
        logger.trace(msg, params);
    }

    /**
     * info级别日志输出
     * @param clazz 类
     * @param msg 日志
     * @param params 其他参数
     */
    public static void info(Class clazz, String msg, Object... params) {
        Logger logger = LoggerFactory.getLogger(clazz.getName());
        logger.info(msg, params);
    }

    /**
     * warn级别日志输出
     * @param clazz 类
     * @param msg 日志
     * @param params 其他参数
     */
    public static void warn(Class clazz, String msg, Object... params) {
        Logger logger = LoggerFactory.getLogger(clazz);
        logger.warn(msg, params);
    }

    /**
     * error级别日志输出
     * @param clazz 类
     * @param msg 日志
     * @param params 其他参数
     */
    public static void error(Class clazz, String msg, Object... params) {
        Logger logger = LoggerFactory.getLogger(clazz);
        logger.error(msg, params);
    }
}

5、日志实现-Log4j(LogManager)

    @Test
    void log4j2Test() {
        org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger("log4j2Test");
        log.error("hello, I am error!");
        log.warn("hello, I am warn!");
        log.info("hello, I am {}!", "info");
        log.debug("hello, I am debug!");
        log.trace("hello, I am trace!");
    }

日志实现 Log4j2 自定义工具类:

public class Log4j2LogUtil {
    /**
     * debug级别日志输出
     * @param clazz 类
     * @param msg 日志
     * @param params 其他参数
     */
    public static void debug(Class clazz, String msg, Object... params) {
        Logger logger = LogManager.getLogger(clazz.getName());
        logger.debug(msg, params);
    }

    /**
     * trace级别日志输出
     * @param clazz 类
     * @param msg 日志
     * @param params 其他参数
     */
    public static void trace(Class clazz, String msg, Object... params) {
        Logger logger = LogManager.getLogger(clazz.getName());
        logger.trace(msg, params);
    }

    /**
     * info级别日志输出
     * @param clazz 类
     * @param msg 日志
     * @param params 其他参数
     */
    public static void info(Class clazz, String msg, Object... params) {
        Logger logger = LogManager.getLogger(clazz.getName());
        logger.info(msg, params);
    }

    /**
     * warn级别日志输出
     * @param clazz 类
     * @param msg 日志
     * @param params 其他参数
     */
    public static void warn(Class clazz, String msg, Object... params) {
        Logger logger = LogManager.getLogger(clazz);
        logger.warn(msg, params);
    }

    /**
     * error级别日志输出
     * @param clazz 类
     * @param msg 日志
     * @param params 其他参数
     */
    public static void error(Class clazz, String msg, Object... params) {
        Logger logger = LogManager.getLogger(clazz);
        logger.error(msg, params);
    }

    /**
     * fatal日志
     * @param clazz 类
     * @param msg 日志
     * @param params 其他参数
     */
    public static void fatal(Class clazz, String msg, Object... params) {
        Logger logger = LogManager.getLogger(clazz);
        logger.fatal(msg, params);
    }
}

6、SpringBoot + Log4j2最佳实践

建议使用 SLF4J门面 + Log4j2门面及实现,搭配同时使用,一般情况打印日志的时候可以使用SLF4J之类的API,需要使用Log4j2更多强大功能时就使用自己的门面API即可(例如:参数格式化以及"惰性"打日志之类的功能)

package com.xyz;

public class Log4j2Test {
    private static final org.slf4j.Logger slf4j = org.slf4j.LoggerFactory.getLogger(Log4j2Test.class);
    private static final org.apache.logging.log4j.Logger log4j = org.apache.logging.log4j.LogManager.getLogger(Log4j2Test.class);
    public static void main(String[] args) {
        // 使用slf4j门面 + log4j实现
        slf4j.info("test: {}", "xx");
        // 使用log4j门面 + log4j实现
        log4j.debug("test:{}",() -> "xx");
    }
}

4、动态切换日志级别的几种方式

1、monitorInterval 定时读取配置信息

  1. 热加载配置文件是很有用的,比如服务上线之后,当需要输出详细日志信息排查问题时,可以降低优先级,比如:TRACE 、DEBUG,当完事之后,可以再调高优先级,比如 WARN、ERROR,减少日志记录,提供性能

  2. Log4j2 与 Logback 一样,可以在修改后自动重新加载其配置,与 Logback 不同的是,它在进行重新配置时不会丢失日志事件

  3. 使用非常简单只需要在文件开始的 configuration 标签中指定 monitorInterval 属性即可,值大于 0 时会自动创建子线程进行定时扫描,更新配置;不配置或者值为0时,不会进行监控,运行中修改是无效的:

    
    <configuration status="WARN" monitorInterval="60">
    
  4. 如果是在本地 IDE 编辑器中测试,注意修改的是 classes 编译目录下的 log4j2.xml 文件,而不是 resources 目录的源文件

  5. 对于可以手动修改配置文件的时候,此种方式还是很方便的,但是如果应用是打成 .jar、.war 包发布部署,则这种方式还是不太方便,因为仍然得从生产下载 jar、war 包,然后修改配置,重启服务

2、LoggerConfig 动态修改日志记录器

比较推荐的一种使用方式

  1. 鉴于现在都是微服务开发,分布式部署,而且为了方便管理,基本都是打包后部署在一些平台上面。直接修改日志配置文件不太现实
  2. 所以我们可以自己提供一个 Controller 请求接口,用于动态修改日志输出级别,想设置什么级别,只需要调用一下接口,传递一下参数即可,无论是设置整个 root 级别,还是对指定包或者类都能轻松设置,不用重启服务就能立马生效
  3. 实现也是非常简单,在线演示源码如下:
package com.xyz;

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
public class Log4j2Controller {
    private static final Logger log =  LogManager.getLogger(Log4j2Controller.class);
    /**
     * 打印日志测试: http://localhost:8080/logback/logs
     */
    @GetMapping("logback/logs")
    public void logs() {
        log.error("hello, I am error!");
        log.warn("hello, I am warn!");
        log.info("hello, I am {}!", "info");
        log.debug("hello, I am debug!");
        log.trace("hello, I am trace!");
    }

    /**
     * Log4j2 动态修改 root(根)日志级别,修改之后配置立刻生效.
     * http://localhost:8080/log4j/setRootLevel?level=TRACE
     *
     * @param level :可选值有:ALL、TRACE、DEBUG、INFO、WARN、ERROR、FATAL、OFF,否则默认会设置为 DEBUG,不区分大小写。
     * @return
     */
    @GetMapping(value = "/log4j/setRootLevel")
    public Map<String, Object> setRootLevel(@RequestParam(value = "level") String level) {
        Map<String, Object> dataMap = new HashMap<>();
        dataMap.put("code", 200);
        dataMap.put("msg", "修改root日志级别成功");
        try {
            // LoggerContext getContext(final boolean currentContext):获取 log4j 日志上下文,false 表示返回合适调用方的上下文
            LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false);

            // 返回当前配置,发生重新配置时,将替换该配置。
            Configuration configuration = loggerContext.getConfiguration();
            // 查找记录器名称的相应 LoggerConfig
            LoggerConfig loggerConfig = configuration.getLoggerConfig(LogManager.ROOT_LOGGER_NAME);

            // 设置日志级别,如果 level 值不属于 ALL、TRACE、DEBUG、INFO、WARN、ERROR、FATAL、OFF,则默认会设置为 DEBUG
            Level toLevel = Level.toLevel(level);
            loggerConfig.setLevel(toLevel);

            // 根据当前配置更新所有记录器
            loggerContext.updateLoggers();

            // 查询 root(根)日志输出级别结果返回
            String root_logger_name = LogManager.getRootLogger().getLevel().name();
            dataMap.put("data", root_logger_name);
        } catch (Exception e) {
            dataMap.put("code", 500);
            dataMap.put("msg", e.getMessage());
            e.printStackTrace();
        }
        return dataMap;
    }
}

3、SpringBootActuator 监控管理日志

使用SpringBoot Actuator监控管理日志的优点:

  • 无需编码,只需要引用Spring Boot Actuator监控依赖,然后开启访问端点 /loggers,即可轻松查看日志输出级别,并进行切换
  • 解耦日志框架,无论使用的 Logback 还是 Log4j2 ,都能轻松切换

1、引入pring Boot Actuator依赖


<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-actuatorartifactId>
dependency>

2、开启日志访问端点: /loggers

management:
  endpoint:
    health:
      show-details: ALWAYS # 展示节点的详细信息
  endpoints:
    web:
      exposure:
        include: info,health,logfile,loggers # 指定公开的访问端点

3、查看级别

  • 发送 GET 请求,获取日志等级:http://localhost:8080/actuator/loggers
  • 返回的信息非常详细,包含了 ROOT,以及程序中各个包和类的日志级别
  • 其中 configuredLevel 表示配置级别,effectiveLevel 表示有效级别,configuredLevel 可能为 null,因为没有配置。

4、修改日志级别

  • 发送 POST 请求,设置指定包或者类(com.xyz)日志输出级别:http://localhost:8080/actuator/loggers/com.xyz
  • 发送 POST 请求,设置 root 全局日志输出级别:http://localhost:8080/actuator/loggers/root
  • 请求 Body 的内容格式:{“configuredLevel”:“error”}

5、整合过程中出现问题解决方法

错误1:“Logging system failed to initialize using configuration from 'classpath:log4j2.xml”

错误原因:引用jar时引用了多个logback的框架,由于idea开发工具未根据pom.xml的配置自动配置依赖,未去除之前的log4j的依赖,导致无法解析节点信息

解决办法:在pom.xml文件中右击选择Diagrams,查看依赖图,找到spring.boot.starter-logging依赖,删除即可。

错误2:“java.lang.ClassNotFoundException: com.lmax.disruptor.EventFactory”

解决方法:修改配置

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-log4j2artifactId>
dependency>
<dependency>
    <groupId>com.lmaxgroupId>
    <artifactId>disruptorartifactId>
    <version>3.4.1version>
dependency>

7、参考资料 & 鸣谢

  • Spring Boot 2.x 集成 SLF4j + log4j2 日志框架【CSDN 蚩尤后裔】:https://wangmaoxiong.blog.csdn.net/article/details/117634140
  • java日志(四)–jcl和log4j及log4j2使用【CSDN panda-star】:https://blog.csdn.net/chinabestchina/article/details/104744069
  • log4j2 的使用【超详细图文】【蚩尤后裔 不埋雷的探长】:https://tanzhang.blog.csdn.net/article/details/110723441
  • log4j2使用filter过滤日志【CSDN justry_deng】:https://blog.csdn.net/justry_deng/article/details/109413153
  • log4j2四大组件【此木 IT 码农】:http://www.chaiguanxin.com/tags/log4j2

如下是参考微信公众号文章:

  • 你知道 log4j2 各项配置的全部含义吗?带你了解 log4j2 的全部组件【小脑斧科技博客】https://mp.weixin.qq.com/s/2XIR4_2UdAMQk-BYVd–vw
  • SpringBoot—整合log4j2入门和log4j2.xml配置详解【后端技术解忧铺】https://mp.weixin.qq.com/s/sLo_yMwTxzp52TLBKtdkqA
  • Log4j2的正确使用姿势【话题内有log4j2其他教程】【一只小笨龟】https://mp.weixin.qq.com/s/lJ4WSgd2Dg912RW7IaZ-3g

如下是Log4j2自定义组件文章:

  • 教你打印自己的日志 – 如何自定义 log4j2 各组件【小脑斧科技博客】https://mp.weixin.qq.com/s/Xh13NZVoGalgfL1zZwKDWw
  • log4j日志级别调整&日志限流【一只小笨龟】https://mp.weixin.qq.com/s/Jc3kBDKl6izuEJIZFKDhxA
  • log4j2如何实现日志自脱敏?山人自有妙计!【看点代码再上班】https://mp.weixin.qq.com/s/TAjC0OERPWxyjoeYkb685A

你可能感兴趣的:(从零开始学,Java日志框架,java)