spring boot使用日志组件log4j2

一、为什么要引入log4j2

有以下几个原因:

1、日志格式的统一,减少日志采集阶段,在正则匹配时的误差。

2、以前是将控制台的日志重定向至日志文件里,这个在做日志切割的时候,会有问题。

3、把所有的日志都存放在一个文件里,区分不了埋点日志还是普通日志。所以趁着业务上有埋点的需求,有必须引入log4j2这样的日志组件了。为什么不使用默认的logback呢?原因主要是log4j2支持异步logger了。

这里引用一段log4j2的优点吧,注意不是与logback的区别。

  • 1). 插件式结构。 Log4j 2 支持插件式结构。我们可以根据自己的需要自行扩展 Log4j 2. 我们可以实现自己的
    appender 、 logger 、 filter 。
  • 2). 配置文件优化。在配置文件中可以引用属性,还可以直接替代或传递到组
    件。而且支持 json 格式的配置文件。不像其他的日志框架,它在重新配置的时候不会丢失之前的日志文件。
  • 3). Java 5 的并发性。 Log4j 2 利用 Java 5 中的并发特性支持,尽可能地执行最低层次的加锁。解决了在 log4j 1.x 中存
    留的死锁的问题。
  • 4). 异步 logger 。 Log4j 2 是基于 LMAX Disruptor 库的。在多线程的场景下,和已有的日志框架相比,异步的 logger 拥有 10 倍左右的效率提升。

二、 引入xml文件

1、为什么需要单独引入xml文件?

(1)约定优先于配置。spring boot最喜欢做的事情就是默认实现了,关于日志这块,它的默认实现是logback。核心类是LoggingSystem。

又必要看下它的源码实现,发现有三个日志实现类:LogbackLoggingSystem、Log4J2LoggingSystem、JavaLoggingSystem。


image.png
package org.springframework.boot.logging;

import java.util.Collections;
import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

public abstract class LoggingSystem {
    public static final String SYSTEM_PROPERTY = LoggingSystem.class.getName();
    public static final String NONE = "none";
    public static final String ROOT_LOGGER_NAME = "ROOT";
    private static final Map SYSTEMS;

    public LoggingSystem() {
    }

//抽象方法,也作钩子方法
    public abstract void beforeInitialize();

    public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) {
    }

//空实现,子类可以重写该方法
    public void cleanUp() {
    }

    public Runnable getShutdownHandler() {
        return null;
    }

    public Set getSupportedLogLevels() {
        return EnumSet.allOf(LogLevel.class);
    }

    public void setLogLevel(String loggerName, LogLevel level) {
        throw new UnsupportedOperationException("Unable to set log level");
    }

    public List getLoggerConfigurations() {
        throw new UnsupportedOperationException("Unable to get logger configurations");
    }

    public LoggerConfiguration getLoggerConfiguration(String loggerName) {
        throw new UnsupportedOperationException("Unable to get logger configuration");
    }

    public static LoggingSystem get(ClassLoader classLoader) {
        String loggingSystem = System.getProperty(SYSTEM_PROPERTY);
        if (StringUtils.hasLength(loggingSystem)) {
// 这里可以将下面的"none".equals修改为NONE.equals
            return (LoggingSystem)("none".equals(loggingSystem) ? new LoggingSystem.NoOpLoggingSystem() : get(classLoader, loggingSystem));
        } else {
// findFirst() 是意指取第一个实现者LogbackLoggingSystem
            return (LoggingSystem)SYSTEMS.entrySet().stream().filter((entry) -> {
// 注意这里是isPresent,不是isParent哈,返回真/假。过滤掉已加载过的类。
                return ClassUtils.isPresent((String)entry.getKey(), classLoader);
            }).map((entry) -> {
//动态加载日志实现类
                return get(classLoader, (String)entry.getValue());
            }).findFirst().orElseThrow(() -> {
                return new IllegalStateException("No suitable logging system located");
            });
        }
    }

    private static LoggingSystem get(ClassLoader classLoader, String loggingSystemClass) {
        try {
// ClassUtils.forName()这个方法通过反射机制实现动态加载。
            Class systemClass = ClassUtils.forName(loggingSystemClass, classLoader);
            return (LoggingSystem)systemClass.getConstructor(ClassLoader.class).newInstance(classLoader);
        } catch (Exception var3) {
            throw new IllegalStateException(var3);
        }
    }
// LoggingSystem 的三个实现
    static {
        Map systems = new LinkedHashMap();
        systems.put("ch.qos.logback.core.Appender", "org.springframework.boot.logging.logback.LogbackLoggingSystem");
        systems.put("org.apache.logging.log4j.core.impl.Log4jContextFactory", "org.springframework.boot.logging.log4j2.Log4J2LoggingSystem");
        systems.put("java.util.logging.LogManager", "org.springframework.boot.logging.java.JavaLoggingSystem");
        SYSTEMS = Collections.unmodifiableMap(systems);
    }

 static class NoOpLoggingSystem extends LoggingSystem {
        NoOpLoggingSystem() {
        }

        public void beforeInitialize() {
        }

        public void setLogLevel(String loggerName, LogLevel level) {
        }

        public List getLoggerConfigurations() {
            return Collections.emptyList();
        }

        public LoggerConfiguration getLoggerConfiguration(String loggerName) {
            return null;
        }
    }
}

(2)、上面我们知道了spring boot的默认实现是logback,并且实现了绝大多数的功能。但是对外提供了哪些配置项呢。

这就需要看下配置类LoggingSystemProperties了。
第一部分是说LogFile的相关配置,第二部分是说SystemProperty系统配置。

package org.springframework.boot.logging;

import org.springframework.boot.system.ApplicationPid;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertyResolver;
import org.springframework.core.env.PropertySourcesPropertyResolver;
import org.springframework.util.Assert;

public class LoggingSystemProperties {
    public static final String PID_KEY = "PID";
    public static final String EXCEPTION_CONVERSION_WORD = "LOG_EXCEPTION_CONVERSION_WORD";
    public static final String LOG_FILE = "LOG_FILE";
    public static final String LOG_PATH = "LOG_PATH";
    public static final String CONSOLE_LOG_PATTERN = "CONSOLE_LOG_PATTERN";
    public static final String FILE_LOG_PATTERN = "FILE_LOG_PATTERN";
    public static final String FILE_MAX_HISTORY = "LOG_FILE_MAX_HISTORY";
    public static final String FILE_MAX_SIZE = "LOG_FILE_MAX_SIZE";
    public static final String LOG_LEVEL_PATTERN = "LOG_LEVEL_PATTERN";
    public static final String LOG_DATEFORMAT_PATTERN = "LOG_DATEFORMAT_PATTERN";
    private final Environment environment;

    public LoggingSystemProperties(Environment environment) {
        Assert.notNull(environment, "Environment must not be null");
        this.environment = environment;
    }

    public void apply() {
        this.apply((LogFile)null);
    }

    public void apply(LogFile logFile) {
        PropertyResolver resolver = this.getPropertyResolver();
        this.setSystemProperty(resolver, "LOG_EXCEPTION_CONVERSION_WORD", "exception-conversion-word");
        this.setSystemProperty("PID", (new ApplicationPid()).toString());
        this.setSystemProperty(resolver, "CONSOLE_LOG_PATTERN", "pattern.console");
        this.setSystemProperty(resolver, "FILE_LOG_PATTERN", "pattern.file");
        this.setSystemProperty(resolver, "LOG_FILE_MAX_HISTORY", "file.max-history");
        this.setSystemProperty(resolver, "LOG_FILE_MAX_SIZE", "file.max-size");
        this.setSystemProperty(resolver, "LOG_LEVEL_PATTERN", "pattern.level");
        this.setSystemProperty(resolver, "LOG_DATEFORMAT_PATTERN", "pattern.dateformat");
        if (logFile != null) {
            logFile.applyToSystemProperties();
        }

    }

    private PropertyResolver getPropertyResolver() {
        if (this.environment instanceof ConfigurableEnvironment) {
            PropertyResolver resolver = new PropertySourcesPropertyResolver(((ConfigurableEnvironment)this.environment).getPropertySources());
            ((PropertySourcesPropertyResolver)resolver).setIgnoreUnresolvableNestedPlaceholders(true);
            return resolver;
        } else {
            return this.environment;
        }
    }
// 解析logging.开头的配置项
    private void setSystemProperty(PropertyResolver resolver, String systemPropertyName, String propertyName) {
        this.setSystemProperty(systemPropertyName, resolver.getProperty("logging." + propertyName));
    }

    private void setSystemProperty(String name, String value) {
        if (System.getProperty(name) == null && value != null) {
            System.setProperty(name, value);
        }

    }
}

详细见官网https://docs.spring.io/spring-boot/docs/2.1.2.RELEASE/reference/htmlsingle/#boot-features-logging-format 包含了哪些配置项。

logging配置项.png

官网也给出了,如果需要再多地关于日志的配置,则引入了log4j2和logback的xml文件。

三、引入后,之前的代码写法,会受影响吗?

不会。因为都是通过slf4j门面模式来做的。


LogbackLoggingSystem继承Slf4JLoggingSystem.png

Log4J2LoggingSystem继承Slf4JLoggingSystem.png

在java中,你使用如下写法:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private Logger logger =  LoggerFactory.getLogger(this.getClass());
logger.info("打印日志");

// 或者使用lombok注解

import lombok.extern.slf4j.Slf4j;
@Slf4j
public class A{
      log.info("打印日志");
}

四、log4j2.xml模板

需求:业务埋点日志、异步打印日志、日志格式自定义。



    
        user-service

        
            time=%d{yyyy-MM-dd}T%d{HH:mm:ss.SSS+0800}`appName=${AppName}`tradeId=%X{X-B3-TraceId}`spanId=%X{X-B3-SpanId}`parentSpanId=%X{X-B3-ParentSpanId}`threadId=%thread`logLevel=%level`%m%ex%n
        

        
            time=%d{yyyy-MM-dd}T%d{HH:mm:ss.SSS+0800}`appName=${AppName}`tradeId=%X{X-B3-TraceId}`spanId=%X{X-B3-SpanId}`parentSpanId=%X{X-B3-ParentSpanId}`threadId=%thread`logLevel=%level`msg=%m%ex%n
        

        log

        ${baseLogDir}/burialPoint.log
        ${baseLogDir}/sys.log
    
    
        
            
                ${customizePattern}
            
            
                
                
            
            
            
                
                    
                    
                
            
        

        
            
                ${defaultPattern}
            
            
                
                
            
            
            
                
                    
                    
                
            
        
    

    
        
            
        
        
            
        
        
            
        
        
            
        

        
            
        

        
            
        

        
        
            
        

        
            
        
    


五、log4j2的配置说明

(1).根节点Configuration有两个属性:status和monitorinterval,有两个子节点:Appenders和Loggers(表明可以定义多个Appender和Logger).

status用来指定log4j本身的打印日志的级别.

monitorinterval用于指定log4j自动重新配置的监测间隔时间,单位是s,最小是5s.

(2).Appenders节点,常见的有三种子节点:Console、RollingFile、File.

Console节点用来定义输出到控制台的Appender.

name:指定Appender的名字.

target:SYSTEM_OUT 或 SYSTEM_ERR,一般只设置默认:SYSTEM_OUT.

PatternLayout:输出格式,不设置默认为:%m%n.

File节点用来定义输出到指定位置的文件的Appender.

name:指定Appender的名字.

fileName:指定输出日志的目的文件带全路径的文件名.

PatternLayout:输出格式,不设置默认为:%m%n.

RollingFile节点用来定义超过指定大小自动删除旧的创建新的的Appender.

name:指定Appender的名字.

fileName:指定输出日志的目的文件带全路径的文件名.

PatternLayout:输出格式,不设置默认为:%m%n.

filePattern:指定新建日志文件的名称格式.

Policies:指定滚动日志的策略,就是什么时候进行新建日志文件输出日志.

TimeBasedTriggeringPolicy:Policies子节点,基于时间的滚动策略,interval属性用来指定多久滚动一次,默认是1 hour。modulate=true用来调整时间:比如现在是早上3am,interval是4,那么第一次滚动是在4am,接着是8am,12am...而不是7am.

SizeBasedTriggeringPolicy:Policies子节点,基于指定文件大小的滚动策略,size属性用来定义每个日志文件的大小.

DefaultRolloverStrategy:用来指定同一个文件夹下最多有几个日志文件时开始删除最旧的,创建新的(通过max属性)。

(3).Loggers节点,常见的有两种:Root和Logger.

Root节点用来指定项目的根日志,如果没有单独指定Logger,那么就会默认使用该Root日志输出

level:日志输出级别,共有8个级别,按照从低到高为:All < Trace < Debug < Info < Warn < Error < Fatal < OFF.

AppenderRef:Root的子节点,用来指定该日志输出到哪个Appender.

Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。

level:日志输出级别,共有8个级别,按照从低到高为:All < Trace < Debug < Info < Warn < Error < Fatal < OFF.

name:用来指定该Logger所适用的类或者类所在的包全路径,继承自Root节点.

AppenderRef:Logger的子节点,用来指定该日志输出到哪个Appender,如果没有指定,就会默认继承自Root.如果指定了,那么会在指定的这个Appender和Root的Appender中都会输出,此时我们可以设置Logger的additivity="false"只在自定义的Appender中进行输出。

(4).关于日志level.

共有8个级别,按照从低到高为:All < Trace < Debug < Info < Warn < Error < Fatal < OFF.

All:最低等级的,用于打开所有日志记录.

Trace:是追踪,就是程序推进以下,你就可以写个trace输出,所以trace应该会特别多,不过没关系,我们可以设置最低日志级别不让他输出.

Debug:指出细粒度信息事件对调试应用程序是非常有帮助的.

Info:消息在粗粒度级别上突出强调应用程序的运行过程.

Warn:输出警告及warn以下级别的日志.

Error:输出错误信息日志.

Fatal:输出每个严重的错误事件将会导致应用程序的退出的日志.

OFF:最高等级的,用于关闭所有日志记录.

程序会打印高于或等于所设置级别的日志,设置的日志等级越高,打印出来的日志就越少。

六、log4j2的通用配置

在resources目录下,新建文件log4j2.component.properties,和log4j2.xml同一目录。

  • 环形队列的大小
    AsyncLogger.RingBufferSize=10000
    AsyncLoggerConfig.RingBufferSize=10000
  • 自动降级--丢弃日志
    log4j2.AsyncQueueFullPolicy=Discard
  • 队列满后丢弃INFO级别的日志
    log4j2.DiscardThreshold=INFO

七、pom依赖的调整

注意:starter-web 和 log4j2 的位置必须调整至最顶部。


       
            org.springframework.boot
            spring-boot-starter-web
            
                
                    org.springframework.boot
                    spring-boot-starter-logging
                
            
        

        
            org.springframework.boot
            spring-boot-starter-log4j2
        

        
            com.lmax
            disruptor
            3.4.2
        
...
...

八、磁盘空间估算

根据日志文件的生成和删除策略,评估磁盘空间的最大需求。
我们是基于大小和时间的双重文件滚动策略,并配合压缩。单位时间内控制最多保留日志个数,并控制总的日志留
存时间。

  • 1 )单个日志文件的最大大小为 500MB ,每个小时内最多生成 100 个文件,日志文件最多保留 3 天。
    推导出日志文件留存的最大数为 7200 个。

  • 2 )考虑到历史日志文件我们采用了 gz 压缩算法,压缩比例为 1/50 的话,我们可以算出最多需要 72GB 的磁盘空间。
    日志文件留存的个数 = 3d * 24h * 100 个 /h = 7200 个
    日志文件的空间需求 = 500MB + 7200 个 * 500MB * (1/50) (压缩比) = 72.5GB

  • 3 )日志打印中的埋点日志,采用异步方式,减少 IO 次数。

你可能感兴趣的:(spring boot使用日志组件log4j2)