一、异常体系结构

1.1 顶层父类 Throwable

  • Throwable:所有异常和错误的超类。
  • 两个直接子类
    • Error:系统级错误,如OutOfMemoryErrorStackOverflowError,程序无法处理,应终止运行。
    • Exception:程序级异常,是我们处理的核心。

1.2 Exception 的分类

  1. RuntimeException (运行时异常)

    • 特点:非受检异常,编译器不强制要求捕获。
    • 常见类型
      • NullPointerException (空指针)
      • ArrayIndexOutOfBoundsException (数组越界)
      • IllegalArgumentException (非法参数)
      • ArithmeticException (算术错误,如除零)
    • 处理原则:通常由程序逻辑错误导致,应通过优化代码避免,而非捕获。
  2. 非 RuntimeException (编译时异常)

    • 特点:受检异常,编译器强制要求捕获或声明抛出。
    • 常见类型
      • IOException (文件 / 网络读写错误)
      • SQLException (数据库操作错误)
      • ClassNotFoundException (类未找到)
    • 处理原则:外部环境或资源导致的错误,必须处理。

二、异常处理原则

2.1 核心设计思想

  • 异常与非异常的哲学:异常是 “意外”,不是 “预期”。不要用异常来控制程序的正常流程(如替代 if-else)。
  • 职责明确
    • try:包裹可能抛出异常的代码。
    • catch:捕获并处理异常(记录日志、转换异常类型、提供默认值等)。
    • finally总是执行,用于释放资源(如关闭流、数据库连接)。

2.2 try-catch-finally 执行顺序

  1. 执行try块中的代码。
  2. 如果发生异常
    • 跳出try块,寻找第一个匹配的catch块。
    • 执行catch块中的异常处理逻辑。
  3. 如果没有发生异常
    • 跳过所有catch块。
  4. 无论是否发生异常
    • 执行finally块中的代码(用于资源清理)。
  5. 继续执行后续代码。

2.3 常见踩坑提醒

  1. 空指针陷阱:在finally块中避免使用可能为null的对象,否则可能掩盖原始异常。
  2. 异常吞噬catch块中不要只写e.printStackTrace()而不做任何处理,这会丢失异常信息。
  3. 资源泄漏:忘记在finally中关闭资源(如 IO 流、数据库连接)。

三、Logback 核心工作原理详解

3.1 三大核心组件

  1. Logger (日志记录器)

    • 负责日志的产生,是日志记录的入口。
    • 按包名或类名进行层级划分(如com.example.service.UserService)。
    • 日志级别(从低到高):TRACE < DEBUG < INFO < WARN < ERROR
  2. Appender (附加器)

    • 负责日志的输出目的地。
    • 一个Logger可以关联多个Appender(如同时输出到控制台和文件)。
    • 常见实现:
      • ConsoleAppender:输出到控制台。
      • FileAppender:输出到文件。
      • RollingFileAppender:滚动输出到文件(按大小或时间分割)。
  3. Layout (布局)

    • 负责日志的格式化输出。
    • 定义日志的显示格式(如包含时间、级别、线程名、类名等)。
    • 常用实现:PatternLayout(通过自定义模式字符串来格式化)。

3.2 日志记录流程

  1. 客户端代码调用logger.info("Hello World")
  2. Logback 框架根据Logger的名称和配置,找到对应的Appender列表。
  3. 对于每个Appender,Logback 使用其关联的Layout将日志事件(LoggingEvent)格式化为字符串。
  4. 格式化后的字符串被Appender输出到指定的目的地(控制台、文件等)。

四、Logback 配置详解(XML 方式)

4.1 配置文件结构

Logback 的核心配置文件通常名为logback.xml,放置在类路径(src/main/resources)下。一个典型的配置文件包含以下几个部分:

xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds">

    <!-- 1. 定义变量 -->
    <property name="LOG_PATH" value="logs" />
    <property name="LOG_FILE" value="myapp" />

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

    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/${LOG_FILE}.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- 滚动文件的名称模式 -->
            <FileNamePattern>${LOG_PATH}/${LOG_FILE}.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
            <!-- 单个文件最大大小 -->
            <MaxFileSize>100MB</MaxFileSize>
            <!-- 保留时间 -->
            <MaxHistory>30</MaxHistory>
            <!-- 总大小限制 -->
            <TotalSizeCap>3GB</TotalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- 3. 定义Logger -->
    <logger name="com.example" level="DEBUG" additivity="false">
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="FILE" />
    </logger>

    <!-- 4. 根Logger -->
    <root level="INFO">
        <appender-ref ref="CONSOLE" />
    </root>

</configuration>

4.2 关键配置说明

  • <property>:定义可复用的变量,如日志路径。
  • <appender>:定义一个输出目的地。ConsoleAppender用于控制台,RollingFileAppender用于文件滚动。
  • <encoder>:定义日志的输出格式。%d是日期,%thread是线程名,%-5level是日志级别(左对齐 5 个字符),%logger{36}是日志记录器名称(最多 36 个字符),%msg是日志消息,%n是换行符。
  • <logger>:为特定包或类定义日志行为。additivity="false"表示不继承父 Logger 的 Appender。
  • <root>:根 Logger,所有 Logger 的默认父类。

五、日志使用规范

5.1 日志级别使用指南

  • ERROR:系统核心功能失败,如数据库连接中断、关键业务流程失败。必须记录并立即关注。
  • WARN:潜在问题或非预期但不影响系统运行的情况,如配置项缺失但有默认值、API 调用超时但已重试。
  • INFO:重要的业务流程完成或系统状态变更,如用户登录、订单创建、服务启动 / 停止。
  • DEBUG:开发和调试阶段的详细信息,如方法的入参和出参、循环的中间状态等。生产环境应关闭或调至INFO级别
  • TRACE:最详细的日志,用于追踪代码执行路径。通常仅在开发环境使用。

5.2 日志内容规范

  1. 日志要清晰、有意义:避免使用 “进入方法”、“退出方法” 等无信息量的日志。
  2. 包含上下文信息:在记录业务相关日志时,应包含关键的业务 ID(如订单 ID、用户 ID),方便问题定位。
  3. 异常日志要完整:捕获异常时,应使用logger.error("错误描述", e),而不是logger.error("错误描述: " + e.getMessage()),前者会打印完整的堆栈信息。

六、全文总结

  1. 异常处理:理解异常体系结构,区分 RuntimeException 与非 RuntimeException,合理使用 try-catch-finally,避免空指针和资源泄漏。
  2. 日志框架:掌握 Logback 的三大核心组件(Logger、Appender、Layout)及其工作原理。
  3. 配置与使用:能够独立完成 Logback 的 XML 配置,根据不同环境(开发、测试、生产)合理调整日志级别和输出格式。
  4. 最佳实践:遵循日志级别使用指南,确保日志内容清晰、有上下文,异常日志记录完整堆栈信息。

更多推荐