一文搞懂Java日志框架的具体使用

Java工具 潘老师 来源:断剑斩八方 1年前 (2023-04-14) 517 ℃ (0) 扫码查看

1 日志文件

日志文件是用于记录系统操作事件的集合,可分为事件日志和消息日志。日志文件主要记录系统行为的时间、地点、状态等相关信息,能够帮助我们了解并监控系统状态,在发生错误或者接近某种危险状态时能够及时提醒我们处理,同时在系统产生问题时,能够帮助我们快速的定位、诊断并解决问题。

2 Java常用日志框架

JUL(java.util.logging)、JCL(Apache Commons Logging)、Logback、Log4j、Log4j2、Slf4j、Jboss-logging 等。

2.1 日志门面

主要有:JCL、Slf4j、Log4j2、Jboss-logging等。日志门面即日志的一个抽象层,提供了使用各种日志实现的统一接口,它只是一个接口,并没有实现类。在项目中使用日志门面提供的接口输出日志,当我们更换日志框架的时候,不需要去修改项目代码,只需要配置使用哪种日志实现框架,项目运行时就会自动调用具体日志框架输出(面向接口编程)。日志门面最流行的是使用Slf4j。项目日志文件的内容格式、保存路径等配置,需要符合具体配置的日志框架语法,因为底层真正使用的是你配置的日志框架。

2.2 日志实现

主要有:JUL、Logback、Log4j、Log4j2等。日志实现是能够直接单独使用的日志框架,但当项目更换不同的日志框架时,因为每种框架的使用的方法不同,因此需要修改项目中日志输出的代码。如果使用日志门面,就能够避免修改代码这个问题。

在项目中,一般选择一个日志门面(抽象层)+ 一个日志实现。

Spring 框架默认使用 JCL 日志框架,SpringBoot 使用:Slf4j 日志门面+ Logback 日志框架 。

3 Slf4j日志门面

官网:https://www.slf4j.org/

目前市面上最流行的日志门面,现在的项目中,基本上都是使用Slf4j作为我们的日志系统。

3.1 Slf4j 日志绑定

开发的时,日志方法的调用,不应该直接调用日志框架具体实现,而是调用日志门面(抽象层)里的方法,即面向接口编程。

日志绑定:Slf4j日志门面依赖 + 日志实现依赖。

日志绑定流程:

(1)添加 slf4j-api 的依赖

(2)绑定具体的日志实现框架

a 绑定已经实现了 slf4j的日志框架,直接添加对应的依赖

b 绑定没有实现 slf4j的日志框架,先添加该日志框架的适配器依赖,再添加日志实现的依赖

(3)项目中使用 slf4j的api进行统一的日志记录

(4)slf4j有且仅有一个日志实现框架的绑定,如果出现多个日志实现框架,默认使用排在第一个依赖的实现

项目中先导入 Slf4j 依赖,再导入日志实现的依赖,如果需要适配器的日志实现,只需导入适配器依赖即可,因为在适配器中已经导入了对应的日志实现依赖,当然也可以继续导入日志实现的依赖。Slf4j绑定日志实现配置图如下:

注意:每一个日志框架都有自己的配置文件。使用Slf4j以后,配置文件还是需要使用所选择的日志实现的配置文件。比如使用 Slf4j + Logback ,那么配置文件使用的配置需要符合 logback 配置要求。

3.2 Slf4j 的使用

根据3.1 Slf4j日志绑定图,添加绑定依赖如下,添加依赖后,idea工具可在pom.xml文件中鼠标右键点击Diagrams选项,查看依赖结构图表,展开结构图表后,你会发现有的日志实现其实不用导入 Slf4j日志门面的依赖,如:reload4j日志实现,在适配器已经导入了Slf4j的api依赖,当然也可以导入Slf4j日志门面的依赖,不会有影响。

    <dependencies>
        <!-- Slf4j日志门面 -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.36</version>
        </dependency>

        <!-- 使用Slf4j内置的简单日志实现 -->
        <!--<dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.36</version>
        </dependency>-->
        <!-- 使用logback日志实现 -->
        <!-- <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.11</version>
        </dependency> -->
        <!-- slf4j-nop日志开关(该日志没有不会有输出) -->
        <!--<dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-nop</artifactId>
            <version>1.7.36</version>
        </dependency>-->
        
        <!-- 使用reload4j日志实现 则只需要导入适配器,
        不需要在导入reload4j.jar包本身,因为在适配器依赖里已经导入了,
        使用reload4j日志实需要添加log4j.properties配置文件才能输出日志 -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-reload4j</artifactId>
            <version>1.7.36</version>
        </dependency>
        <!--<dependency> 不需要在导入本身jar
            <groupId>ch.qos.reload4j</groupId>
            <artifactId>reload4j</artifactId>
            <version>1.2.20</version>
        </dependency>-->
        
        <!-- 使用jdk的logging日志实现 -->
        <!--<dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-jdk14</artifactId>
            <version>1.7.25</version>
        </dependency>-->

        <!-- junit单元测试 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>

测试类:

import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Slf4jTest {
    private static Logger log = LoggerFactory.getLogger(Slf4jTest.class);

    @Test
    public void test() {
        //使用slf4j日志门面的api,底层会自动调用具体日志实现
        log.trace("这是 trace 日志。。。");
        log.debug("这是 debug 日志。。。");
        log.info("这是 info 日志。。。");
        log.warn("这是 warn 日志。。。");
        log.error("这是 error 日志。。。");
    }
}

reload4j日志实现需要的配置 log4j.properties 文件。

log4j.rootLogger=debug, stdout, R

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout

log4j.appender.stdout.layout.ConversionPattern=%5p - %m%n

log4j.appender.R=org.apache.log4j.RollingFileAppender
log4j.appender.R.File=firestorm.log

log4j.appender.R.MaxFileSize=100KB
log4j.appender.R.MaxBackupIndex=1

log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n

log4j.logger.com.codefutures=DEBUG

3.3 桥接器/统一日志框架

不同的框架,往往都有自己默认的日志框架,如:Spring(JCL),Hibernate(jboss-logging)、MyBatis等等。统一日志框架,就是将别的日志框架,也一起统一使用slf4j进行日志输出。桥接器能够解决项目中日志的遗留问题,比如以前项目中使用log4j做日志记录,现在需要切换使用logback日志框架,没有桥接器以前需要去修改项目代码,有了slf4j桥接器后,可以通过桥接器直接切换到slf4j日志门面,再使用logback日志实现输出,并不需要去修改项目代码。统一日志框架也是一样,先将框架默认的日志框架排除,再使用桥接器,最后就可以做到统一日志输出。

桥接器的使用步骤:

​ (1)先去除之前的日志框架的依赖

​ (2)添加对应的slf4j提供的桥接器依赖(其实就是将桥接器替换掉原有的日志框架jar包)

​ (3)添加slf4j的具体日志实现

​ a 绑定已经实现了 slf4j的日志框架,直接添加对应的依赖

​ b 绑定没有实现 slf4j的日志框架,先添加该日志框架的适配器依赖,再添加日志实现的依赖

桥接器使用示意图:

桥接器的原理:

其实就是偷天换日,创建一个跟框架默认使用的日志框架一样的jar包(桥接器),然后将框架默认使用的日志jar去掉,桥接器跟原来的日志框架包路径相同,具有同样方法,只是在这些方法是空的,并在这些方法中做一层跳转,跳转到slf4j日志门面,当各框架调用自己日志方法输出的时候,最终就会调用slf4j输出。

    <dependencies>
        <!-- Slf4j日志门面依赖 -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.36</version>
        </dependency>

        <!-- 使用log4j日志实现,测试类中使用log4j日志实现的方法 -->
        <!--<dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-reload4j</artifactId>
            <version>1.7.36</version>
        </dependency>-->

        <!-- 使用slf4j桥接器(其实就是替换掉log4j的依赖) -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>log4j-over-slf4j</artifactId>
            <version>1.7.36</version>
        </dependency>

        <!-- 使用logback日志实现 -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.11</version>
        </dependency>

        <!-- junit单元测试 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>

 

测试类:使用桥接器后,测试类中的代码都不需要修改(包路径不需要修改,日志输出方法也不需要修改),经过桥接器跳转后,最终会调用logback输出。

//这里使用的是log4j的对象(包路径)
import org.apache.log4j.Logger;
import org.junit.Test;

public class Slf4jBridgingTest {
    // 定义log4j日志对象
    private static final Logger log4j = Logger.getLogger(Slf4jBridgingTest.class);
    // 测试桥接器
    @Test
    public void test() {
        log4j.trace("这是 trace 日志。。。");
        log4j.debug("这是 debug 日志。。。");
        log4j.info("这是 info 日志。。。");
        log4j.warn("这是 warn 日志。。。");
        log4j.error("这是 error 日志。。。");
    }
}

使用log4j输出日志:

DEBUG - 这是 debug 日志。。。
 INFO - 这是 info 日志。。。
 WARN - 这是 warn 日志。。。
ERROR - 这是 error 日志。。。

桥接后,日志门面为logback输出日志:

00:48:31.671 [main] DEBUG top.mengh.Slf4jBridgingTest - 这是 debug 日志。。。
00:48:31.681 [main] INFO top.mengh.Slf4jBridgingTest - 这是 info 日志。。。
00:48:31.681 [main] WARN top.mengh.Slf4jBridgingTest - 这是 warn 日志。。。
00:48:31.681 [main] ERROR top.mengh.Slf4jBridgingTest - 这是 error 日志。。。

4 logback 日志实现的使用

官网:https://logback.qos.ch/

logback是由log4j创始人设计的另外一个开源日志组件,性能比log4j要好。

logback主要分为三个模块:

logback-core:其他两个模块的基础模块

logback-classic:是log4j的一个改良版本,同时完整实现了slf4j的api

logback-access:访问模块与servlet容器集成,提供通过http来访问日志的功能

4.1 logback配置文件

logback启动时,会依次读取以下类型的配置文件,如果均不采用,会采用默认配置。

logback.groovy

logback-test.xml

logback.xml

logback组件之间的关系:

Logger:日志对象,记录器。把它关联到对应的context上后,主要用于存放日志对象,也可以定义日志类型、级别。

Appender:用于指定日志输出的目的地,目的地可以时控制台、文件、数据库等。

Layout:负责把事件转换成字符串,格式化的日志信息输出。在logback中,Layout对象被封装在encoder中。

logback.xml文件基本配置信息:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 配置集中管理属性 -->
<property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n"/>
<property name="htmlPattern" value="%-5level %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L %thread %m%n"/>
<!--
日志输出格式:
%-5level
%d{yyyy-MM-dd HH:mm:ss.SSS}日期
%c类的全限定名
%M为method
%L为行号
%thread线程名称
%m或者%msg为信息
%n换行
-->
<!-- 定义日志文件保存路径属性 -->
<property name="log_dir" value="./logs"/>
<!-- 控制台输出 appender 输出位置指定-->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<!-- 控制输出流对象,默认System.out,改为System.err -->
<target>System.err</target>
<!-- 日志消息格式配置 -->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${pattern}</pattern>
</encoder>
</appender>
<!-- 日志文件输出的 appender -->
<appender name="file" class="ch.qos.logback.core.FileAppender">
<!-- 日志文件保持路径 -->
<file>${log_dir}/logback.log</file>
<!-- 日志文件消息格式配置 -->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${pattern}</pattern>
</encoder>
<!-- 日志级别过滤器 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 日志过滤器级别,只输出error级别的日志 -->
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- html格式日志文件输出 appender -->
<appender name="htmlFile" class="ch.qos.logback.core.FileAppender">
<!-- 日志文件保存路径 -->
<file>${log_dir}/logback.html</file>
<!-- html 消息格式配置 -->
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="ch.qos.logback.classic.html.HTMLLayout">
<pattern>${htmlPattern}</pattern>
</layout>
</encoder>
</appender>
<!-- 日志文件拆分和归档压缩 的appender对象 -->
<appender name="rollFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 日志文件保持路径 -->
<file>${log_dir}/roll_logback.log</file>
<!-- 日志文件消息格式配置 -->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${pattern}</pattern>
</encoder>
<!-- 指定拆分规则 -->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 按照时间和压缩格式声明拆分的文件名 -->
<!--<fileNamePattern>${log_dir_window}/rolling.%d{yyyy-MM-dd-HH-mm-ss}.%i.log.gz</fileNamePattern>-->
<fileNamePattern>${log_dir}/%d{yyyy-MM-dd}/rolling.%d{yyyy-MM-dd-HH}.%i.log.gz</fileNamePattern>
<!-- 以文件大小为节点 -->
<maxFileSize>1MB</maxFileSize>
<!-- 以上的文件命名规则为:rolling.2022-05-01.0.log.gz,rolling.2022-05-01.1.log.gz
当文件大于1MB时,文件名后按1,2,3递增 -->
</rollingPolicy>
</appender>
<!-- 异步日志,提高性能,开启异步线程去执行日志输出,
不必等待日志输出完成后才执行我们的业务代码 -->
<appender name="async" class="ch.qos.logback.classic.AsyncAppender">
<!-- 指定某个具体的 appender -->
<appender-ref ref="rollFile"/>
</appender>
<!-- root logger配置 -->
<!--<root level="All">
<appender-ref ref="console"/>
<appender-ref ref="file"/>
<appender-ref ref="rollFile"/>
<appender-ref ref="htmlFile"/>
<appender-ref ref="async"/>
</root>-->
<!-- 自定义logger对象,如果没有自定义 logger对象,默认使用root,
additivity默认为true,继承root,
自定义logger对象可以针对不同的包路径下输出不同的日志 -->
<logger name="top.mengh.LogbackTest" additivity="false">
<appender-ref ref="console"/>
<appender-ref ref="rollFile"/>
</logger>
<!-- 自定义 logger对象 -->
<logger name="top.mengh.DemoTest"  level="info" additivity="false">
<appender-ref ref="console"/>
<appender-ref ref="file"/>
</logger>
</configuration>

5 log4j2的使用

log4j2参考了logback的一些优秀设计,并修复了一些问题,因此带来了一些重大的提升,主要有:

异常处理:在logback中,appender中的异常不会被应用感知到,但是在log4j2中,提供了一些异常处理机制;

性能提升:log4j2相较于log4j和logback都具有很明显的性能提升;

自动重载配置:参考了logback的设计,提供自动刷新参数配置,最实用的就是我们在生产上可以动态修改日志的级别而不需要重启应用;

无垃圾机制:log4j2在大部分情况下,都可以使用其设计一套无垃圾机制,避免频繁的日志收集导致的jvm gc。

log4j2的性能主要体现在:异步日志,和无垃圾机制。

5.1 log4j2入门

目前市面上最主流的日志门面就是slf4j,虽然log4j2也是日志门面,但大部分人习惯了slf4j,所以一般还是将log4j2看做日志实现,slf4j + log4j2搭配使用。

添加依赖

        <!-- log4j2日志门面 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.17.2</version>
</dependency>
<!-- log4j2日志实现 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.17.2</version>
</dependency>

java代码

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.Test;
public class Log4j2Test {
private static final Logger log = LogManager.getLogger(Log4j2Test.class);
@Test
public void test() {
log.fatal("这是 fatal 日志。。。");
log.trace("这是 trace 日志。。。");
log.debug("这是 debug 日志。。。");
log.info("这是 info 日志。。。");
log.warn("这是 warn 日志。。。");
log.error("这是 error 日志。。。");
}
}

5.2 使用slf4j作为日志门面

添加依赖

    <dependencies>
<!-- 使用slf4j作为日志门面 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.36</version>
</dependency>
<!-- 使用log4j的适配器进行绑定 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.17.2</version>
</dependency>
<!-- log4j2日志门面 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.17.2</version>
</dependency>
<!-- log4j2日志实现 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.17.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>

java代码

import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Slf4jTest {
private static final Logger log = LoggerFactory.getLogger(Slf4jTest.class);
@Test
public void test() {
log.trace("这是 trace 日志。。。");
log.debug("这是 debug 日志。。。");
log.info("这是 info 日志。。。");
log.warn("这是 warn 日志。。。");
log.error("这是 error 日志。。。");
}
}

5.3 log4j2配置

log4j2默认加载classpath下的 log4j2.xml文件中的配置。

<?xml version="1.0" encoding="UTF-8"?>
<!--
status=“warn” 日志框架本身的输出日志级别
monitorInterval="5" 自动加载配置文件的间隔时间,不低于5秒
-->
<configuration status="warn" monitorInterval="5">
<!-- 日志处理 -->
<appenders>
<!-- 控制台输出 appender对象 -->
<Console name="Console" target="SYSTEM_OUT">
<!-- 日志消息格式 -->
<PatternLayout pattern="[%d{HH:mm:ss.SSS}] [%t] [%-5level] %c{36}:%L --- %m%n"/>
</Console>
<!-- 日志文件输出 appender对象 -->
<File name="file" fileName="./logs/log4j2.log">
<!-- 日志消息格式 -->
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} --- %m%n"/>
</File>
<!-- 使用随机读写流的日志文件输出,appender对象,性能提高 -->
<RandomAccessFile name="accessFile" fileName="./logs/log4j-access.log">
<!-- 日志消息格式 -->
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%t] [%-5level] %l %c{36} --- %m%n"/>
</RandomAccessFile>
<!-- 按照一定规则拆分的日志文件,appender对象 -->
<RollingFile name="rollingFile" fileName="./logs/rollingFile.log"
filePattern="./logs/$${date:yyyy-MM-dd}/rolling-%d{yyyy-MM-dd-HH}.%i.log">
<!--日志级别过滤,只接受程序中DEBUG级别的日志进行处理-->
<ThresholdFilter level="DEBUG" onMatch="ACCEPT" onMismatch="DENY"/>
<!-- 日志消息格式 -->
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%t] [%-5level] %l %c{36} --- %m%n"/>
<policies>
<!-- 在系统启动时,触发拆分规则,生成一个新的日志文件 -->
<OnStartupTriggeringPolicy/>
<!-- 按照文件大小拆分,10MB -->
<SizeBasedTriggeringPolicy size="10MB"/>
<!-- 按照时间节点拆分,规则根据 filePattern 定义的-->
<TemiBaseTriggeringPolicy/>
</policies>
<!-- 防止日志文件太大,造成服务器内存溢出的问题,
在同一个目录下(也就是每天),文件的个数限定为 30 个,超过就进行覆盖 -->
<DefaultRolloverStrategy max="30"/>
</RollingFile>
</appenders>
<!-- 定义logger,只有定义了logger并引入的appender,appender才会生效 -->
<loggers>
<!--默认的root的logger,使用root配置日志级别-->
<!--<root level="DEBUG">
&lt;!&ndash; 指定日志处理器 &ndash;&gt;
<appender-ref ref="Console"/>
<appender-ref ref="file"/>
</root>-->
<!-- 自定义logger对象 -->
<logger name="top.mengh" additivity="false">
<appender-ref ref="Console"/>
<!--<appender-ref ref="file"/>
<appender-ref ref="accessFile"/>-->
<appender-ref ref="rollingFile"/>
</logger>
</loggers>
</configuration>

5.4 log4j异步日志

log4j2的优越性能主要依靠log4j2的异步日志功能,log4j2提供了两种方式实现日志的方式,一个时通过AsyncAppender,一个是通过AsyncLogger,分别对应Appender组件和Logger组件。

配置log4j2异步日志需要添加依赖

<!-- log4j2异步日志依赖 -->
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.4.4</version>
</dependency>

5.4.1 AsyncAppender 方式

​ 这种方式使用不多,因为它的性能跟logback相差无几。

<?xml version="1.0" encoding="UTF-8"?>
<configuration status="warn" monitorInterval="5">
<!-- 日志处理 -->
<appenders>
<!-- 日志文件输出 appender对象 -->
<File name="file" fileName="./logs/log4j2.log">
<!-- 日志消息格式 -->
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} --- %m%n"/>
</File>
<!-- 使用异步日志 -->
<!-- AsyncAppender 方式 -->
<Async name="Async">
<AppenderRef ref="file"/>
</Async>
</appenders>
<loggers>
<!-- 自定义logger对象 -->
<logger name="top.mengh" level="trace" additivity="false">
<appender-ref ref="Console"/>
<!-- 使用异步日志 appender 对象 -->
<AppenderRef ref="Async"/>
</logger>
</loggers>
</configuration>

5.4.2 AsyncLogger 方式

AsyncLogger 是官方推荐的异步方式,它可以使得调用logger.log返回的更快,有两种选择,可以使用全局异步和混合异步。

全局异步:就是所有的日志都使用异步记录,在配置文件上不用做任何的改动,只需要添加一个log4j2.component.properties配置文件,并添加内容:

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

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

<?xml version="1.0" encoding="UTF-8"?>
<configuration status="warn" monitorInterval="5">
<!-- 日志处理 -->
<appenders>
<!-- 控制台输出 appender对象 -->
<Console name="Console" target="SYSTEM_OUT">
<!-- 日志消息格式 -->
<PatternLayout pattern="[%d{HH:mm:ss.SSS}] [%t] [%-5level] %c{36}:%L --- %m%n"/>
</Console>
<!-- 日志文件输出 appender对象 -->
<File name="file" fileName="./logs/log4j2.log">
<!-- 日志消息格式 -->
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} --- %m%n"/>
</File>
</appenders>
<loggers>
<!-- 自定义logger对象,这里使用同步的方式 -->
<logger name="top.mengh.Log4j2Test" level="trace" additivity="false">
<appender-ref ref="Console"/>
</logger>
<!-- 自定义logger对象,使用异步日志 AsyncLogger 方式
includeLocation="false" 关闭日志记录的行号信息,开启输出行号信息性能可能会比同步记录还差-->
<AsyncLogger name="top.mengh.Slf4jTest" level="trace" includeLocation="false" additivity="false">
<AppenderRef ref="Console"/>
<AppenderRef ref="file"/>
</AsyncLogger>
</loggers>
</configuration>

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

(1)如果使用异步日志,AsyncAppender 、AsyncLogger 和全局异步这三种方式不要同时出现,同时出现会造成所有日志性能跟最差的AsyncAppender 方式一样。

(2)设置includeLocation=”false”,打印行号位置信息会降低异步日志的性能,性能可能会比同步日志还差。

java代码

import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Slf4jTest {
private static final Logger log = LoggerFactory.getLogger(Slf4jTest.class);
@Test
public void test() {
System.out.println("======start======");
System.out.println("======start======");
for (int i = 0; i < 10; i++) {
log.trace("这是 trace 日志。。。");
log.debug("这是 debug 日志。。。");
log.info("这是 info 日志。。。");
log.warn("这是 warn 日志。。。");
log.error("这是 error 日志。。。");
}
System.out.println("======end======");
System.out.println("======end======");
}
}

异步日志配置在控制台输出,会发现执行完了System.out.println()的代码,才会输出log日志。

 

======start======
======start======
======end======
======end======
[00:07:13.172] [main] [DEBUG] top.mengh.Slf4jTest: --- 这是 debug 日志。。。
[00:07:13.172] [main] [INFO ] top.mengh.Slf4jTest: --- 这是 info 日志。。。
[00:07:13.172] [main] [WARN ] top.mengh.Slf4jTest: --- 这是 warn 日志。。。
[00:07:13.172] [main] [ERROR] top.mengh.Slf4jTest: --- 这是 error 日志。。。

6 SpringBoot默认日志框架

6.1 SpringBoot 默认日志框架

SpringBoot底层默认使用的日志依赖:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
<scope>compile</scope>
</dependency>

底层依赖关系:
底层依赖关系

总结:

(1)SpringBoot 底层默认使用 Slf4j + Logback 方式进行日志记录;

(2)SpringBoot把其他的默认日志都替换成了slf4j;

SpringBoot使用日志最核心的总结:SpringBoot能自动适配所有的日志,而且底层使用slf4j+logback的方式记录日志,引入其他框架的时候,只需要把这个框架默认使用的日志框架排除掉。

SpringBoot使用日志的时候,只需要在类中获取Logger日志对象即可。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Demo{
private final Logger log = LoggerFactory.getLogger(Demo.class);
public void test(){
log.trace("这是 trace 日志。。。");
log.debug("这是 debug 日志。。。");
log.info("这是 info 日志。。。");
log.warn("这是 warn 日志。。。");
log.error("这是 error 日志。。。");
}
}

如果不想每次都使用LoggerFactory.getLogger()获取日志对象,则在 pom.xml文件中添加lombok依赖,再在使用的类上添加注解 @Slf4j 。添加lombok依赖,idea需要先安装lombok插件

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>

在类的上方贴 @Slf4j 注解,即可使用,如:

@Slf4j
public class Demo{
public void test(){
log.info("这是 info 日志。。。");
}
}

日志等级优先级: trace < debug < info < warn < error

springBoot默认使用的级别是 info级别,所以只会输出含 info 以上级别的日志。

6.2 指定配置文件

给类路径下放上每个日志框架自己的配置文件,那么springBoot就不会使用默认配置,在工程resource目录下放置。

日志实现 配置文件

spring:
profiles:
active: dev
# slf4j日志配置
logging:
level:
# 日志等级优先级 trace < debug < info < warn < error
# 使用trace级别, 分包配置级别,不同的包下可以使用不同的级别
top.mengh: trace
file:
# name是在当前项目下生成名为 springboot.log 日志文件
#name: springboot.log
# path是在指定目录下生成日志文件,默认名为spring.log,如下面的路径为项目根目录/logs/spring.log
# 如果同时指定 name 和 path 时,只有 name 生效。
path: ./logs
pattern:
# 指定控制台日志输出格式
console: "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n"
# 指定文件中日志格式
file: "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n"

可以通过切换环境使用不同的日志格式化输出,如logback.xml:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n"/>
<!-- 定义日志文件保存路径属性 -->
<property name="log_dir" value="./logs"/>
<!-- 控制台输出 appender 对象-->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!-- 开发环境的输出格式 -->
<springProfile name="dev">
<pattern>${pattern}</pattern>
</springProfile>
<!-- 生产环境的输出格式 -->
<springProfile name="pro">
<pattern>[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] ===== %m%n</pattern>
</springProfile>
</encoder>
</appender>
<!-- 日志文件输出的 appender -->
<appender name="file" class="ch.qos.logback.core.FileAppender">
<!-- 日志文件保持路径 -->
<file>${log_dir}/logback.log</file>
<!-- 日志文件消息格式配置 -->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${pattern}</pattern>
</encoder>
</appender>
<!-- 自定义 logger对象 -->
<logger name="top.mengh"  level="trace" additivity="false">
<appender-ref ref="console"/>
<appender-ref ref="file"/>
</logger>
</configuration>

6.3 日志实现切换 log4j2
添加依赖:先排除springboot默认的logback依赖,在添加log4j2的依赖。

        <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.4.3</version>
<!-- 排除springboot默认的logback依赖 -->
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-logging</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- 添加log4j2依赖,该依赖底层日志门面还是使用slf4j -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

 

这时候工程使用的配置文件得是log4j2的配置文件。


版权声明:本站文章,如无说明,均为本站原创,转载请注明文章来源。如有侵权,请联系博主删除。
本文链接:https://www.panziye.com/java/6214.html
喜欢 (0)
请潘老师喝杯Coffee吧!】
分享 (0)
用户头像
发表我的评论
取消评论
表情 贴图 签到 代码

Hi,您需要填写昵称和邮箱!

  • 昵称【必填】
  • 邮箱【必填】
  • 网址【可选】