一、前言

  1. 记录应用系统曰志主要有三个原因 记录操作轨迹、监控系统运行状况、回溯系统故障。记录操作行为及操作轨迹数据,可以数据化地分析用户偏好,有助于优化业务逻辑,为用户提供个性化的服务。例如,通过 access.log 记录用户的操作频度和跳转链接,有助于分析用户的后续行为。<br /> 全面有效的日志系统有助于建立完善的应用监控体系,由此工程师可以实时监控系统运行状况,及时预警,避免故障发生。监控系统运行状况,是指对服务器使用状态,如内存、 CPU 等使用情况,应用运行情况 如响应时间 QPS 等交互状态;应用错误信息,如空指针、 SQL 异常等的监控。例如,在 CPU 使用率大于 60%, 四核服务器中load 大于4时发出报警,提醒工程师及时处理,避免发生故障。<br /> 当系统发生线上问题时,完整的现场日志有助于工程师快速定位问题。例如当系统内存溢出时,如果日志系统记录了问题发生现场的堆信息,就可以通过这个曰志分析是什么对象在大量产生并且没有释放内存,回溯系统故障,从而定位问题。

二、规范

推荐的日志文件命名方式为appName_logType_logName.log 其中 logType为日志类型,推荐分类有 stats monitor visit等, logName 为日志描述。这种命名的好处是通过文件名就可以知道曰志文件属于什么应用,什么类型 ,什么目的,也有利于归类查找。
例如, mppserver 应用中单独监控时区转换异常的日志文件名定义为mppserver__monitor_timeZoneConvert.log

保存时间:

代码规约推荐曰志文件至少保存15天,可以根据日志文件的重要程度、文件大小及磁盘空间再自行延长保存时间。

预先判断日志级别:

对DEBUG 、INFO 级别的日志,必须使用条件输出或者使用占位符的方式打印。该约定综合考虑了程序的运行效率和日志打印需求。例如 在某个配置了打印日志级别为WARN 的应用中,如果针对 DEBUG 级别的日志,仅仅在程序中写出 logger.debug(”Processing trade with id:” + id + ” and symbol:"+ symbol);,那么该日志不会被打印但是会执行字符串拼接操作,如果 symbol 是对象 还会执行 toString() 方法
白白浪费了系统资源。
如下示例代码为正确的打印日志方式:

  1. //使明条件判断形式
  2. if (logger.isDebugEnabled()) {
  3. logger.debug ("Processing trade with id:" + id + "and symlbol:" + symbol) ;
  4. }
  5. //使用占位符形式
  6. logger.debug ("Processing trade with id: {} and symbol: {}",id, symbol);

避免无效日志打印:

生产环境禁止输出 DEBUG 曰志且有选择地输出 INFO日志。使用 INFO、WARN 级别来记录业务行为信息时,一定要控制日志输出量,以免磁盘空间不足。同时要为曰志文件设置合理的生命周期及时清理过期的日志。避免重复打印,务必在日志配置文件中设置 additivity=false。
区别对待错误日志:
WARN、ERROR 都是与错误有关的日志级别,但不要一发生错误就笼统地输出ERROR 级别日志。 一些业务异常是可以通过引导重试就能恢复正常的,例如用户输入参数错误。在这种情况下,记录日志是为了在用户咨询时可以还原现场,如果输出ERROR 级别就表示一旦出现就需要人为介入,这显然不合理。所以,ERROR只记录系统逻辑错误、异常或者违反重要的业务规则,其他错误都可以归为 WARN级别。

保存记录内容完整:

曰志记录的内容包括现场上下文信息与异常堆栈信息,所以打印时需要注意以下两点:

  • 记录异常时一定要输出异常堆栈,例如 logger.error("xxx" +e.getMessage(),e)
  • 曰志中如果输出对象实例,要确保实例类重写了 toString()方法,否则只会输出对象的 hashCode 没有实际意义。

    三、SpringBoot默认日志配置

    1.由低到高:trace < debug < info < warn < error
    2. Spring Boot默认设定的是 info 级别日志,(日志默认级别也称为root级别)。可修改默认级别日志:logging.level.root=级别名
    3. 可以进行调整日志级别,设定某个级别后,就只打印设定的这个级别及后面高级别的日志信息。没有指定级别的就用SpringBoot默认规定的级别:root级别
    4. 可修改指定包的日志级别:指定某个包下面的所有日志级别:logging.level.包名=级别名

分析日志底层实现

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-web</artifactId>
  4. </dependency>
  5. <!--spring-boot-starter-web 中引入了 spring-boot-starter 启动器 -->
  6. <dependency>
  7. <groupId>org.springframework.boot</groupId>
  8. <artifactId>spring-boot-starter</artifactId>
  9. <version>2.3.1.RELEASE</version>
  10. <scope>compile</scope>
  11. </dependency>
  12. <!--spring-boot-starter 中引入了 spring-boot-starter-logging 日志启动器 -->
  13. <dependency>
  14. <groupId>org.springframework.boot</groupId>
  15. <artifactId>spring-boot-starter-logging</artifactId>
  16. <version>2.3.1.RELEASE</version>
  17. <scope>compile</scope>
  18. </dependency>
  19. <!--spring-boot-starter-logging 日志启动器 采用的是 logback 日志框架 -->
  20. <dependency>
  21. <groupId>ch.qos.logback</groupId>
  22. <artifactId>logback-classic</artifactId>
  23. <scope>compile</scope>
  24. </dependency>

总结:SpringBoot中默认日志启动器为 spring-boot-starter-logging ,默认采用的是 logback 日志框架

spring-boot-2.3.1.RELEASE.jar! \org\springframework\boot\logging\logback\base.xml 做了日志的默认配置**

  1. <included>
  2. <!--日志格式默认规定-->
  3. <include resource="org/springframework/boot/logging/logback/defaults.xml" />
  4. <!--日志文件默认生成路径-->
  5. <property name="LOG_FILE"value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/spring.log}"/>
  6. <!--控制台日志信息默认配置-->
  7. <include resource="org/springframework/boot/logging/logback/console-appender.xml" /> <!--文件中日志信息默认配置-->
  8. <include resource="org/springframework/boot/logging/logback/file-appender.xml" /> <!--日志级别默认为: info -->
  9. <root level="INFO">
  10. <appender-ref ref="CONSOLE" />
  11. <appender-ref ref="FILE" />
  12. </root>
  13. </included>

四、自定义日志配置

1.首先需要修改依赖

修改pom依赖

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-web</artifactId>
  4. <exclusions><!-- 去掉springboot默认配置 -->
  5. <exclusion>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-logging</artifactId>
  8. </exclusion>
  9. </exclusions>
  10. </dependency>
  11. <dependency> <!-- 引入log4j2依赖 -->
  12. <groupId>org.springframework.boot</groupId>
  13. <artifactId>spring-boot-starter-log4j2</artifactId>
  14. </dependency>

yml文件配置

logging:

  file:
      #在当前项目下生成日志文件
    name: demo.log #文件名
    #在指定路径下生成日志
    path: D:/spring/log
  pattern:
    console: '%clr(%d{yyyy-MM-dd} [%thread] %-5level %logger{50} - %msg%n)'
    file: '%d{yyyy-MM-dd HH:mm:ss.SSS} >>> [%thread] >>> %-5level >>> %logger{50} >>> %msg%n'
    #path: logs/log_lzy
  config: classpath:logconfig.xml #xml配置地址

注意:如上,yml文件中首尾加上单引号可解决识别不了%的问题,properties不需要加

可以采用xml的方式进行配置

<?xml version="1.0" encoding="UTF-8"?>
<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<configuration monitorInterval="5">
    <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->

    <!--变量配置-->
    <Properties>
        <!-- 格式化输出:%date表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符-->
        <!-- %logger{36} 表示 Logger 名字最长36个字符 -->
        <property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" />
        <!-- 定义日志存储的路径 -->
        <property name="FILE_PATH" value="./logs/log4j2" />
        <property name="FILE_NAME" value="lzy-log4j2-demo" />
    </Properties>

    <appenders>

        <console name="Console" target="SYSTEM_OUT">
            <!--输出日志的格式-->
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <!--控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
            <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
        </console>

        <!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,适合临时测试用-->
        <File name="Filelog" fileName="${FILE_PATH}/test.log" append="false">
            <PatternLayout pattern="${LOG_PATTERN}"/>
        </File>

        <!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
        <RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/info.log" filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz">
            <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
            <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <Policies>
                <!--interval属性用来指定多久滚动一次,默认是1 hour-->
                <TimeBasedTriggeringPolicy interval="1"/>
                <SizeBasedTriggeringPolicy size="10MB"/>
            </Policies>
            <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
            <DefaultRolloverStrategy max="15"/>
        </RollingFile>

        <!-- 这个会打印出所有的warn及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
        <RollingFile name="RollingFileWarn" fileName="${FILE_PATH}/warn.log" filePattern="${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz">
            <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
            <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <Policies>
                <!--interval属性用来指定多久滚动一次,默认是1 hour-->
                <TimeBasedTriggeringPolicy interval="1"/>
                <SizeBasedTriggeringPolicy size="10MB"/>
            </Policies>
            <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
            <DefaultRolloverStrategy max="15"/>
        </RollingFile>

        <!-- 这个会打印出所有的error及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
        <RollingFile name="RollingFileError" fileName="${FILE_PATH}/error.log" filePattern="${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz">
            <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
            <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <Policies>
                <!--interval属性用来指定多久滚动一次,默认是1 hour-->
                <TimeBasedTriggeringPolicy interval="1"/>
                <SizeBasedTriggeringPolicy size="10MB"/>
            </Policies>
            <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
            <DefaultRolloverStrategy max="15"/>
        </RollingFile>

    </appenders>

    <!--Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。-->
    <!--然后定义loggers,只有定义了logger并引入的appender,appender才会生效-->
    <loggers>

        <!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
        <logger name="org.mybatis" level="info" additivity="false">
            <AppenderRef ref="Console"/>
        </logger>
        <!--监控系统信息-->
        <!--若是additivity设为false,则 子Logger 只会在自己的appender里输出,而不会在 父Logger 的appender里输出。-->
        <Logger name="org.springframework" level="info" additivity="false">
            <AppenderRef ref="Console"/>
        </Logger>

        <root level="info">
            <appender-ref ref="Console"/>
            <appender-ref ref="Filelog"/>
            <appender-ref ref="RollingFileInfo"/>
            <appender-ref ref="RollingFileWarn"/>
            <appender-ref ref="RollingFileError"/>
        </root>
    </loggers>

</configuration>

可以选择yml的方式进行配置

配置前需要向pom.xml导入依赖

 <!-- 加上这个才能辨认到log4j2-spring.yml文件 -->
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-yaml</artifactId>
            <version>2.11.0.rc1</version>
        </dependency>
 <!-- log4j2支持异步日志,导入disruptor依赖,不需要支持异步日志,也可以去掉该依赖包 -->
        <dependency>
            <groupId>com.lmax</groupId>
            <artifactId>disruptor</artifactId>
            <version>3.4.2</version>
        </dependency>

application.yml的配置

# log4j2配置
logging:
  config: classpath:config/log4j2-spring.yml
  file:
    path: F:/spring/log

log4j2-spring.yml的配置

# 共有8个级别,按照从低到高为:ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF。

Configuration:
  status: warn
  monitorInterval: 30

  Properties: # 定义全局变量
    Property: # 缺省配置(用于开发环境)。其他环境需要在VM参数中指定,如下:
      #测试:-Dlog.level.console=warn -Dlog.level.xjj=trace
      #生产:-Dlog.level.console=warn -Dlog.level.xjj=info
      - name: log.level.console
        value: info
      - name: log.path
        value: F:/log #日志生成地址
      - name: project.name
        value: opendoc
      - name: log.pattern
        value: "%d{yyyy-MM-dd HH:mm:ss.SSS} -%5p ${PID:-} [%15.15t] %-30.30C{1.} : %m%n"

  Appenders:
    Console:  #输出到控制台
      name: CONSOLE
      target: SYSTEM_OUT
      PatternLayout:
        pattern: ${log.pattern}
    #   启动日志
    RollingFile:
      - name: ROLLING_FILE
        fileName: ${log.path}/${project.name}.log
        filePattern: "${log.path}/historyRunLog/$${date:yyyy-MM}/${project.name}-%d{yyyy-MM-dd}-%i.log.gz"
        PatternLayout:
          pattern: ${log.pattern}
        Filters:
          #        一定要先去除不接受的日志级别,然后获取需要接受的日志级别
          ThresholdFilter:
            - level: error
              onMatch: DENY
              onMismatch: NEUTRAL
            - level: info
              onMatch: ACCEPT
              onMismatch: DENY
        Policies:
          TimeBasedTriggeringPolicy:  # 按天分类
            modulate: true
            interval: 1
        DefaultRolloverStrategy:     # 文件最多100个
          max: 100
      #   平台日志
      - name: PLATFORM_ROLLING_FILE
        ignoreExceptions: false
        fileName: ${log.path}/platform/${project.name}_platform.log
        filePattern: "${log.path}/platform/$${date:yyyy-MM}/${project.name}-%d{yyyy-MM-dd}-%i.log.gz"
        PatternLayout:
          pattern: ${log.pattern}
        Policies:
          TimeBasedTriggeringPolicy:  # 按天分类
            modulate: true
            interval: 1
        DefaultRolloverStrategy:     # 文件最多100个
          max: 100
      #   业务日志
      - name: BUSSINESS_ROLLING_FILE
        ignoreExceptions: false
        fileName: ${log.path}/bussiness/${project.name}_bussiness.log
        filePattern: "${log.path}/bussiness/$${date:yyyy-MM}/${project.name}-%d{yyyy-MM-dd}-%i.log.gz"
        PatternLayout:
          pattern: ${log.pattern}
        Policies:
          TimeBasedTriggeringPolicy:  # 按天分类
            modulate: true
            interval: 1
        DefaultRolloverStrategy:     # 文件最多100个
          max: 100
      #   错误日志
      - name: EXCEPTION_ROLLING_FILE
        ignoreExceptions: false
        fileName: ${log.path}/exception/${project.name}_exception.log
        filePattern: "${log.path}/exception/$${date:yyyy-MM}/${project.name}-%d{yyyy-MM-dd}-%i.log.gz"
        ThresholdFilter:
          level: error
          onMatch: ACCEPT
          onMismatch: DENY
        PatternLayout:
          pattern: ${log.pattern}
        Policies:
          TimeBasedTriggeringPolicy:  # 按天分类
            modulate: true
            interval: 1
        DefaultRolloverStrategy:     # 文件最多100个
          max: 100
      #   DB 日志
      - name: DB_ROLLING_FILE
        ignoreExceptions: false
        fileName: ${log.path}/db/${project.name}_db.log
        filePattern: "${log.path}/db/$${date:yyyy-MM}/${project.name}-%d{yyyy-MM-dd}-%i.log.gz"
        PatternLayout:
          pattern: ${log.pattern}
        Policies:
          TimeBasedTriggeringPolicy:  # 按天分类
            modulate: true
            interval: 1
        DefaultRolloverStrategy:     # 文件最多100个
          max: 100


  Loggers:
    Root:
      level: info
      AppenderRef:
        - ref: CONSOLE
        - ref: ROLLING_FILE
        - ref: EXCEPTION_ROLLING_FILE
    Logger:
      - name: platform
        level: info
        additivity: false
        AppenderRef:
          - ref: CONSOLE
          - ref: PLATFORM_ROLLING_FILE

      - name: bussiness
        level: info
        additivity: false
        AppenderRef:
          - ref: BUSSINESS_ROLLING_FILE

      - name: exception
        level: debug
        additivity: true
        AppenderRef:
          - ref: EXCEPTION_ROLLING_FILE

      - name: db
        level: info
        additivity: false
        AppenderRef:
          - ref: DB_ROLLING_FILE


#    监听具体包下面的日志
#    Logger: # 为com.xjj包配置特殊的Log级别,方便调试
#      - name: com.xjj
#        additivity: false
#        level: ${sys:log.level.xjj}
#        AppenderRef:
#          - ref: CONSOLE
#          - ref: ROLLING_FILE