spring boot使用日志组件log4j2

2019-09-26  本文已影响0人  天草二十六_简村人

一、为什么要引入log4j2

有以下几个原因:

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

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

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

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

二、 引入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<String, String> 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<LogLevel> getSupportedLogLevels() {
        return EnumSet.allOf(LogLevel.class);
    }

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

    public List<LoggerConfiguration> 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<String, String> 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<LoggerConfiguration> 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模板

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

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" monitorInterval="5">
    <Properties>
        <Property name="AppName">user-service</Property>

        <Property name="customizePattern">
            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
        </Property>

        <Property name="defaultPattern">
            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
        </Property>

        <Property name="baseLogDir">log</Property>

        <Property name="BurialPointLogFile">${baseLogDir}/burialPoint.log</Property>
        <Property name="SysLogFile">${baseLogDir}/sys.log</Property>
    </Properties>
    <Appenders>
        <RollingRandomAccessFile name="BurialPointLogFileAppender" fileName="${BurialPointLogFile}"
                                 filePattern="${BurialPointLogFile}-%d{yyyy-MM-dd}-%i.gz" immediateFlush="false">
            <PatternLayout>
                <Pattern>${customizePattern}</Pattern>
            </PatternLayout>
            <Policies>
                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
                <SizeBasedTriggeringPolicy size="500MB"/>
            </Policies>
            <!-- 业务埋点日志, 文件保存3天, 每个小时最多生成100个文件 -->
            <DefaultRolloverStrategy max="100">
                <Delete basePath="${baseLogDir}" maxDepth="1">
                    <IfFileName glob="*.gz"/>
                    <IfLastModified age="3d"/>
                </Delete>
            </DefaultRolloverStrategy>
        </RollingRandomAccessFile>

        <RollingRandomAccessFile name="SysLogFileAppender" fileName="${SysLogFile}"
                                 filePattern="${SysLogFile}-%d{yyyy-MM-dd}-%i.gz" immediateFlush="false">
            <PatternLayout>
                <Pattern>${defaultPattern}</Pattern>
            </PatternLayout>
            <Policies>
                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
                <SizeBasedTriggeringPolicy size="500MB"/>
            </Policies>
            <!-- 系统日志, 文件保存7天, 每个小时最多生成20个文件 -->
            <DefaultRolloverStrategy max="20">
                <Delete basePath="${baseLogDir}" maxDepth="1">
                    <IfFileName glob="*.gz"/>
                    <IfLastModified age="7d"/>
                </Delete>
            </DefaultRolloverStrategy>
        </RollingRandomAccessFile>
    </Appenders>

    <Loggers>
        <Logger name="org.springframework.boot" level="info" includeLocation="false" additivity="false">
            <appender-ref ref="SysLogFileAppender"/>
        </Logger>
        <Logger name="org.springframework" level="error" includeLocation="false" additivity="false">
            <appender-ref ref="SysLogFileAppender"/>
        </Logger>
        <Logger name="org.apache" level="warn" includeLocation="false" additivity="false">
            <appender-ref ref="SysLogFileAppender"/>
        </Logger>
        <Logger name="org.hibernate" level="error" includeLocation="false" additivity="false">
            <appender-ref ref="SysLogFileAppender"/>
        </Logger>

        <Logger name="com.xxl.job" level="warn" includeLocation="false" additivity="false">
            <appender-ref ref="SysLogFileAppender"/>
        </Logger>

        <Logger name="com.alibaba.fastjson" level="error" includeLocation="false" additivity="false">
            <appender-ref ref="SysLogFileAppender"/>
        </Logger>

        <!-- 埋点日志, 采用异步模式 -->
        <AsyncLogger name="BurialPoint" level="info" includeLocation="false" additivity="false">
            <appender-ref ref="BurialPointLogFileAppender"/>
        </AsyncLogger>

        <AsyncRoot level="info">
            <AppenderRef ref="SysLogFileAppender"/>
        </AsyncRoot>
    </Loggers>

</Configuration>

五、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同一目录。

七、pom依赖的调整

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

<dependencies>
       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>

        <dependency>
            <groupId>com.lmax</groupId>
            <artifactId>disruptor</artifactId>
            <version>3.4.2</version>
        </dependency>
...
...
</dependencies>

八、磁盘空间估算

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

上一篇下一篇

猜你喜欢

热点阅读