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

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

七、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>

八、磁盘空间估算

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

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

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,658评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,482评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,213评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,395评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,487评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,523评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,525评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,300评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,753评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,048评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,223评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,905评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,541评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,168评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,417评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,094评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,088评论 2 352

推荐阅读更多精彩内容