CodeGym /课程 /JAVA 25 SELF /日志记录基础:java.util.logging、Log4j、SLF4J

日志记录基础:java.util.logging、Log4j、SLF4J

JAVA 25 SELF
第 63 级 , 课程 0
可用

1. 为什么需要日志记录

日志记录 ≠ 控制台输出

当你刚开始编程时,可能觉得到处插入 System.out.println("I am here!") 就足以定位问题。这在程序很小、并且只在你本机运行时确实可行。但设想一个大型项目、一台 24/7 运行的服务器,或者一个有成千上万用户的应用——你不可能坐在每个用户身后盯着他们的控制台吧?

日志记录 不只是输出消息。它是对应用运行情况进行系统化记录:错误、警告、业务事件、技术细节等。日志可以保存到文件、数据库,也可以通过网络发送——从而支持在程序运行后或运行过程中对其进行分析。

日志的用途

  • 问题诊断:一旦出现异常,日志就是你的首要帮手。通过日志可以定位错误出现的位置与原因。
  • 审计与安全:日志会记录用户的重要操作,有助于事件调查。
  • 调试:有些 Bug 只在特定条件下出现,没有日志很难捕捉。
  • 监控:通过日志可以跟踪应用状态、性能与“健康度”。

生活中的例子

如果飞机没有“黑匣子”(飞行日志),事故调查几乎不可能。在编程中,日志就是你的黑匣子。

2. 日志记录的基本级别

日志记录通常使用消息的级别(levels)。就像红绿灯:红色——危险,黄色——注意,绿色——一切正常。

主要级别(从“最响”到“最静”):

级别 描述
ERROR
严重错误,应用无法继续工作
WARN
警告:出现了异常情况,但程序仍在运行
INFO
关于正常事件的信息消息
DEBUG
用于调试的详细信息(通常只有开发者可见)
TRACE
最详细的信息——用于深入诊断

示例:

  • 用户因密码错误无法登录——这是 WARN
  • 数据库挂了——这是 ERROR
  • 应用启动——这是 INFO
  • 在循环中输出变量值——这是 DEBUGTRACE

如何选择级别

不要把所有内容都写成 ERROR——否则真正的错误会被淹没。要有意识地使用级别:只有真正致命的故障才应当作为错误。

3. Java 中的标准日志记录(java.util.logging

Java 自带了内置的日志系统——包 java.util.logging(简称 JUL)。这是“原生”的工具,即使你没有引入任何库,它也始终可用。

主要类:

  • Logger——用于记录日志的核心类。
  • Level——日志级别的枚举(SEVEREWARNINGINFOCONFIGFINEFINERFINEST)。
  • Handler——处理器,指定日志写到哪里(文件、控制台等)。
  • Formatter——负责消息格式。

JUL 的使用示例

import java.util.logging.Logger;
import java.util.logging.Level;

public class LoggingExample {
    // 按类名获取 logger
    private static final Logger logger = Logger.getLogger(LoggingExample.class.getName());

    public static void main(String[] args) {
        logger.info("应用已启动");
        logger.warning("这是一个警告!");
        logger.severe("这是一个错误!");

        int x = 42;
        logger.fine("调试变量 x=" + x); // 默认不输出
    }
}

重要提示:默认情况下,JUL 只输出 INFO 及以上级别。要查看 fine 和其他更详细的消息,需要配置日志级别。

通过文件进行配置

可以通过 logging.properties 文件配置 JUL(通常位于 JRE 目录)。可以指定:

  • logger 的最小级别
  • 日志写到哪里(文件、控制台)
  • 消息格式

logging.properties 中的示例行:

.level=INFO

4. 外部日志库

Log4j(Apache)

Log4j 是 Java 最知名的日志库之一。它灵活、强大,支持多种格式、异步写入、日志轮转等功能。

Log4j 2 的最简配置示例(XML):

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss} [%t] %-5level %logger{36} - %msg%n"/>
        </Console>
    </Appenders>
    <Loggers>
        <Root level="info">
            <AppenderRef ref="Console"/>
        </Root>
    </Loggers>
</Configuration>

在代码中使用 Log4j 2 的示例:

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class Log4jExample {
    private static final Logger logger = LogManager.getLogger(Log4jExample.class);

    public static void main(String[] args) {
        logger.info("Hello from Log4j!");
        logger.error("通过 Log4j 记录的错误");
    }
}

要让它生效,需要在项目中添加 Log4j 的依赖(例如通过 Maven 或 Gradle)。

SLF4J:日志门面

SLF4J(Simple Logging Facade for Java)不是一个独立的日志系统,而是你的代码与具体实现(Log4jLogbackJUL 等)之间的“抽象层”。

门面的价值在于:你只需以一种方式编写代码,就能在需要时切换不同的日志实现,即使不同的库采用不同的 logger,也无需重写代码。

SLF4J 的示例:

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

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

    public static void main(String[] args) {
        logger.info("你好,通过 SLF4J 进行日志记录!");
        logger.warn("注意:可能会出问题");
        logger.error("通过 SLF4J 记录的错误");
    }
}

它如何工作?

  • 你通过 SLF4J 编写代码。
  • 在 classpath 中添加所需的实现(例如 LogbackLog4j)。
  • SLF4J 会将调用“转发”到所选实现。

SLF4J 与其他日志库的交互:SLF4J 可以工作在 JULLog4jLogback 等之上,使得迁移无需重写代码。

5. 实战:对比 System.out.println 与日志记录

示例 1:System.out.println

public class PrintlnExample {
    public static void main(String[] args) {
        System.out.println("应用已启动");
        System.out.println("发生错误:出了点问题");
    }
}

问题所在:

  • 没有消息级别(错误、信息、警告——全都一样)。
  • 没有时间、类名、线程信息。
  • 所有消息混在一起。
  • 无法灵活配置输出(例如只写错误)。

示例 2:通过 SLF4J + Logback 进行日志记录

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

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

    public static void main(String[] args) {
        logger.info("应用已启动");
        logger.error("发生错误:出了点问题");
    }
}

优势:

  • 每条消息都带有时间、级别、类名、线程。
  • 可以按级别过滤消息。
  • 可以将日志写入文件、通过网络发送、格式化、归档。
  • 日志记录是线程安全的(对多线程应用尤为重要)。

差异演示

System.out.println
日志记录(SLF4J/Logback)
应用已启动 12:34:56 [main] INFO LoggingVsPrintln - 应用已启动
发生错误:出了点问题 12:34:56 [main] ERROR LoggingVsPrintln - 发生错误:出了点问题

6. 如何在自己的项目中添加日志记录

对于标准 logger(JUL)

JDK 中已自带,可直接使用:

import java.util.logging.Logger;

public class MyApp {
    private static final Logger logger = Logger.getLogger(MyApp.class.getName());

    public static void main(String[] args) {
        logger.info("这是一条信息日志");
    }
}

对于 Log4j 或 SLF4J

  1. 添加依赖(例如通过 Maven)。

    对于 Log4j 2:

    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.20.0</version>
    </dependency>
    

    对于 SLF4J + Logback:

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>2.0.7</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.4.7</version>
    </dependency>
    
  2. 创建配置文件(例如 logback.xml(用于 Logback)或 log4j2.xml(用于 Log4j))。
  3. 在代码中使用 logger(参见上面的示例)。

7. 迷你应用:为命令行计算器添加日志

假设我们有一个简单的应用——命令行计算器。为它添加日志记录。

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

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

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        logger.info("计算器已启动");

        try {
            System.out.print("请输入第一个数字:");
            int a = Integer.parseInt(scanner.nextLine());
            logger.debug("输入的第一个数字: {}", a);

            System.out.print("请输入第二个数字:");
            int b = Integer.parseInt(scanner.nextLine());
            logger.debug("输入的第二个数字: {}", b);

            int sum = a + b;
            logger.info("数字之和: {}", sum);
            System.out.println("结果: " + sum);
        } catch (NumberFormatException ex) {
            logger.error("数字输入错误", ex);
            System.out.println("错误:请输入正确的数字!");
        } catch (Exception ex) {
            logger.error("未知错误", ex);
            System.out.println("发生了错误!");
        }
        logger.info("计算器已结束");
    }
}

这里发生了什么:

  • 所有关键事件都被记录到日志。
  • 错误以 ERROR 级别记录,并包含完整的堆栈跟踪。
  • 程序运行结束后,所有信息都可供分析。

8. 使用日志记录时的常见错误

错误 №1:只用 System.out.println 进行调试和诊断。
这在起步阶段很方便,但完全不适用于真实的应用。你无法管理消息级别、看不到时间,也无法在程序运行在服务器上时分析日志。

错误 №2:在 ERROR 级别记录过量信息。
如果把所有内容都当作错误来记录,你将难以分辨什么是真正重要的,什么只是调试信息。

错误 №3:记录敏感数据(密码、令牌、卡号)。
日志必须是安全的!不要把不应被他人看到的信息写进日志。

错误 №4:缺少日志配置。
如果不配置级别、格式、存储位置——你可能会意外“淹没”在海量无用信息中,或者根本看不到错误。

错误 №5:在大型项目中不使用日志门面SLF4J
当项目增长并需要从一个库迁移到另一个库时——没有门面会非常痛苦。SLF4J 允许在不重写代码的情况下轻松更换日志“引擎”。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION