Java常见日志框架和使用、日志记录规范

 更新时间:2026年05月08日 15:22:06   作者:两年半的个人练习生^_^  
文章主要介绍了Java项目中使用日志的重要性,推荐使用标准的日志框架,文章详细解释了日志门面、实现和历史框架的区别,并提供了日志级别的定义及使用方法,强调了记录日志时需要注意的事项,文章总结了实际开发中常见的误区,并给出了合理使用日志的建议

日志常用于输出到控制台或指定文件,帮助我们记录用户行为以及排查bug

最早接触的就是System.out.println将内容输出到控制台,也导致经常在后续开发中可能顺手就写了sout来记录。

这种方式虽然简单,但在真实项目中远远不够。正式开发里,我们更推荐使用标准的日志框架,比如 SLF4J + Logback 或 SLF4J + Log4j2。

一、为什么不能只用 System.out.println

System.out.println() 的优点只有一个:简单,写了就能看到输出。

但它的问题也很明显:

  • 没有日志级别,无法区分普通信息、警告、错误
  • 无法统一格式,排查问题时信息杂乱
  • 不方便输出到文件、日志平台、监控系统
  • 不能按环境灵活控制输出
  • 高并发、生产环境下几乎不适合作为正式日志手段

例如:

System.out.println("用户登录成功:" + userId);

这句代码只是把一行文字打印到控制台,既没有时间、线程、类名,也无法统一收集。

而日志框架的写法通常是:

log.info("用户登录成功, userId={}", userId);

它的价值在于:

  • 有明确的日志级别
  • 支持格式化输出
  • 能输出到控制台、文件或日志系统
  • 可以按配置决定哪些日志要打印、哪些不打印
  • 更适合线上运维和问题排查

所以,sout 更像临时调试工具,log.info() 才是项目开发中的标准做法。

二、Java 常见日志框架

Java 日志体系里,经常会看到好几个名字,初学者容易混淆。可以把它们分成三类来看。

1. 日志门面

日志门面本身不负责真正输出日志,它只负责“定义统一接口”,让业务代码不直接依赖某个具体实现。

常见门面:

  • SLF4J
  • JCL(Jakarta Commons Logging,较老)

目前主流项目中最常见的是 SLF4J。

它的好处是:代码里统一写 log.info()、log.error(),底层到底用 Logback 还是 Log4j2,可以后续替换,不影响业务代码。

2. 日志实现

日志实现才是真正把日志打印出来、写到文件里的组件。

常见实现:

  • Logback
  • Log4j2
  • java.util.logging

其中:

  • Logback:Spring Boot 默认常用实现,配置方便,生态成熟
  • Log4j2:性能好、功能强,企业项目也很常见
  • java.util.logging:JDK 自带,但实际项目中使用相对少

3. 历史框架

  • Log4j 1.x:老版本,现在一般不推荐新项目使用
  • System.out.println():只能算打印,不算真正意义上的日志框架

三、主流推荐方案

在现在的 Java 项目中,推荐组合一般是:

  • SLF4J + Logback
  • SLF4J + Log4j2

如果是 Spring Boot 项目,默认通常就是 SLF4J + Logback,上手成本最低,适合大多数场景。

可以记一个简单结论:

开发中优先面向 SLF4J 编码,具体实现选 Logback 或 Log4j2。

四、日志级别怎么理解

日志框架通常提供多个级别,用于表示信息的重要程度。常见级别从低到高如下:

  • TRACE
  • DEBUG
  • INFO
  • WARN
  • ERROR

下面逐个理解。

1. TRACE

最细粒度的跟踪信息,一般用于非常详细的程序执行过程。

log.trace("进入方法 checkPermission, userId={}", userId);

平时业务开发中很少开。

2. DEBUG

用于开发调试,记录一些关键变量、执行路径、参数信息。

log.debug("查询条件: userId={}, status={}", userId, status);

开发环境常用,生产环境通常关闭或谨慎开启。

3. INFO

记录正常业务流程中的关键信息,是项目里最常用的级别之一。

log.info("订单创建成功, orderId={}, userId={}", orderId, userId);

比如:

  • 服务启动成功
  • 用户登录成功
  • 订单创建成功
  • 定时任务开始/结束

4. WARN

表示程序出现异常情况或风险,但还没有导致功能彻底失败。

log.warn("库存不足, skuId={}, remain={}", skuId, remain);

比如:

  • 参数不规范但系统做了兼容
  • 某个远程调用超时后触发重试
  • 检测到配置缺失但系统使用默认值继续运行

5. ERROR

表示发生了明确错误,通常需要重点关注。

log.error("支付失败, orderId={}", orderId, e);

比如:

  • 数据库操作失败
  • 接口调用异常
  • 程序捕获到未预期异常

五、日志的基本使用方式

下面用常见的 SLF4J 风格说明日志怎么写。

1. 定义日志对象

传统写法:

public class UserService {
    private static final Logger log = 
            LoggerFactory.getLogger(UserService.class);
}

如果项目使用 Lombok,也可以简化为:

@Slf4j
public class UserService {
}

然后直接使用:

log.info("日志打印");

2. 参数化输出,不要字符串拼接

推荐写法:

log.info("用户登录成功, userId={}", userId);

不推荐:

log.info("用户登录成功, userId=" + userId);

原因:

  • 代码更整洁
  • 日志框架会更高效地处理参数
  • 可读性更好

多个参数时:

log.info("订单创建成功, orderId={}, userId={}, amount={}", orderId, userId, amount);

3. 异常日志要带堆栈

正确写法:

@Slf4j
public class test {
    public static void main(String[] args) {
        log.info("日志打印");
        try {
            int x = 1 / 0;
        } catch (Exception e) {
            log.error("系统计算异常", e);
        }
    }
}

或者带业务上下文:

 log.error("订单支付异常, orderId={}, userId={}", orderId, userId, e);

不推荐只打印异常信息:

log.error("订单支付异常, orderId={}, userId={}", orderId, userId);

因为这样会丢失完整堆栈,不利于定位问题。

六、Spring Boot 中的日志使用

Spring Boot 默认对日志支持比较友好,一般引入 Web 依赖后就已经带了日志能力。

业务代码里通常直接这样写:

@Slf4j
@Service
public class OrderService {
    public void createOrder(Long userId) {
        log.info("开始创建订单, userId={}", userId);
        log.info("订单创建成功, userId={}", userId);
    }
}

配置日志级别也比较方便,例如在 application.yml 中:

logging:
  level:
    root: info        # 全局默认日志级别
    com.example.demo: debug  # 你项目包的日志级别

表示:

  • 全局日志级别是 info
  • com.example.demo 包下允许输出 debug 级别日志

七、日志记录规范

下面是比较实用的一套日志规范。

1. 日志要有价值,避免“流水账”

不要什么都记,也不要一句话完全没有信息量。

不推荐:

log.info("进入方法");
log.info("执行结束");

这种日志看起来很多,但对排查问题帮助有限。

推荐:

log.info("开始处理退款申请, refundId={}, orderId={}", refundId, orderId);
log.info("退款处理完成, refundId={}, status={}", refundId, status);

要尽量带上业务上下文,让日志能回答:

  • 是谁触发的
  • 处理了什么对象
  • 结果是什么
  • 出错时影响了哪个业务数据

2. 关键业务日志必须带唯一标识

例如:

  • userId
  • orderId
  • requestId
  • traceId
  • taskId

示例:

log.info("订单支付成功, orderId={}, userId={}", orderId, userId);

这样才能通过一个 ID 把整条调用链串起来。

3. 不要打印敏感信息

日志中严禁直接记录:

  • 密码
  • 身份证号完整信息
  • 银行卡完整号码
  • 手机号完整号码
  • token、密钥、验证码
  • 用户隐私数据

例如下面这种就不合适:

log.info("用户登录, username={}, password={}", username, password);

正确做法是脱敏或不打印:

log.info("用户登录, username={}", username);

如果必须记录手机号,也要脱敏处理。

4. INFO 记录业务节点,DEBUG 记录调试细节

很多项目的日志问题不是“太少”,而是“太乱”。最常见错误是把大量细节都打成 info,导致生产日志噪声很大。

可以这样理解:

  • INFO:记录关键业务事件
  • DEBUG:记录开发调试信息
  • ERROR:记录失败和异常

例如:

log.info("开始支付流程, orderId={}", orderId);
log.debug("支付请求参数: {}", request);
log.info("支付完成, orderId={}, response={}, userId={}, orderId, response, userId);

5. 不要重复记录同一个异常

一个异常如果层层捕获、层层 log.error(),最后日志里就会出现多份重复堆栈,反而干扰排查。

常见原则:

  • 能处理就处理,不一定要打 error
  • 不能处理再往上抛
  • 最终由边界层统一记录一次完整异常

例如在 service 里:

catch (Exception e) { 
    throw new BusinessException("支付失败", e); 
}

在 controller 或全局异常处理中统一打印:

log.error("请求处理失败, requestId={}", requestId, e);

这样更清晰。

6. 日志内容要统一、可检索

建议统一格式,避免同一种业务场景每个人写法都不一样。

例如统一风格:

log.info("订单创建成功, orderId={}, userId={}, amount={}", orderId, userId, amount);

统一风格的好处是:

  • 搜索方便
  • 排查高效
  • 团队协作成本低

7. 重要操作必须记日志

下面这些场景通常建议记录日志:

  • 用户登录、登出
  • 创建订单、支付、退款
  • 定时任务执行
  • 第三方接口调用结果
  • 权限校验失败
  • 数据导入导出
  • 配置加载、服务启动与停止
  • 关键异常和兜底逻辑

8. 高频循环里谨慎打日志

如果在循环中大量打印日志,可能导致:

  • 日志量暴增
  • 磁盘占用过快
  • IO 压力变大
  • 排查时淹没关键信息

例如:

for (User user : userList) {
   log.info("处理用户: {}", user.getId());
}

如果数据量很大,这种写法就要非常谨慎。

更好的方式可能是汇总记录:

log.info("开始批量处理用户, size={}", userList.size());

处理结束后再输出结果摘要。

八、一个简单的日志示例

下面给一个比较符合规范的 service 示例:

@Slf4j
@Service
public class OrderService {
    public void pay(Long orderId, Long userId) {
        log.info("开始支付订单, orderId={}, userId={}", orderId, userId);
        try {
            // 1. 参数校验 
            log.debug("校验订单状态, orderId={}", orderId);
            // 2. 执行支付逻辑 
            log.debug("调用支付网关, orderId={}", orderId);
            // 3. 支付成功 
            log.info("订单支付成功, orderId={}, userId={}", orderId, userId);
        } catch (Exception e) {
            log.error("订单支付失败, orderId={}, userId={}", orderId, userId, e);
            throw e;
        }
    }
}

这个例子体现了几个点:

  • info 记录关键业务节点
  • debug 记录内部处理细节
  • error 记录失败并保留异常堆栈
  • 日志中带了 orderId 和 userId 这样的关键上下文

九、实际开发中的常见误区

1. 把 System.out.println() 当正式日志用

这是最常见的初级问题。临时调试可以,但不能作为项目正式方案。

2. 所有日志都打 info

这会让生产日志变得非常吵,真正重要的信息反而不明显。

3. 只打印“异常了”,不打印上下文

例如:

log.error("支付失败");

这太模糊了。至少要带上订单号、用户 ID 等关键信息。

4. 只打印 e.getMessage(),不打印堆栈

这样往往无法定位代码具体在哪一行出错。

5. 打印敏感数据

这是日志安全的大忌。

6. 每层都打印同一个异常

会导致重复日志,影响排查效率。

十、总结

Java 日志框架的核心价值,不只是“输出内容”,而是帮助我们在开发、测试、上线、运维整个生命周期里快速理解系统发生了什么。

从实践角度来说,可以记住这几个结论:

  1. 正式项目不要依赖 System.out.println(),而要使用日志框架。
  2. 推荐面向 SLF4J 编码,底层常用 Logback 或 Log4j2。
  3. 合理区分 debug、info、warn、error。
  4. 日志要带关键业务上下文,比如 userId、orderId、traceId。
  5. 异常日志要保留完整堆栈,不要只打印一句错误信息。
  6. 严禁记录密码、密钥、身份证号等敏感数据。
  7. 日志要服务于排查问题,而不是机械地“打印几行字”。

到此这篇关于Java日志框架和使用、日志记录规范的文章就介绍到这了,更多相关java日志框架使用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • spring boot 项目中使用thymeleaf模板的案例分析

    spring boot 项目中使用thymeleaf模板的案例分析

    这篇文章主要介绍了spring boot 项目中使用thymeleaf模板的案例分析,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-09-09
  • Java实现导出ZIP压缩包的方法

    Java实现导出ZIP压缩包的方法

    这篇文章主要介绍了Java实现导出ZIP压缩包的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-11-11
  • Springboot使用Junit测试没有插入数据的原因

    Springboot使用Junit测试没有插入数据的原因

    这篇文章主要介绍了Springboot使用Junit测试没有插入数据的原因,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-04-04
  • SpringBoot实现Logback输出日志到Kafka方式

    SpringBoot实现Logback输出日志到Kafka方式

    本文介绍了如何在SpringBoot应用中通过自定义Appender实现Logback输出日志到Kafka,包括配置maven依赖、Kafka工具类和logback.xml配置
    2025-02-02
  • 使用java对一副扑克牌建模

    使用java对一副扑克牌建模

    这篇文章主要为大家详细介绍了如何使用java对一副扑克牌建模,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-04-04
  • java编程中字节流转换成字符流的实现方法

    java编程中字节流转换成字符流的实现方法

    下面小编就为大家带来一篇java编程中字节流转换成字符流的实现方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-01-01
  • Maven多模块之父子关系的创建

    Maven多模块之父子关系的创建

    这篇文章主要介绍了Maven多模块之父子关系的创建,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-03-03
  • 详解Java修饰符

    详解Java修饰符

    Java语言提供了很多修饰符,主要分为以下两类:访问修饰符;非访问修饰符。修饰符用来定义类、方法或者变量,通常放在语句的最前端。我们通过下面的例子来说明,下面就跟小编一起来看下吧
    2016-12-12
  • RabbitMQ Stream插件使用案例代码

    RabbitMQ Stream插件使用案例代码

    这篇文章主要介绍了RabbitMQ Stream插件使用案例代码,2.4版为RabbitMQ流插件引入了对RabbitMQStream插件Java客户端的初始支持,需要的朋友可以参考下
    2024-04-04
  • Java file类中renameTo方法操作实例

    Java file类中renameTo方法操作实例

    renameTo()方法是File类的一部分,renameTo()函数用于将文件的抽象路径名重命名为给定的路径名​​,下面这篇文章主要给大家介绍了关于Java file类中renameTo方法操作的相关资料,需要的朋友可以参考下
    2022-11-11

最新评论