日志

使用log4j2 打印日志,对一些敏感数据进行脱敏。【通过配置选择哪个日志生效】

一、常见日志框架

日志框架采用外观模式来实现,给系统提供门面,不暴露具体实现细节。

1、外观模式

使用者只需和外观角色打交道就行

外观模式

2、门面

2.1 slf4j

SLF4J是一款Java程序编写的日志门面框架,其本身定义了统一的日志接口,且对不同的日志实现框架进行抽象化,我们的应用只需要跟SLF4J进行沟通

在这里插入图片描述

在引入包的时候,需要同时引入slf4j.jar,日志实现.jar,以及对应的适配包.jar

日志使用中都是引入slf4j包里面的

1
2
3
4
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;  

private final  static Logger logger = LoggerFactory.getLogger(SLF4JandLog4j.class);

在执行的时候选择具体的实现系统

image-20220716174927441

Slf4j实现机制: Slf4j在编译期间,静态绑定本地的LOG库,因此可以在OSGi中正常使用。它是通过查找类路径下org.slf4j.impl.StaticLoggerBinder,然后绑定工作都在这类里面进。

2.2 Commons Logging

是一个基于Java的日志记录实用程序,是用于日志记录和其他工具包的编程模型。它通过其他一些工具提供API,日志实现和包装器实现。Commons Loggin自动搜索并使用Log4j(Log4j是另一个流行的日志系统),如果没有找到Log4j,再使用JDK Logging。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class Main {
    public static void main(String[] args) {
        Log log = LogFactory.getLog(Main.class);
        log.info("start...");
        log.warn("end.");
    }
}

3、实现

log4j

logback

Springboot 默认会使用logback 来记录日志,引入spring-boot-starter-logging的时候会引入如下包

image-20220716183450856

pom

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
 <!--比较纯粹的时候logback-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>jul-to-slf4j</artifactId>
                    <groupId>org.slf4j</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>log4j-to-slf4j</artifactId>
                    <groupId>org.apache.logging.log4j</groupId>
                </exclusion>
            </exclusions>
        </dependency>

Logback 则支持 XML 与 groovy 两种方式配置方式;

配置

默认情况下,Spring Boot将日志输出到控制台,不会写到日志文件。可以在application.propertiesapplication.yml配置,这样只能配置简单的场景,保存路径、日志格式等。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
logging:
  level:
    #    全局输出级别
    root: info
    #    指定包输出级别
    com.huochai.controller: error
  pattern:
    #    输出格式(控制台输出格式)
    console: "%d - %msg%n"
    #    输出格式(文件输出格式)
    file: "%d - %msg%n"
  #    日志输出文件的位置
  file:
    /Users/peilizhi/javaProects/log/logback.log
  #    日志输出文件的目录
  path:
    #    没有指定logging.file 时会默认创建一个spring.log文件用于记录
    /Users/peilizhi/javaProects/log
  #  自定义配置文件
  # classpath 与文件地址要写在同一行中才有效
  config: classpath:log/logback-test.xml

在配置的时候 系统不建议使用file 与 path 参数,可能是想通过自定义配置来指定文件输出位置

  • logging.file,设置文件,可以是绝对路径,也可以是相对路径。如:logging.file=my.log
  • logging.path,设置目录,会在该目录下创建spring.log文件,并写入日志内容。
  • 如果同时指定了logging.file 与logging.path 的话,logging.file 会生效,logging.path 不会生效

自定义配置

是xml 形式的配置文件,只需增加固定开头之后,可以直接书写配置

固定开头:

configuration
  • debug: 默认false

    • ​ true :打印logback 内部执行日志
  • scan; 默认值为true

    • true时,配置文件如果发生改变,将会被重新加载
  • scanPeriod :加载的时间间隔,默认的时间间隔为1分钟。

    如果开启自动加载的话,有任何改动应该是立即生效的

appender :

负责写日志的组件,定义了一些输出的格式、文件大小等

  • name : 名称
  • class : 全类名,常见的有ch.qos.logback.core.ConsoleAppender【控制台输出】、ch.qos.logback.core.rolling.RollingFileAppender[文件输出]。
ConsoleAppender

用于控制台输出

  • encode :对输出日志进行格式规范
  • target : 字符串System.out(默认)或者System.err
FileAppender

负责写日志到文件中

  • file:被写入的文件名,可以是相对目录,也可以是绝对目录,如果上级目录不存在会自动创建,没有默认值。  
  • append:如果是 true,日志被追加到文件结尾,如果是 false,清空现存文件,默认是true。
  • encoder:对记录事件进行格式化。
  • prudent:如果是 true,日志会被安全的写入文件,即使其他的FileAppender也在向此文件做写入操作,效率低,默认是 false。
RollingFileAppender

滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件。可用于每天记录不同的日志到不同的文件中

  • file:被写入的文件名,可以是相对目录,也可以是绝对目录,如果上级目录不存在会自动创建,没有默认值。

  • append:如果是 true,日志被追加到文件结尾,如果是 false,清空现存文件,默认是true。

  • rollingPolicy :当发生滚动时,决定RollingFileAppender的行为,涉及文件移动和重命名。属性class定义具体的滚动策略类

    • TimeBasedRollingPolicy

      按照一定的时间执行滚动

      • fileNamePattern :包含文件名及“%d”转换符,“%d”可以包含一个java.text.SimpleDateFormat指定的时间格式,如:%d{yyyy-MM}。这就是滚动的时间规律,
      • maxHistory:控制保留的归档文件的最大数量,超出数量就删除旧文件。假设设置每个月滚动,且maxHistory是6,则只保存最近6个月的文件,删除之前的旧文件
    • SizeBasedTriggeringPolicy

      根据文件大小进行滚动

      • maxFileSize:这是活动文件的大小,默认值是10MB。
      • triggeringPolicy : 告知 RollingFileAppender 合适激活滚动。
logger

用于控制指定包或者类的打印策略,可以指定level,appender

  • name : 全类名
  • level :日志级别 大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL和OFF,还有一个特殊值INHERITED或者同义词NULL,代表强制执行上级的级别。 如果未设置此属性,那么当前loger将会继承上级的级别。
    • append-ref : 指定的输出类,是之前appender 标签中name 字段的引用(可以指定多个)
  • addtivity: 是否向上级logger传递打印信息。默认是true。可以包含零个或多个元素,标识这个appender将会添加到这个logger。
root

它也是元素,但是它是根loger,是所有的上级。只有一个level属性,因为name已经被命名为"root",且已经是最上级了。

日志格式

%m (%msg)输出代码中指定的消息

%p(%level) 输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL

%r输出自应用启动到输出该log信息耗费的毫秒数

%c输出所属的类目,通常就是所在类的全名

%t输出产生该日志事件的线程名

%n输出一个回车换行符,Windows平台为“\r\n”,Unix平台为“\n”

%d输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss,SSS},

%l输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数。举例:Testlog4.main(TestLog4.java:10)

%thread 线程名

logback的配置,需要配置输出源appender,打日志的logger(子节点)和root(根节点),实际上,它输出日志是从子节点开始,子节点如果有输出源直接输入,如果无,判断配置的addtivity,是否向上级传递,即是否向root传递,传递则采用root的输出源,否则不输出日志。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
<?xml version="1.0" encoding="UTF-8"?>
<!--打印logback内部执行日志-->
<configuration debug="true" scan="true" scanPeriod="60 seconds">

    <!--定义一些常用的参数-->
    <property name="LOG_PATH" value="/Users/peilizhi/javaProects/log"/>

    <!--控制台日志, 控制台输出 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <!--        class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"-->
        <encoder>
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度,%msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!--文件日志, 按照每天生成日志文件 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--当天正在使用的日志-->
        <file>${LOG_PATH}/logback.log</file>

        <!--基于文件大小和时间的滚动策略-->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--日志文件输出的文件名【历史记录】-->
            <FileNamePattern>${LOG_PATH}/logback-%d{yyyy-MM-dd}-%i.log</FileNamePattern>
            <!--日志文件保留天数-->
            <MaxHistory>60</MaxHistory>
            <!--日志文件最大的大小-->
            <MaxFileSize>10MB</MaxFileSize>
        </rollingPolicy>

        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>

        <!-- 日志过滤 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!-- 指定日志级别 -->
            <level>ERROR</level>
            <!-- 匹配则全部接受 -->
            <onMatch>ACCEPT</onMatch>
            <!-- 不匹配则全部拒绝 -->
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>


    <!-- 输出ERROR日志 -->
    <appender name="ERROR_LOG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/logback-error.log</file>
        <!-- 基于文件大小和时间的滚动策略 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/logback-error-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
            <!-- 日志文件保留天数 -->
            <maxHistory>60</maxHistory>
            <!-- 单个日志文件大小 -->
            <maxFileSize>10MB</maxFileSize>
        </rollingPolicy>
        <!-- 日志输出格式 -->
        <encoder>
            <pattern>[%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36}: %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <!-- 日志过滤 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!-- 指定日志级别 -->
            <level>ERROR</level>
            <!-- 匹配则全部接受 -->
            <onMatch>ACCEPT</onMatch>
            <!-- 不匹配则全部拒绝 -->
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>


    <appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
        <!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
        <discardingThreshold>0</discardingThreshold>
        <!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
        <queueSize>512</queueSize>
        <!-- 添加附加的appender,最多只能添加一个 -->
        <appender-ref ref="FILE"/>
    </appender>

    <!-- show parameters for hibernate sql 专为 Hibernate 定制 -->
    <logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE"/>
    <logger name="org.hibernate.type.descriptor.sql.BasicExtractor" level="DEBUG"/>
    <logger name="org.hibernate.SQL" level="DEBUG"/>
    <logger name="org.hibernate.engine.QueryParameters" level="DEBUG"/>
    <logger name="org.hibernate.engine.query.HQLQueryPlan" level="DEBUG"/>

    <!--myibatis log configure-->
    <logger name="com.apache.ibatis" level="TRACE"/>
    <logger name="java.sql.Connection" level="DEBUG"/>
    <logger name="java.sql.Statement" level="DEBUG"/>
    <logger name="java.sql.PreparedStatement" level="DEBUG"/>

    <!-- 日志输出级别 -->
    <root level="DEBUG">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="ASYNC_FILE"/>
        <appender-ref ref="ERROR_LOG_FILE"/>
    </root>
</configuration>

异步输出

AsyncAppender,异步记录日志。

AsyncAppender并不处理日志,只是将日志缓冲到一个BlockingQueue里面去,并在内部创建一个工作线程从队列头部获取日志,之后将获取的日志循环记录到附加的其他appender上去,从而达到不阻塞主线程的效果。因此AsynAppender仅仅充当事件转发器,必须引用另一个appender来做事。

由于使用了BlockingQueue来缓存日志,因此就会出现队列满的情况。正如上面原理中所说的,在这种情况下,AsyncAppender会做出一些处理:默认情况下,如果队列80%已满,AsyncAppender将丢弃TRACE、DEBUG和INFO级别的event

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<appender name="FILE" class= "ch.qos.logback.core.rolling.RollingFileAppender">  
            <!-- 按天来回滚,如果需要按小时来回滚,则设置为{yyyy-MM-dd_HH} -->  
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">  
                 <fileNamePattern>/opt/log/test.%d{yyyy-MM-dd}.log</fileNamePattern>  
                 <!-- 如果按天来回滚,则最大保存时间为1天,1天之前的都将被清理掉 -->  
                 <maxHistory>30</maxHistory>  
            <!-- 日志输出格式 -->  
            <layout class="ch.qos.logback.classic.PatternLayout">  
                 <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%msg%n</Pattern>  
            </layout>  
</appender> 
  
  <!-- 异步输出 -->  
  <appender name ="ASYNC" class= "ch.qos.logback.classic.AsyncAppender">  
    <!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->  
    <discardingThreshold >0</discardingThreshold>  
    <!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->  
    <queueSize>512</queueSize>  
    <!-- 添加附加的appender,最多只能添加一个 -->  
    <appender-ref ref ="FILE"/>  
  </appender>  
       
     <root level ="trace">  
            <appender-ref ref ="ASYNC"/>  
     </root>  

log4j2

pom

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <!-- 排除默认的logback日志-->
            <exclusions>
                <exclusion>
                    <artifactId>spring-boot-starter-logging</artifactId>
                    <groupId>org.springframework.boot</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- 引入log4j2配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
            <version>${spring-log4j2.version}</version>
        </dependency>

        <!-- 引入异步log4j2-->
        <dependency>
            <groupId>com.lmax</groupId>
            <artifactId>disruptor</artifactId>
            <version>${disruptor}</version>
        </dependency>

配置

1
2
logging:
  config: classpath:log/log4j2-test.xml

自定义配置

其中log4j2支持多个格式自定义配置:log4j2.properties log4j2.yaml log4j2.json `log4j2.xml

加载的优先级为 properties > yaml > json > xml

配置文件的节点与logback文件节点大致一样

configuration
  • status :这个属性表示log4j2本身的日志信息打印级别。如果把status改为TRACE再执行测试代码,可以看到控制台中打印了一些log4j加载插件、组装logger等调试信息。

​ 日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL

  • monitorInterval: Log4j2 能够自动检测修改配置 文件和重新配置本身,设置间隔秒数
appender

有RollingFile、Console、RollingRandomAccessFile。

RollingRandomAccessFileAppenderRollingFileAppender 在功能上基本一致,但是底层的实现有所区别,RollingFileAppender 底层是 BufferedOutputStream,RollingRandomAccessFileAppender 底层是使用 ByteBuffer + RandomAccessFile ,性能上有了很大的提升。

RollingRandomAccessFileAppender
  • fileName 指定当前日志文件的位置和文件名称
  • filePattern 指定当发生Rolling时,文件的转移和重命名规则
  • SizeBasedTriggeringPolicy 指定当文件体积大于size指定的值时,触发Rolling
  • DefaultRolloverStrategy 指定最多保存的文件个数

TimeBasedTriggeringPolicy 这个配置需要和filePattern结合使用,注意filePattern中配置的文件重命名规则是${log_path}/zcrTest%d{yyyy-MM-dd}.log,最小的时间粒度是dd,即分钟,TimeBasedTriggeringPolicy指定的size是1,结合起来就是每一天生成一个新文件。如果改成%d{yyyy-MM-dd HH},最小粒度为小时,则每一个小时生成一个文件。

filers

Log4j2 中的过滤日志的配置,可以排除一些不需要的配置 。根据范围分为:

Context-wide(全局配置),Logger ,Appender ,Appender Reference

配置文件中节点的顺序, 全局Filter节点<Filters> 必须放在<properties>节点之后。 否则会出现意料不到的问题。例如本人碰到的就是 <PatternLayout pattern="${pattern_debug_info_warn}" /> 替换失败。

经常使用的filter

  • CompositeFilter : 组合模式,用以向外界提供统一的访问接口。

  • ThresholdFilter : 这个Filter负责按照所配置的日志级别过滤Log Event,等于或超出所配置级别的log Event将返回onMatch结果。(threshold : 阈值, 临界值; 门槛,入口)

  • LevelRangeFilter : 这个Filter也是负责按照日志级别过滤Log,只是相比较于ThresholdFilter,它比较的是指定区间范围。这里有一个需要密切注意就是其两个参数minLevel,maxLevel的配置;其和我们的思维惯式有着些许的差异,可能会造成困扰。细节之处可以查看StandardLevel中各个日志级别的底层数值,以及Level.isInRange的比较逻辑。概括而言就是 底层支撑的数值越小,日志危险级别越高。也就是我们如果想要只保留WARN到ERROR级别的日志,那么应该如下配置:

    ```xml
    <LevelRangeFilter minLevel="ERROR" maxLevel="WARN"
            onMatch="ACCEPT">
        </LevelRangeFilter>
    ```
    
  • DynamicThresholdFilter :这个Filter允许依据ThreadContext中是否存在特定的某些值,以及日志级别来过滤LOG。

策略

ACCEPT(接受)DENY(拒绝)NEUTRAL(中立)

中立适合就是当前过滤器暂时不处理,交给后面的过滤器处理

1
2
3
4
5
6
<Filters>
    <!--如果是error级别拒绝,设置 onMismatch="NEUTRAL" 可以让日志经过后续的过滤器-->
    <ThresholdFilter level="error" onMatch="DENY" onMismatch="NEUTRAL"/>
    <!--如果是debug\info\warn级别的日志输出-->
    <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
完整配置
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<Configuration status="info">

    <Properties>
        <property name="LOG_PATH" value="/Users/peilizhi/javaProects/log"/>
    </Properties>

    <Filters>
        <!-- 全局级别Filter,放在property之后 -->
        <LevelRangeFilter minLevel="ERROR" maxLevel="DEBUG" onMatch="ACCEPT"/>
    </Filters>

    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n"/>
        </Console>

        <RollingRandomAccessFile name="fileAppender"
                                 fileName="${LOG_PATH}/log4j2.log"
                                 filePattern="${LOG_PATH}/log4j2-%d{yyyyMMddHH}-%i.log"
                                 append="true" immediateFlush="false">
            <PatternLayout
                    pattern="%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n"/>
            <Policies>
                <TimeBasedTriggeringPolicy/>
                <SizeBasedTriggeringPolicy size="250 MB"/>
            </Policies>
        </RollingRandomAccessFile>


        <RollingRandomAccessFile name="errorAppender"
                                 fileName="${LOG_PATH}/log4j2-error.log"
                                 filePattern="${LOG_PATH}/log4j2-error-%d{yyyyMMddHH}-%i.log"
                                 append="true" immediateFlush="false">
            <PatternLayout
                    pattern="%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n"/>
            <Policies>
                <TimeBasedTriggeringPolicy/>
                <SizeBasedTriggeringPolicy size="250 MB"/>
            </Policies>

            <!--appender 级别filer-->
            <Filters>
                <ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
            </Filters>
        </RollingRandomAccessFile>


    </Appenders>

    <Loggers>
        <AsyncRoot level="info" includeLocation="true">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="fileAppender"/>
            <AppenderRef ref="errorAppender"/>
        </AsyncRoot>
    </Loggers>
</Configuration>
日志格式

%d 表示时间,默认情况下表示打印完整时间戳 2012-11-02 14:34:02,123,可以调整 %d 后面的参数来调整输出的时间格式

%p 表示输出日志的等级,可以使用 %highlight{%p} 来高亮显示日志级别

%c 用来输出类名,默认输出的是完整的包名和类名,%c{1.} 输出包名的首字母和完整类名

%t 表示线程名称

%m 表示日志内容,%M 表示方法名称

%n 表示换行符

%L 表示打印日志的代码行数

4、对比

老牌的 Log4j2 凭借着入场早、背靠 Apache 两大优势有着不错的用户支持,官网文档也很完善。

新生的 Logback 凭借着 SLF4j 的原生实现以及被 Spring Boot 钦点的日志框架(Spring 也提供了Log4j2 的 starter,切换依赖即可完成更换日志框架,前文讲过,此处不再赘述),同样也拥有着很好的前景。

社区支持方面,Log4j2 作为 Apache 顶级项目,支持度应该是很不错的,Logback 作为Ceki创业后的产物,也能有很好的保证,二者生态可谓不相上下。

日志的功能我们从使用者的角度可以分为:配置、使用、以及独有特性。配置文件方面,Log4j 提供了更多的配置文件配置方式,Log4j2 支持 properties、YAML、JSON、XML四种,Logback 则支持 XML 与 groovy 两种方式;

Appender 方面,两者均支持自定义扩展 Appender ,Log4j2 并且提供了几乎全部场景的 Appender,文件、控制台、JDBC、NoSql、MQ、Syslog、Socket、SMTP等,Logback提供 Appender 略少于 Log4j2,提供了文件、控制台、数据库、Syslog、Socket、SMTP等,动态化输出方面,Log4j2 提供了ScriptAppenderSelector,Logback 则提供了 Evaluator 与 SiftingAppender(两者均可以用于判断并选择对应的 Appender);

独有特性方面,Logback 支持 Receivers, 可以接收其他 Logback 的 Socket Appender 输出,Logbak 还拥有 logback-access 模块,可以用来与 Servlet容器(如 Tomcat 和 Jetty)集成,提供 http 访问日志功能;Log4j2 则拥有号称能够减少 JVM 垃圾回收停顿时间的 Garbage-free(无垃圾模式),Log4j2 API 支持使用 Java 8 lambda,SLF4j 则在 2.0 版本。

总体来说,Logback 更加简单,并且与self4j 匹配更好(同一作者)。log4j2功能更加强大(filter过滤功能)

二、日志输出规范

日志级别

  • FATAL:系统级的错误,一旦出现,就意味着整个系统不能服务,是最严重的日志级别,一个进程的生命周期中应该只记录一次FATAL级别的日志,即该进程遇到无法恢复的错误而退出时
  • ERROR: 服务错误日志,项目还能正常使用,但是影响客户正常使用,而对于用户自己操作不当,如请求参数错误等等,是绝对不应该记为ERROR日志的
  • WARN: 该日志表示系统可能出现问题,也可能没有,这种情况如网络的波动等。对于那些目前还不是错误,然而不及时处理也会变为错误的情况,也可以记为WARN日志.
  • INFO: 系统正常运行日志。某个子系统的初始化,某个请求的成功执行等等;记录一些项目执行的关键节点。INFO日志不宜过多,通常情况下,INFO级别的日志应该不大于TRACE日志的10%;
  • DEBUG or TRACE:精确记录执行的过程,使用什么入参、执行顺序是什么、执行何种操作。

日志输出建议

  • 日志输出要精简,能够根据前后文推断出来的内容就不要再打了

  • 日志的目的是为了不重现问题的情况下,根据日志输出对问题进行诊断。

  • 生产环境的日志级别尽可能的设置高一点,避免大量日志打印

  • 在执行核心动作时要打日志记录,记录为INFO

  • 增删改操作需要打印参数日志(以便定位一些异常业务问题)

  • 条件分支需要打印日志:包括条件值以及重要参数;

  • 异常信息应该包括两类信息案发现场信息异常堆栈信息。如果不处理,那么通过关键字throws/throw 往上抛出,由父级方法处理

  • 不允许记录日志后又抛出异常,因为这样会多次记录日志

    反例

    1
    2
    3
    4
    
    if (virtualIpPortCrash) {
                log.error("接入虚拟IP端口冲突");
                throw new BusinessException("接入虚拟IP端口冲突");
            }
    
  • 不允许出现 e.printStackTrace

  • 使用 {} 占位符

  • 建议规范:

    1
    
    2019-12-01 00:00:00.000|pid|log-level|[svc-name,trace-id,span-id,user-id,biz-id]|thread-name|package-name.class-name : log message
    
  • 打印异常

    • log.error(“异常信息: “, e); 能打印完整异常堆栈信息,建议使用,不要使用e.printStackTrace();
    • log.error(“异常信息: {}”, e.getMessage()); // 最简单的异常信息,没有异常类,没有堆栈
    • log.error(“异常信息: {}”, e.toString()); // 最简单的异常信息,有异常类,没有堆栈

三、日志拓展

1、 日志脱敏

链接:https://blog.csdn.net/qq_40885085/article/details/113385261

Built with Hugo
主题 StackJimmy 设计

本站访客数 人次   总访问量   本文阅读量