当前位置: 首页 > article >正文

系统可观测性——Logback日志框架

摘要

Logback是一种Java日志框架,可以提供高度可配置的日志记录功能,包括级别控制和事件过滤等功能。它基于SLF4J(Simple Logging Facade for Java)日志抽象层,可以与多种流行的Java日志框架兼容,如Log4j和Java Util Logging。Logback的核心组件包括Logger、Appender和Layout,它们可以协同工作以产生可定制和易于理解的日志输出。Logback支持多种输出形式,例如控制台输出、文件输出等。它还支持异步日志记录和事件过滤器,可以高效地记录大量日志数据,并快速定位和解决问题。Logback还支持动态配置和可插拔式的架构设计,使得它非常易于使用和扩展。作为一种广泛应用于Java应用程序的日志框架,Logback的功能介绍非常重要。

1. Logback框架

Logback是一个Java日志框架,是log4j项目的继承者,也是log4j创始人设计的另一个开源日志组件,性能比log4j要好。它旨在解决log4j存在的一些问题,并提供了更高效和更灵活的日志框架。

  • 日志级别TRACEDEBUGINFOWARNERROR,用于控制输出日志的粒度。
  • Logger:负责记录日志信息。
  • Appender:定义日志的输出方式(如文件、控制台、数据库等)。
  • Layout/Pattern:控制日志的输出格式。

1.1. Logback特点

  1. 高性能:采用异步日志机制,可以将日志操作和业务逻辑分离,从而大幅度提升系统的性能。
  2. 灵活的配置:支持多种不同的配置方式,包括基于XML、Groovy、JSON等格式的配置文件,同时还支持通过代码进行配置。
  3. 多种日志级别:支持6种不同的日志级别,包括TRACE、DEBUG、INFO、WARN、ERROR和FATAL,用户可以根据需要选择合适的日志级别。
  4. 多种输出方式:支持多种不同的输出方式,包括控制台输出、文件输出、邮件发送等。用户可以根据需要选择合适的输出方式。
  5. 插件丰富:提供许多有用的插件,例如logstash-logback-encoder,可以将日志输出到ELK(Elasticsearch、Logstash、Kibana)平台上进行分析和可视化。
  6. 易于集成:Logback与Spring、Hibernate、JUnit等框架都有很好的集成,可以方便地进行应用程序的日志输出管理。

1.2. Logback核心模块

logback-core:它是 Logback 的核心模块,提供了基本的日志功能。它支持多种输出格式和输出目标,包括控制台输出、文件输出和 Socket 输出。logback-core 可以与其他日志框架集成,例如 log4j 和 JDK Logging。

logback-classic:它是 Logback 的经典模块,是 log4j 的改进版。它提供了更强大的日志功能,并且向下兼容 log4j。它还支持 SLF4J,可以在不修改代码的情况下将应用程序从一个日志框架切换到另一个日志框架。

logback-access:它是 Logback 的访问模块,提供了基于 HTTP 请求的访问日志记录功能。使用 logback-access,可以记录每个请求的详细信息,包括请求方法、URL、响应状态码等。

1.3. Logback配置文件结构

  1. configuration : 配置文件的根元素,它包含了多个子元素:appender、logger 和 root
  2. appender : 定义了输出端,可以是控制台、文件、网络等,每一个appender都需要有一个唯一的名称和一个类定义
  3. logger : 定义了日志记录器,用于记录指定类的日志信息。logger元素需要指定一个名称和一个级别,以及一个可选的appender-ref子元素,用于将日志记录器连接到一个appender。
  4. root : 定义了根日志记录器,它会接收所有未被其他logger接收的日志事件

Logback 的配置文件通常是 XML 格式,文件名为 logback.xml。它的结构如下:

<!--
scan: 当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。
scanPeriod: 设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。
debug: 当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。
-->
<configuration scan="true" scanPeriod="60 seconds" debug="false">
  <!--
  定义变量值的标签,property标签有两个属性,name和value;过property定义的值会被插入到logger上下文中。定义变量后,可以使${name}来使用变量
  -->
  <property name="AppName" value="demo"/>
  <!--
  每个logger都关联到logger上下文,默认上下文名称为“default”。但可以使用contextName标签设置成其他名字,用于区分不同应用程序的记录
  -->
  <contextName>${AppName}</contextName>
  <!--负责写日志的组件-->
  <appender>
    <filter></filter>
  </appender>
  <!--用来设置某一个包或者具体的某一个类的日志打印级别以及指定appender。-->
  <logger>
    <appender-ref ref=""/>
  </logger>
  <!---根logger,也是一种logger,且只有一个level属性-->
  <root>
  </root>
</configuration>

1.4. LogBack日志级别

  1. ALL:最低等级的,用于打开所有日志记录
  2. TRACE:是最详细的日志信息,通常用于诊断问题和追踪代码执行流程。在生产环境中,应该关闭TRACE级别的日志输出,以避免影响系统性能。
  3. DEBUG:是调试信息,用于调试应用程序。在生产环境中,建议将DEBUG级别的日志输出关闭
  4. INFO:是普通的信息记录,通常用于向用户展示可读的操作结果或执行状态。例如,当用户成功登录时,可以记录一条 INFO 级别的日志
  5. WARN:表示警告信息,通常用于记录一些不严重但需要注意的问题。例如,当系统资源紧张时,可以记录一条WARN级别的日志
  6. ERROR:表示错误信息,通常用于记录系统运行时发生的错误。例如,当数据库连接失败时,可以记录一条ERROR级别的日志
  7. FATAL:表示致命错误,通常用于记录系统崩溃或无法恢复的错误。例如,当出现内存泄漏或硬盘损坏时,可以记录一条FATAL级别的日志
  8. OFF:最高等级的,用于关闭所有日志记录。

2. Logback组件配置

2.1. logback-core

logback-core是logback日志系统的核心组件,提供了基础结构,如Loggers、Appenders、Layouts、Filters等等,用于创建、配置和管理logback事件。它是logback的最底层组件,为其他组件提供支持,但使用者通常不需要直接使用它,而是通过更高级别的logback组件使用它的功能。

2.2. logback-classic

logback-classic是logback日志系统的核心实现,基于logback-core,提供了更高级别和更易用的API及强大的日志框架功能,如异步日志、日志分级、Logger上下文等等。它还支持SLF4J,提供符合JDK标准的Java日志框架API接口,同时也具备logback自身的特性。因此,logback-classic是使用logback的主要方式,也是logback日志系统的经典实现。

2.3. logback-access

logback-access是logback日志系统的其中一个模块,专为记录web应用程序的访问日志而设计。它类似于访问日志分析工具,可帮助开发人员深入了解客户端请求、服务器响应和应用程序运行状态,以便进行调整和优化。logback-access能够自动捕捉HTTP请求和响应对象,提供多种配置选项,定制记录内容和格式化方式。此外,它与logback-classic能够很好地配合使用,将应用程序访问日志和其他日志信息记录到同一文件中,方便管理和分析。

3. Logback核心标签

<?xml version="1.0" encoding="UTF-8" ?>

<!-- logback中一共有5种有效级别,分别是TRACE、DEBUG、INFO、WARN、ERROR,优先级依次从低到高 -->
<configuration scan="true" scanPeriod="60 seconds" debug="false">

  <property name="DIR_NAME" value="spring-helloworld"/>

  <!-- 将记录日志打印到控制台 -->
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n</pattern>
    </encoder>
  </appender>

  <!-- RollingFileAppender begin -->
  <appender name="ALL" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!-- 根据时间来制定滚动策略 -->
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <fileNamePattern>${user.dir}/logs/${DIR_NAME}/all.%d{yyyy-MM-dd}.log</fileNamePattern>
      <maxHistory>30</maxHistory>
    </rollingPolicy>

    <!-- 根据文件大小来制定滚动策略 -->
    <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
      <maxFileSize>30MB</maxFileSize>
    </triggeringPolicy>

    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n</pattern>
    </encoder>
  </appender>

  <appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!-- 根据时间来制定滚动策略 -->
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <fileNamePattern>${user.dir}/logs/${DIR_NAME}/error.%d{yyyy-MM-dd}.log</fileNamePattern>
      <maxHistory>30</maxHistory>
    </rollingPolicy>

    <!-- 根据文件大小来制定滚动策略 -->
    <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
      <maxFileSize>10MB</maxFileSize>
    </triggeringPolicy>

    <filter class="ch.qos.logback.classic.filter.LevelFilter">
      <level>ERROR</level>
      <onMatch>ACCEPT</onMatch>
      <onMismatch>DENY</onMismatch>
    </filter>

    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n</pattern>
    </encoder>
  </appender>

  <appender name="WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!-- 根据时间来制定滚动策略 -->
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <fileNamePattern>${user.dir}/logs/${DIR_NAME}/warn.%d{yyyy-MM-dd}.log</fileNamePattern>
      <maxHistory>30</maxHistory>
    </rollingPolicy>

    <!-- 根据文件大小来制定滚动策略 -->
    <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
      <maxFileSize>10MB</maxFileSize>
    </triggeringPolicy>

    <filter class="ch.qos.logback.classic.filter.LevelFilter">
      <level>WARN</level>
      <onMatch>ACCEPT</onMatch>
      <onMismatch>DENY</onMismatch>
    </filter>

    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n</pattern>
    </encoder>
  </appender>

  <appender name="INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!-- 根据时间来制定滚动策略 -->
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <fileNamePattern>${user.dir}/logs/${DIR_NAME}/info.%d{yyyy-MM-dd}.log</fileNamePattern>
      <maxHistory>30</maxHistory>
    </rollingPolicy>

    <!-- 根据文件大小来制定滚动策略 -->
    <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
      <maxFileSize>10MB</maxFileSize>
    </triggeringPolicy>

    <filter class="ch.qos.logback.classic.filter.LevelFilter">
      <level>INFO</level>
      <onMatch>ACCEPT</onMatch>
      <onMismatch>DENY</onMismatch>
    </filter>

    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n</pattern>
    </encoder>
  </appender>

  <appender name="DEBUG" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!-- 根据时间来制定滚动策略 -->
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <fileNamePattern>${user.dir}/logs/${DIR_NAME}/debug.%d{yyyy-MM-dd}.log</fileNamePattern>
      <maxHistory>30</maxHistory>
    </rollingPolicy>

    <!-- 根据文件大小来制定滚动策略 -->
    <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
      <maxFileSize>10MB</maxFileSize>
    </triggeringPolicy>

    <filter class="ch.qos.logback.classic.filter.LevelFilter">
      <level>DEBUG</level>
      <onMatch>ACCEPT</onMatch>
      <onMismatch>DENY</onMismatch>
    </filter>

    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n</pattern>
    </encoder>
  </appender>

  <appender name="TRACE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!-- 根据时间来制定滚动策略 -->
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <fileNamePattern>${user.dir}/logs/${DIR_NAME}/trace.%d{yyyy-MM-dd}.log</fileNamePattern>
      <maxHistory>30</maxHistory>
    </rollingPolicy>

    <!-- 根据文件大小来制定滚动策略 -->
    <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
      <maxFileSize>10MB</maxFileSize>
    </triggeringPolicy>

    <filter class="ch.qos.logback.classic.filter.LevelFilter">
      <level>TRACE</level>
      <onMatch>ACCEPT</onMatch>
      <onMismatch>DENY</onMismatch>
    </filter>

    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n</pattern>
    </encoder>
  </appender>

  <appender name="SPRING" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!-- 根据时间来制定滚动策略 -->
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <fileNamePattern>${user.dir}/logs/${DIR_NAME}/springframework.%d{yyyy-MM-dd}.log
      </fileNamePattern>
      <maxHistory>30</maxHistory>
    </rollingPolicy>

    <!-- 根据文件大小来制定滚动策略 -->
    <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
      <maxFileSize>10MB</maxFileSize>
    </triggeringPolicy>

    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n</pattern>
    </encoder>
  </appender>
  <!-- RollingFileAppender end -->

  <!-- logger begin -->
  <!-- 本项目的日志记录,分级打印 -->
  <logger name="org.zp.notes.spring" level="TRACE" additivity="false">
    <appender-ref ref="STDOUT"/>
    <appender-ref ref="ERROR"/>
    <appender-ref ref="WARN"/>
    <appender-ref ref="INFO"/>
    <appender-ref ref="DEBUG"/>
    <appender-ref ref="TRACE"/>
  </logger>

  <!-- SPRING框架日志 -->
  <logger name="org.springframework" level="WARN" additivity="false">
    <appender-ref ref="SPRING"/>
  </logger>

  <root level="TRACE">
    <appender-ref ref="ALL"/>
  </root>
  <!-- logger end -->

</configuration>

3.1. <configuration>

  • 作用:<configuration>是logback配置文件的根元素。
  • 要点:它有<appender>、<logger>:、<root>三个子元素。

3.2. <Appender>

  • 作用:将记录日志的任务委托给名为appender的组件。
  • 要点:可以配置零个或多个。它有<file>、<filter>:、<layout.>、<encoder>四个子元素。
  • 属性:name:设置appender名称。class:设置具体的实例化类。

3.2.1. <File>

  • 作用:设置日志文件路径。

3.2.2. <Filter>

  • 作用:设置过滤器。
  • 要点:可以配置零个或多个。

3.2.3. <Layout>

  • 作用:设置 appender。
  • 要点:可以配置零个或一个。
  • 属性:class:设置具体的实例化类。

3.2.4. <encoder>

  • 作用:设置编码。
  • 要点:可以配置零个或多个。
  • 属性:class:设置具体的实例化类。

3.3. <Logger>

在Logback-classic模块中,日志记录器负责记录日志,并将其关联到应用程序的上下文中,以便存储日志对象。此外,日志记录器还可用于定义日志的级别和类型。

  • 作用:设置 logger。
  • 要点:可以配置零个或多个。
  • 属性:
    • name
    • level:设置日志级别。不区分大小写。可选值:TRACE、DEBUG、INFO、WARN、ERROR、ALL、OFF。
    • additivity:可选值:true 或 false。

3.3.1. <appender-ref>

  • 作用:appender 引用。
  • 要点:可以配置零个或多个。

3.4. <root>

  • 作用:设置根 logger。
  • 要点
    • 只能配置一个。
    • 除了 level,不支持任何属性。level 属性和 <logger> 中的相同。
    • 有一个子元素 <appener-ref>,与 <logger> 中的相同。

4. Logback多场景示例

Logback 支持根据配置生成多个不同的日志文件。通过灵活的配置,可以将不同的日志输出到不同的文件,以便更好地管理和分析日志。以下是一些常见的场景和示例:

4.1. 按业务模块或功能划分日志文件

可以为不同的业务模块或功能设置单独的日志文件。例如,一个应用中有订单模块和用户模块,分别输出到 order.loguser.log 文件。

<configuration>
    <property name="LOG_PATH" value="/var/log/myapp"/>

    <!-- 订单模块日志配置 -->
    <appender name="ORDER-APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/order.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/order.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxFileSize>10MB</maxFileSize>
            <maxHistory>7</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger{35} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- 用户模块日志配置 -->
    <appender name="USER-APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/user.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/user.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxFileSize>10MB</maxFileSize>
            <maxHistory>7</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger{35} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- 订单模块日志 -->
    <logger name="com.myapp.order" level="INFO" additivity="false">
        <appender-ref ref="ORDER-APPENDER"/>
    </logger>

    <!-- 用户模块日志 -->
    <logger name="com.myapp.user" level="INFO" additivity="false">
        <appender-ref ref="USER-APPENDER"/>
    </logger>

    <!-- 默认日志配置 -->
    <root level="ERROR">
        <appender-ref ref="STDOUT"/>
    </root>
</configuration>

说明

  1. 两个模块分别对应两个日志文件
  • order.log 用于订单模块 (com.myapp.order)。
  • user.log 用于用户模块 (com.myapp.user)。
  1. 使用 additivity="false" 确保模块日志不会重复输出到 root 中。
  2. 默认日志级别是 ERROR,其他模块的日志不会干扰指定的模块。

4.1.1. Spring和Logback的依赖

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

4.1.2. 订单模块

package com.myapp.order;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

@Service
public class OrderService {
    private static final Logger logger = LoggerFactory.getLogger(OrderService.class);

    public void processOrder(String orderId) {
        logger.info("Processing order: {}", orderId);
        // 其他业务逻辑
        logger.debug("Order details: {}", orderId);
    }
}

4.1.3. 用户模块

package com.myapp.user;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    private static final Logger logger = LoggerFactory.getLogger(UserService.class);

    public void createUser(String username) {
        logger.info("Creating user: {}", username);
        // 其他业务逻辑
        logger.debug("User details: {}", username);
    }
}

4.1.4. 测试模块日志划分

启动项目后,分别调用订单模块和用户模块的方法,检查日志文件。

  • 调用订单模块 processOrder 方法:
NFO  2024-12-13 10:00:01.123 [main] com.myapp.order.OrderService - Processing order: 12345
DEBUG 2024-12-13 10:00:01.456 [main] com.myapp.order.OrderService - Order details: 12345

输出到文件:order.log

  • 调用用户模块 createUser 方法:
INFO  2024-12-13 10:05:01.123 [main] com.myapp.user.UserService - Creating user: john_doe
DEBUG 2024-12-13 10:05:01.456 [main] com.myapp.user.UserService - User details: john_doe

输出到文件:user.log

4.1.5. 总结

在日志目录 /var/log/myapp/ 下,可以看到以下两个文件:

  • order.log:保存订单模块的日志。
  • user.log:保存用户模块的日志。

每个日志文件都只包含对应模块的日志,清晰且易于管理。

4.2. 按日志级别划分日志文件

在 Spring 项目中,按 日志级别(如 INFOERRORDEBUG)划分日志文件,可以结合 Logback 的日志级别过滤器实现。这种配置非常适合在不同文件中保存不同重要性的信息,便于调试和监控。

<configuration>
    <!-- 定义日志路径 -->
    <property name="LOG_PATH" value="/var/log/myapp"/>

    <!-- 控制台输出 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- INFO 日志文件 -->
    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/info.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/info.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxFileSize>10MB</maxFileSize>
            <maxHistory>7</maxHistory>
        </rollingPolicy>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- ERROR 日志文件 -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/error.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/error.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxFileSize>10MB</maxFileSize>
            <maxHistory>7</maxHistory>
        </rollingPolicy>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- WARN 日志文件 -->
    <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/warn.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/warn.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxFileSize>10MB</maxFileSize>
            <maxHistory>7</maxHistory>
        </rollingPolicy>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>WARN</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- 根日志设置(写入控制台和文件) -->
    <root level="DEBUG">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="INFO_FILE"/>
        <appender-ref ref="ERROR_FILE"/>
        <appender-ref ref="WARN_FILE"/>
    </root>
</configuration>

多日志文件

  • 每个日志级别使用一个 RollingFileAppender,将对应日志输出到指定文件中。
  • 例如:
    • info.log:存储 INFO 级别日志。
    • error.log:存储 ERROR 级别日志。
    • warn.log:存储 WARN 级别日志。

日志过滤器

  • <filter> 元素用于根据日志级别筛选日志信息。
    • LevelFilter
      • onMatch="ACCEPT":接收符合指定级别的日志。
      • onMismatch="DENY":拒绝其他级别的日志。

日志文件滚动

  • 每天生成新日志文件(info.%d{yyyy-MM-dd}.%i.log)。
  • 每个日志文件最大 10MB,保留最近 7 天日志。

日志合并

  • 控制台输出所有日志。
  • 不同日志文件中只记录对应级别的日志。

4.2.1. Spring 配置文件

在 Spring 配置文件中,可以通过 application.ymlapplication.properties 设置日志路径,进一步增强灵活性。

logging:
  file:
    path: /var/log/myapp

4.2.2. Spring 代码示例

package com.myapp;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

@Service
public class LogService {
    private static final Logger logger = LoggerFactory.getLogger(LogService.class);

    public void execute() {
        logger.debug("This is a DEBUG log message.");
        logger.info("This is an INFO log message.");
        logger.warn("This is a WARN log message.");
        logger.error("This is an ERROR log message.");
    }
}

运行服务后日志输出文件

  • info.log 中仅包含 INFO 级别日志。
  • error.log 中仅包含 ERROR 级别日志。
  • warn.log 中仅包含 WARN 级别日志。
  • 控制台同时输出所有日志。

文件路径

  • 日志文件存储在 /var/log/myapp 下。
  • 每天生成一个新的日志文件,旧日志根据滚动策略自动清理

4.3. 按时间或大小滚动生成日志文件

每天生成一个新的日志文件,例如 app-2024-12-13.log

<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
  <fileNamePattern>${LOG_PATH}/app-%d{yyyy-MM-dd}.log</fileNamePattern>
  <maxHistory>30</maxHistory>
</rollingPolicy>

文件大小超过设定值后生成新的日志文件,例如 app.log.1app.log.2

<rollingPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
  <maxFileSize>10MB</maxFileSize>
</rollingPolicy>

结合时间和大小滚动:既可以按时间切分,也可以控制单个日志文件的大小。

<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
  <fileNamePattern>${LOG_PATH}/app.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
  <maxFileSize>10MB</maxFileSize>
  <maxHistory>7</maxHistory>
</rollingPolicy>

4.4. 按运行环境生成不同日志文件

在 Spring 项目中,按运行环境(如 devtestprod 等)生成不同的日志文件,可以结合 Logback 的配置Spring Profiles 来实现。以下是完整的实现步骤和代码示例。

4.4.1. Spring-logback配置

<configuration>
    <!-- 读取 Spring Profiles 的 active 环境 -->
    <springProperty scope="context" name="ENV" source="spring.profiles.active" defaultValue="dev"/>
    <property name="LOG_PATH" value="/var/log/myapp/${ENV}"/>

    <!-- 控制台输出 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- 文件输出 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/application.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/application.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxFileSize>10MB</maxFileSize>
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- 根据环境动态设置日志级别 -->
    <logger name="com.myapp" level="INFO" additivity="false">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE"/>
    </logger>

    <!-- 全局默认日志 -->
    <root level="WARN">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE"/>
    </root>
</configuration>

说明

  • 动态环境变量
    • <springProperty> 读取 spring.profiles.active 配置,用于根据环境动态设置日志目录。
    • LOG_PATH 的值会根据运行环境切换,例如:
      • dev 环境:日志路径为 /var/log/myapp/dev/
      • prod 环境:日志路径为 /var/log/myapp/prod/
  • 日志文件滚动
    • 每天生成一个新的日志文件(application.%d{yyyy-MM-dd})。
    • 文件大小超过 10MB 时会自动滚动并追加编号(如 application.2024-12-13.1.log)。
  • 控制台与文件分离
    • 控制台输出日志,便于开发调试。
    • 文件日志按环境划分,便于生产环境管理。

4.4.2. Spring 配置文件

spring:
  profiles:
    active: dev # 当前激活环境,可改为 dev、test、prod

application-dev.yml

logging:
  level:
    com.myapp: DEBUG

application-prod.yml

logging:
  level:
    com.myapp: INFO

4.4.3. Spring 代码示例

package com.myapp;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

@Service
public class LogService {
    private static final Logger logger = LoggerFactory.getLogger(LogService.class);

    public void execute() {
        logger.info("This is an INFO log message.");
        logger.debug("This is a DEBUG log message.");
        logger.error("This is an ERROR log message.");
    }
}
package com.myapp;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(Application.class, args);
        LogService logService = context.getBean(LogService.class);

        // 测试日志输出
        logService.execute();
    }
}

验证结果

运行环境:dev

  1. 激活 dev 环境:在 application.yml 中配置 spring.profiles.active: dev
  2. 日志文件路径:/var/log/myapp/dev/application.log
  3. 输出日志包含 DEBUGINFO 级别。

运行环境:prod

  1. 激活 prod 环境:在 application.yml 中配置 spring.profiles.active: prod
  2. 日志文件路径:/var/log/myapp/prod/application.log
  3. 输出日志仅包含 INFO 及以上级别。

4.5. 按业务模块下在日志级别划分日志文件

在实际项目中,既要按 业务模块(例如 orderpaymentuser),又要在每个模块内按 日志级别(如 INFOERROR)分别记录日志,可以通过在 Logback 配置中组合 loggerappender 来实现。

4.5.1. 目标

  • 按业务模块(例如 orderpayment)生成独立的日志文件。
  • 在每个模块下,按日志级别(如 INFOERROR)进一步划分日志文件。

4.5.2. logback-spring.xml配置

<configuration>
    <!-- 定义日志路径 -->
    <property name="LOG_PATH" value="/var/log/myapp"/>

    <!-- Console 输出 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- Order 模块 INFO 日志 -->
    <appender name="ORDER_INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/order/info.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/order/info.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxFileSize>10MB</maxFileSize>
            <maxHistory>7</maxHistory>
        </rollingPolicy>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- Order 模块 ERROR 日志 -->
    <appender name="ORDER_ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/order/error.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/order/error.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxFileSize>10MB</maxFileSize>
            <maxHistory>7</maxHistory>
        </rollingPolicy>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- Payment 模块 INFO 日志 -->
    <appender name="PAYMENT_INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/payment/info.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/payment/info.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxFileSize>10MB</maxFileSize>
            <maxHistory>7</maxHistory>
        </rollingPolicy>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- Payment 模块 ERROR 日志 -->
    <appender name="PAYMENT_ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/payment/error.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/payment/error.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxFileSize>10MB</maxFileSize>
            <maxHistory>7</maxHistory>
        </rollingPolicy>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- Logger 配置 -->
    <logger name="com.myapp.order" level="INFO" additivity="false">
        <appender-ref ref="ORDER_INFO_FILE"/>
        <appender-ref ref="ORDER_ERROR_FILE"/>
    </logger>

    <logger name="com.myapp.payment" level="INFO" additivity="false">
        <appender-ref ref="PAYMENT_INFO_FILE"/>
        <appender-ref ref="PAYMENT_ERROR_FILE"/>
    </logger>

    <!-- Root 配置 -->
    <root level="WARN">
        <appender-ref ref="CONSOLE"/>
    </root>
</configuration>

配置解析

  1. 模块划分
    • com.myapp.ordercom.myapp.payment 分别对应订单模块和支付模块。
    • 每个模块拥有自己的 INFOERROR 日志文件。
  1. 日志路径
    • order/info.log:存储订单模块的 INFO 日志。
    • order/error.log:存储订单模块的 ERROR 日志。
    • payment/info.log:存储支付模块的 INFO 日志。
    • payment/error.log:存储支付模块的 ERROR 日志。
  1. 日志过滤器
    • 使用 <filter> 对日志级别进行过滤。
    • 每个日志文件仅存储对应的日志级别。
  1. 日志文件滚动
    • 每天生成新日志文件,日志文件最大 10MB,最多保留 7 天。

4.5.3. Spring 示例代码

package com.myapp.order;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

@Service
public class OrderService {
    private static final Logger logger = LoggerFactory.getLogger(OrderService.class);

    public void processOrder() {
        logger.info("Processing order...");
        logger.error("Order processing failed due to insufficient inventory.");
    }
}
package com.myapp.payment;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

@Service
public class PaymentService {
    private static final Logger logger = LoggerFactory.getLogger(PaymentService.class);

    public void processPayment() {
        logger.info("Processing payment...");
        logger.error("Payment processing failed due to insufficient balance.");
    }
}
package com.myapp;

import com.myapp.order.OrderService;
import com.myapp.payment.PaymentService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(Application.class, args);

        OrderService orderService = context.getBean(OrderService.class);
        PaymentService paymentService = context.getBean(PaymentService.class);

        orderService.processOrder();
        paymentService.processPayment();
    }
}

运行结果

  1. 生成的日志文件
  • /var/log/myapp/order/info.log:包含订单模块的 INFO 日志。
  • /var/log/myapp/order/error.log:包含订单模块的 ERROR 日志。
  • /var/log/myapp/payment/info.log:包含支付模块的 INFO 日志。
  • /var/log/myapp/payment/error.log:包含支付模块的 ERROR 日志。
  1. 日志示例
  • info.log
2024-12-13 14:45:22.123 INFO  [main] com.myapp.order.OrderService - Processing order...
2024-12-13 14:45:23.456 INFO  [main] com.myapp.payment.PaymentService - Processing payment...
  • error.log
2024-12-13 14:45:22.123 ERROR [main] com.myapp.order.OrderService - Order processing failed due to insufficient inventory.
2024-12-13 14:45:23.456 ERROR [main] com.myapp.payment.PaymentService - Payment processing failed due to insufficient balance.

5. Logback实战示例

5.1. Logback依赖

 <!--slf4j日志门面-->
  <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.7.26</version>
  </dependency>
  <!--logback日志实现-->
  <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.2.3</version>
  </dependency>

5.2. 配置Logback文件

创建一个 Logback 的配置文件(通常是 logback.xml),并为其定义 appender、logger 和 root 等元素。也可以不用配置。

<?xml version="1.0" encoding="UTF-8"?>

<configuration scan="true" scanPeriod="60 seconds" debug="false">

    <property name="LOG_CONTEXT_NAME" value="web-app"/>
    <!--定义日志文件的存储地址 勿在LogBack的配置中使用相对路径-->
    <property name="LOG_HOME" value="logs/${LOG_CONTEXT_NAME}"/>
    <!-- 定义日志上下文的名称 -->
    <contextName>${LOG_CONTEXT_NAME}</contextName>

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

    <!--info日志统一输出-->
    <appender name="file.info" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <Prudent>true</Prudent>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件输出的文件名,按小时生成-->
            <FileNamePattern>${LOG_HOME}/%d{yyyy-MM-dd}/info/info.%d{yyyy-MM-dd-HH}.%i.log</FileNamePattern>
            <!--日志文件输出的文件名,按天生成-->
            <!--<FileNamePattern>${LOG_HOME}/%d{yyyy-MM-dd}/error/error.%d{yyyy-MM-dd}.%i.log</FileNamePattern>-->
            <!--日志文件保留天数-->
            <MaxHistory>30</MaxHistory>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <!-- 除按日志记录之外,还配置了日志文件不能超过10M(默认),若超过10M,日志文件会以索引0开始 -->
                <maxFileSize>10MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %method 方法名  %L 行数 %msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56}.%method:%L - %msg%n</pattern>
            <charset>utf-8</charset>
        </encoder>
        <!-- 此日志文件只记录info级别的 -->
        <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.info appender-->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!--warn日志统一输出-->
    <appender name="file.warn" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--省略,参考file.info appender-->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>WARN</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!--  日志输出级别 -->
    <root level="debug">
        <appender-ref ref="console"/>
        <appender-ref ref="file.error"/>
        <appender-ref ref="file.info"/>
        <appender-ref ref="file.warn"/>
    </root>

    <!-- 某类/包下的所有日志使用file.info appender输出-->
    <logger name="cn.ybzy.demo.controller.TestController" additivity="false">
        <appender-ref ref="file.info"/>
    </logger>

	<logger name="org.hibernate.SQL">
        <level value="DEBUG"/>
    </logger>

</configuration>

5.3. Logback记录日志

public class Logback {

    public static final Logger LOGGER = LoggerFactory.getLogger(Logback.class);

    @Test
    public void test() {
        LOGGER.error("error");
        LOGGER.warn("wring");
        LOGGER.info("info");
        LOGGER.debug("debug");
        LOGGER.trace("trace");
    }
}
18:05:38.202 [main] ERROR cn.ybzy.Logback - error
18:05:38.206 [main] WARN cn.ybzy.Logback - wring
18:05:38.207 [main] INFO cn.ybzy.Logback - info
18:05:38.207 [main] DEBUG cn.ybzy.Logback - debug

5.4. Logback与Springboot集成

springboot集成了logback日志系统,默认读取名叫logback-spring.xml的配置文件,如果想自定义配置文件的名称,需要在spring boot配置文件中配置指定。

logging:
	config: classpath:logback.xml
public class MyTest {
    
    private static final Logger logger = LoggerFactory.getLogger(MyTest.class);

    public static void main(String[] args) {
        logger.info("info.....");
        logger.warn("warn" + ".....");
        logger.error("error,msg={}", "error....");
    }
}

5.5. Logback与lomBok集成

logback和lombok集成,使用@Slf4j注解标注类,直接使用log对象,需添加lombok依赖。

<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <optional>true</optional>
</dependency>
@Slf4j
public class MyTest {

    public static void main(String[] args) {
        log.info("info.....");
        log.warn("warn" + ".....");
        log.error("error,msg={}", "error....");
    }
}

博文参考

  • davidsuperm.github.io/java/日志框架选型与使用.md at master · DavidSuperM/davidsuperm.github.io · GitHub
  • Logback:Spring Boot内置的日志处理框架 | 二哥的Java进阶之路
  • https://juejin.cn/post/7078552015755804686
  • https://juejin.cn/post/7248890696819720253

http://www.kler.cn/a/441780.html

相关文章:

  • 网络安全(渗透)
  • js截取video视频某一帧为图片
  • 《Effective Java》学习笔记——第2部分 对象通用方法最佳实践
  • 金融场景 PB 级大规模日志平台:中信银行信用卡中心从 Elasticsearch 到 Apache Doris 的先进实践
  • 淘宝关键词页面爬取绘图进行数据分析
  • STM32之CubeMX图形化工具开发介绍(十七)
  • Spring Boot 3.x:自动配置类加载机制的变化
  • 如何在Linux上搭建DHCP服务
  • 解决Docker拉取镜像报错问题的详细步骤
  • win10配置子系统Ubuntu子系统(无需通过Windows应用市场)实际操作记录
  • Python中构建全局字典的详细指南
  • docker快速实现ELK的安装和使用
  • Centos gcc 12.3 安装
  • 使用CNN模型训练图片识别(键盘,椅子,眼镜,水杯,鼠标)
  • docker 拉取镜像 | 创建容器 | 容器运行
  • k8s,理解容器中namespace和cgroups的原理
  • Android Scratch分区
  • 力扣hot100——哈希
  • ESP8266 Ubuntu 安装
  • vue2 项目webpack 4升5
  • docker xxxx is using its referenced image ea06665f255d
  • 使用echarts实现3d柱状图+折线图
  • Vue3 重置ref或者reactive属性值
  • JAVA企业级项目的日志记录技术
  • 《变形金刚:赛博坦的陨落》游戏启动难题:‘buddha.dll’缺失的七大修复策略
  • 搭建C#开发环境