Skip to content

大纲

  • 为什么要记录日志
  • logback 简介
  • logback 使用
  • 日志记录“最佳”实践

为什么要记录日志

在开发中,会经常使用System.out.println方法调用来帮助观察程序运行的操作过程。system.out.printIn打印日志的方式太过原始,影响性能,而且只能输出在控制台,而在生产环境下我们往往需要将日志存储下来。

所以我们一般选择日志框架来做这些事情,使用日志框架有以下几点好处:

  1. 可以设置输出样式,避免自己每次都写"ERROR: " + var;
  2. 可以全局设置输出级别,禁止某些级别输出。例如,只输出错误日志;
  3. 可以被重定向到文件,这样可以在程序运行结束后查看日志;
  4. 可以按包名控制日志级别,比如只输出某些包打的日志;

java 生态下的日志框架有很多,首先 java 标准库也提供了日志包java.util.logging,但 java标准库内置的Logging 使用并不广泛。java 生态下比较流行的日志框架有log4j、log4j 的升级版Log4j 2,还有 spring boot 内置的logback。那我们当然选择最流行的 logback 啦~

log4jlogback这些日志框架都采用门面模式。

门面模式是什么?其实就是面向接口编程,我们在应用程序中编写日志代码使用“门面框架”提供的 api,而底层“门面框架”会去自动在 classPath 中寻找“日志实现”框架,这样一来,如果我们

比如commons logging是一个门面框架,提供了一套日志接口,而真正的日志实现可以使用 Log4j。

slf4j也是一个门面框架,提供了一套日志接口,而真正的日志实现可以使用 LogBack。

Logback 使用

Logback 的使用很简单,一般需要先获取到 Logger 日志对象,然后调用 http://logger.xxx(比如logger.info)就能输出日志了。

最传统的方法就是通过 LoggerFactory 手动获取 Logger,示例代码如下:

java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyService {
   private static final Logger logger = LoggerFactory.getLogger(MyService.class);

   public void doSomething() {
       logger.info("执行了一些操作");
  }
}

LoggerFactory 是一个工厂类,调用getLogger函数可以返回一个 logger 实例,getLogger函数参数可以是一个 class 或是字符串,这样 logger 输出的时候,就可以输出传入的类参数了。

Logback(官网)是一款为Java应用程序设计的日志框架,旨在提供高性能、灵活性和可扩展性。它是log4j项目的继任者,并被广泛用于Java应用程序的日志记录。

logback 的作者也是 log4j 的作者

Logback 源码包分为三个主要的模块:logback-core、logback-classic、和logback-access

  • logback-core提供了基本的日志功能。
  • logback-classic建立在logback-core之上,兼容SLF4J和log4j API,提供了一套强大的日志框架。
  • logback-access允许通过servlet容器的访问日志功能来记录HTTP请求。()

Spring Boot默认集成了Logback,并用INFO级别输出到控制台。 由于Spring Boot通常使用嵌入式Servlet容器,并且这些容器已经具备了记录访问日志的功能,因此在默认情况下不需要引入logback-access。

Logback 具有许多优点,其中一些包括:

  • 性能高效

Logback被设计为高性能的日志框架,具有较低的运行时开销。

异步日志记录和可配置的缓冲机制有助于提高性能。

  • 灵活的配置

Logback的配置文件采用XML格式(通常命名为logback.xml),允许用户以声明式的方式配置日志输出。

支持通过Groovy脚本进行配置,提供更灵活的选项。

  • 丰富的Appender

提供多种Appender用于将日志输出到不同的目的地,如控制台、文件、数据库等。

支持异步Appender以提高性能。

  • 简单而强大的 API

Logback 的 API 设计简单易用,同时具备强大的功能。它继承了 Log4j 的 API,但在设计上进行了改进和优化,使得开发者可以更容易地集成和使用。

  • 广泛的社区支持

作为一个成熟而受欢迎的日志框架,Logback 拥有广泛的社区支持和活跃的开发者社群。这意味着开发者可以在社区中获取丰富的资源、文档和支持。

Logback被划分为三个模块:logback-corelogback-classiclogback-access。其中,logback-core是基础模块,为其他两个模块提供支持;logback-classic是log4j的一个显著改进版本,原生实现了SLF4J API,便于在不同日志系统间切换;logback-access则与Servlet容器集成,提供HTTP访问日志功能。

Logback的架构基于三个核心类:LoggerAppenderLayout。这些组件协同工作,使开发者能够根据消息类型和级别记录日志,并在运行时控制日志的格式化和输出位置。

  • Logger:属于logback-classic模块,是命名实体,遵循层次化命名规则。每个Logger都附加到一个LoggerContext,后者负责创建和组织Logger的树形层次结构。
  • Appender 和 Layout:属于logback-core模块,Appender负责将日志输出到不同目的地,如控制台、文件、数据库等,而Layout则负责格式化日志请求。

````

Logback允许将日志请求输出到多个目的地,这些目的地被称为Appenders。可以将多个Appender附加到一个Logger上,并且Appender在Logger层次结构中是累加的。通过设置Logger的additivity标志为false,可以覆盖这一默认行为。

用户可以通过为Appender关联Layout来定制输出格式。PatternLayout是标准Logback发行版的一部分,允许用户根据类似于C语言printf函数的转换模式来指定输出格式。

Logback-classic中的Logger实现了SLF4J的Logger接口,支持参数化的打印方法,以提高性能并减少对代码可读性的影响。例如,使用logger.debug("The entry is {}.", entry);可以在不构建参数的情况下记录日志,仅在需要记录时才格式化消息。


logback 基于三个主要的类构建:loggerappenderlayout

通过 logback.xml 配置,其实也是在配置这三个类

  • logger context
  • effective level 继承
  • appender: 在 logback 中,appender 决定日志的输出位置 ,appender 可以存在于 控制台、文件、数据库等等。
  • Layout决定了日志信息的输出格式
  • 打印 logback 是否正确配置日志
  • 配置文件 logback.xml 中的 root 节点其实就代表了项目的根 logger 实例(考虑 logger 继承关系)

日志级别

日志级别的作用是标识日志的重要程度,日志级别粒度从细到组分为:

  • TRACE:最细粒度的信息,通常只在开发过程中使用,用于跟踪程序的执行路径。
  • DEBUG:调试信息,记录程序运行时的内部状态和变量值。
  • INFO:一般信息,记录系统的关键运行状态和业务流程。
  • WARN:警告信息,表示可能存在潜在问题,但系统仍可继续运行。
  • ERROR:错误信息,表示出现了影响系统功能的问题,需要及时处理。
  • FATAL:致命错误,表示系统可能无法继续运行,需要立即关注。

其中,用的最多的当属 DEBUG、INFO、WARN 和 ERROR 了。

建议在开发环境使用低级别日志(比如 DEBUG),以获取详细的信息;生产环境使用高级别日志(比如 INFO 或 WARN),减少日志量,降低性能开销的同时,防止重要信息被无用日志淹没。

在src/main/resources目录下创建名为logback-spring.xml或者logback.xml的Logback配置文件。Spring Boot默认会加载类路径下的logback-spring.xml文件。如果该文件不存在,则会尝试加载logback.xml。如果我们使用的是别的配置名称,则需要在springboot 的application.yml配置文件中经行配置:

yaml
# 日志配置
logging:
  config: classpath:mylogback.xml

xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <!-- 日志存放路径 -->
  <property name="log.path" value="/data/xjdoc/logs" />
  <!-- 日志输出格式 -->
  <property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />

  <!-- 控制台输出 -->
  <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>${log.pattern}</pattern>
    </encoder>
  </appender>

  <!-- 系统日志输出 -->
  <appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>${log.path}/sys-info.log</file>
    <!-- 循环政策:基于时间创建日志文件 -->
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <!-- 日志文件名格式 -->
      <fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
      <!-- 日志最大的历史 60天 -->
      <maxHistory>60</maxHistory>
    </rollingPolicy>
    <encoder>
      <pattern>${log.pattern}</pattern>
    </encoder>
    <filter class="ch.qos.logback.classic.filter.LevelFilter">
      <!-- 过滤的级别 -->
      <level>INFO</level>
      <!-- 匹配时的操作:接收(记录) -->
      <onMatch>ACCEPT</onMatch>
      <!-- 不匹配时的操作:拒绝(不记录) -->
      <onMismatch>DENY</onMismatch>
    </filter>
  </appender>

  <appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>${log.path}/sys-error.log</file>
    <!-- 循环政策:基于时间创建日志文件 -->
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <!-- 日志文件名格式 -->
      <fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>
      <!-- 日志最大的历史 60天 -->
      <maxHistory>60</maxHistory>
    </rollingPolicy>
    <encoder>
      <pattern>${log.pattern}</pattern>
    </encoder>
    <filter class="ch.qos.logback.classic.filter.LevelFilter">
      <!-- 过滤的级别 -->
      <level>ERROR</level>
      <!-- 匹配时的操作:接收(记录) -->
      <onMatch>ACCEPT</onMatch>
      <!-- 不匹配时的操作:拒绝(不记录) -->
      <onMismatch>DENY</onMismatch>
    </filter>
  </appender>

  <!-- 系统模块日志级别控制  -->
  <logger name="cn.xj" level="info" />
  <!-- Spring日志级别控制  -->
  <logger name="org.springframework" level="warn" />

  <!--系统操作日志-->
  <root level="info">
    <appender-ref ref="console" />
    <appender-ref ref="file_info" />
    <appender-ref ref="file_error" />
  </root>

</configuration>
xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <!-- 日志存放路径 -->
  <property name="log.path" value="/data/xjdoc/logs" />
  <!-- 日志输出格式 -->
  <property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />

  <!-- 控制台输出 -->
  <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>${log.pattern}</pattern>
    </encoder>
  </appender>

  <!-- 系统日志输出 -->
  <appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>${log.path}/sys-info.log</file>
    <!-- 循环政策:基于时间创建日志文件 -->
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <!-- 日志文件名格式 -->
      <fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
      <!-- 日志最大的历史 60天 -->
      <maxHistory>60</maxHistory>
    </rollingPolicy>
    <encoder>
      <pattern>${log.pattern}</pattern>
    </encoder>
    <filter class="ch.qos.logback.classic.filter.LevelFilter">
      <!-- 过滤的级别 -->
      <level>INFO</level>
      <!-- 匹配时的操作:接收(记录) -->
      <onMatch>ACCEPT</onMatch>
      <!-- 不匹配时的操作:拒绝(不记录) -->
      <onMismatch>DENY</onMismatch>
    </filter>
  </appender>

  <appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>${log.path}/sys-error.log</file>
    <!-- 循环政策:基于时间创建日志文件 -->
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <!-- 日志文件名格式 -->
      <fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>
      <!-- 日志最大的历史 60天 -->
      <maxHistory>60</maxHistory>
    </rollingPolicy>
    <encoder>
      <pattern>${log.pattern}</pattern>
    </encoder>
    <filter class="ch.qos.logback.classic.filter.LevelFilter">
      <!-- 过滤的级别 -->
      <level>ERROR</level>
      <!-- 匹配时的操作:接收(记录) -->
      <onMatch>ACCEPT</onMatch>
      <!-- 不匹配时的操作:拒绝(不记录) -->
      <onMismatch>DENY</onMismatch>
    </filter>
  </appender>

  <appender name="async_file_info" class="ch.qos.logback.classic.AsyncAppender">
    <!--  当队列的剩余容量小于这个阈值并且当前日志level为 TRACE, DEBUG or INFO ,则丢弃这些日志。  -->
    <discardingThreshold>0</discardingThreshold>
    <!--  更改默认的队列的深度,该值会影响性能.默认值为256  -->
    <queueSize>1024</queueSize>
    <!--  新增这行为了打印栈堆信息  -->
    <includeCallerData>true</includeCallerData>
    <!--  添加附加的appender,最多只能添加一个  -->
    <appender-ref ref="file_info"/>
  </appender>
  <appender name="async_file_error" class="ch.qos.logback.classic.AsyncAppender">
    <!--  当队列的剩余容量小于这个阈值并且当前日志level为 TRACE, DEBUG or INFO ,则丢弃这些日志。  -->
    <discardingThreshold>0</discardingThreshold>
    <!--  更改默认的队列的深度,该值会影响性能.默认值为256  -->
    <queueSize>1024</queueSize>
    <!--  新增这行为了打印栈堆信息  -->
    <includeCallerData>true</includeCallerData>
    <!--  添加附加的appender,最多只能添加一个  -->
    <appender-ref ref="file_error"/>
  </appender>
    <appender name="async_file_debug" class="ch.qos.logback.classic.AsyncAppender">
        <!--  当队列的剩余容量小于这个阈值并且当前日志level为 TRACE, DEBUG or INFO ,则丢弃这些日志。  -->
        <discardingThreshold>0</discardingThreshold>
        <!--  更改默认的队列的深度,该值会影响性能.默认值为256  -->
        <queueSize>1024</queueSize>
        <!--  新增这行为了打印栈堆信息  -->
        <includeCallerData>true</includeCallerData>
        <!--  添加附加的appender,最多只能添加一个  -->
        <appender-ref ref="file_debug"/>
    </appender>

    <!-- 系统模块日志级别控制  -->
    <logger name="cn.xj" level="info" />
    <!-- Spring日志级别控制  -->
    <logger name="org.springframework" level="warn" />

 
 <!--系统操作日志-->
    <root level="info">
        <appender-ref ref="console" />
        <appender-ref ref="async_file_info" />
        <appender-ref ref="async_file_error" />
    </root>

</configuration>

xml
<!-- 日志存放路径 -->
<property name="log.path" value="/data/xjdoc/logs" />
<!-- 日志输出格式 -->
<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />

<font style="color:rgb(25, 27, 31);background-color:rgb(248, 248, 250);"><property></font>

  • <font style="color:rgb(25, 27, 31);background-color:rgb(248, 248, 250);">%d{HH:mm:ss.SSS}</font>
  • <font style="color:rgb(25, 27, 31);background-color:rgb(248, 248, 250);">[%thread]</font>
  • <font style="color:rgb(25, 27, 31);background-color:rgb(248, 248, 250);">%-5level</font>
  • <font style="color:rgb(25, 27, 31);background-color:rgb(248, 248, 250);">%logger{20}</font>
  • <font style="color:rgb(25, 27, 31);background-color:rgb(248, 248, 250);">[%method,%line]</font>
  • <font style="color:rgb(25, 27, 31);background-color:rgb(248, 248, 250);">%msg</font>
  • <font style="color:rgb(25, 27, 31);background-color:rgb(248, 248, 250);">%n</font>

xml
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
  <file>${log.path}/sys-info.log</file>
  <!-- 循环政策:基于时间创建日志文件 -->
  <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    <!-- 日志文件名格式 -->
    <fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
    <!-- 日志最大的历史 60天 -->
    <maxHistory>60</maxHistory>
  </rollingPolicy>
  <encoder>
    <pattern>${log.pattern}</pattern>
  </encoder>
  <filter class="ch.qos.logback.classic.filter.LevelFilter">
    <!-- 过滤的级别 -->
    <level>INFO</level>
    <!-- 匹配时的操作:接收(记录) -->
    <onMatch>ACCEPT</onMatch>
    <!-- 不匹配时的操作:拒绝(不记录) -->
    <onMismatch>DENY</onMismatch>
  </filter>
</appender>

appender 元素

[](https://zhida.zhihu.com/search?content_id=237716753&content_type=Article&match_order=1&q=%E6%96%87%E4%BB%B6%E8%B7%AF%E5%BE%84&zhida_source=entity)

**<font style="color:rgb(25, 27, 31);background-color:rgb(248, 248, 250);">rollingPolicy</font>**

**<font style="color:rgb(25, 27, 31);background-color:rgb(248, 248, 250);">encoder</font>**

**<font style="color:rgb(25, 27, 31);background-color:rgb(248, 248, 250);">filter</font>**

xml
<!-- 系统模块日志级别控制  -->
<logger name="cn.xj" level="info" />
<!-- Spring日志级别控制  -->
<logger name="org.springframework" level="warn" />

level 属性定义了日志级别,表示记录的日志消息级别。可选值包括 trace、debug、info、warn、error。

  • TRACE(追踪)

TRACE 是最低级别的日志,用于记录程序的详细执行信息。

这个级别通常用于调试,输出对程序执行流程的跟踪信息。

TRACE 级别的日志量较大,一般情况下不会在生产环境中启用。

  • DEBUG(调试)

DEBUG 级别用于输出调试信息,有助于开发者定位和解决问题。

DEBUG 日志通常包含详细的变量信息、方法调用堆栈等。

在开发和测试阶段中,可以启用 DEBUG 日志以获取更多的信息。

  • INFO(信息)

INFO 级别用于记录一般性的信息,表示程序执行的正常流程。

INFO 日志用于显示重要的运行时信息,通常在生产环境中启用。

这是默认的日志级别,如果没有明确指定级别,则使用 INFO。

  • WARN(警告)

WARN 级别用于记录一些可能需要关注的问题,但不会导致程序失败。

WARN 日志表明程序可能遇到了某些问题或潜在的错误,但仍然可以继续执行。

这是一个提醒性的级别。

  • ERROR(错误)

ERROR 级别用于记录程序的错误和异常情况。

ERROR 日志表示程序遇到了严重的问题,可能导致程序崩溃或无法正常运行。

ERROR 级别的日志通常需要开发者及时关注和处理。

在 Logback 配置文件中,<root> 元素用于配置根 Logger,它是整个日志系统的根节点。根 Logger 拥有最高级别,通常用于设置全局的日志级别和全局的 Appender(附加器)。

xml
<root level="info">
  <appender-ref ref="console" />
  <appender-ref ref="async_file_info" />
  <appender-ref ref="async_file_error" />
</root>

level 属性

level 属性定义了根 Logger 的默认日志级别,表示整个日志系统的最低输出级别。例如,上例中设置为 INFO,表示只输出 INFO 级别及以上的日志。

appender-ref 元素

appender-ref 元素用于引用一个或多个 Appender,将其关联到根 Logger,即设置根 Logger 的输出目的地。可以有多个appender-ref元素,表示将日志输出到多个目的地。