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

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

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

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

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

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

但它的问题也很明显:

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

例如:

java 复制代码
System.out.println("用户登录成功:" + userId);

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

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

java 复制代码
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

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

java 复制代码
log.trace("进入方法 checkPermission, userId={}", userId);

平时业务开发中很少开。


2. DEBUG

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

java 复制代码
log.debug("查询条件: userId={}, status={}", userId, status);

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


3. INFO

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

java 复制代码
log.info("订单创建成功, orderId={}, userId={}", orderId, userId);

比如:

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

4. WARN

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

java 复制代码
log.warn("库存不足, skuId={}, remain={}", skuId, remain);

比如:

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

5. ERROR

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

java 复制代码
log.error("支付失败, orderId={}", orderId, e);

比如:

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

五、日志的基本使用方式

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

1. 定义日志对象

传统写法:

java 复制代码
public class UserService {
    private static final Logger log = 
            LoggerFactory.getLogger(UserService.class);
}

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

java 复制代码
@Slf4j
public class UserService {
    
}

然后直接使用:

java 复制代码
log.info("日志打印");

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

推荐写法:

java 复制代码
log.info("用户登录成功, userId={}", userId);

不推荐:

java 复制代码
log.info("用户登录成功, userId=" + userId);

原因:

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

多个参数时:

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

3. 异常日志要带堆栈

正确写法:

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

或者带业务上下文:

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

不推荐只打印异常信息:

java 复制代码
log.error("订单支付异常, orderId={}, userId={}", orderId, userId);

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


六、Spring Boot 中的日志使用

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

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

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

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

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

表示:

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

七、日志记录规范

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


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

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

不推荐:

java 复制代码
log.info("进入方法");

log.info("执行结束");

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

推荐:

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

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

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

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

例如:

  • userId
  • orderId
  • requestId
  • traceId
  • taskId

示例:

java 复制代码
log.info("订单支付成功, orderId={}, userId={}", orderId, userId);

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


3. 不要打印敏感信息

日志中严禁直接记录:

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

例如下面这种就不合适:

java 复制代码
log.info("用户登录, username={}, password={}", username, password);

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

java 复制代码
log.info("用户登录, username={}", username);

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


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

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

可以这样理解:

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

例如:

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

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

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

常见原则:

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

例如在 service 里:

java 复制代码
catch (Exception e) { 
    throw new BusinessException("支付失败", e); 
}

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

java 复制代码
log.error("请求处理失败, requestId={}", requestId, e);

这样更清晰。


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

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

例如统一风格:

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

统一风格的好处是:

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

7. 重要操作必须记日志

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

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

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

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

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

例如:

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

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

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

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

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

八、一个简单的日志示例

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

java 复制代码
@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. 只打印"异常了",不打印上下文

例如:

java 复制代码
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. 日志要服务于排查问题,而不是机械地"打印几行字"。
相关推荐
ForgeAI码匠6 小时前
ForgeAdmin|Spring Boot 3 后台框架的自动配置设计:少写配置,多做组合
java·spring boot·后端
tongluowan0076 小时前
Redisson的参数及工作原理
java·redis·lua·分布式锁
小娄~~6 小时前
C语言卷子错题集
c语言·开发语言·数据结构
仙俊红7 小时前
Integer\int对比,equals()\hashcode面试
java·面试·职场和发展
WiChP7 小时前
【V0.1B10】从零开始的2D游戏引擎开发之路
java·数据库·游戏引擎
一然明月7 小时前
qt基本用法
开发语言·qt
云烟成雨TD7 小时前
Spring AI Alibaba 1.x 系列【60】检查点机制原理与全流程剖析
java·人工智能·spring
ForgeAI码匠8 小时前
Maven 多模块项目如何避免越写越乱?Forge Admin 的模块边界实践
java·人工智能·开源·maven
hef2888 小时前
Prism图形设计从入门到精通:外观设置、图层顺序与微调技巧
开发语言
z落落8 小时前
C# 数组 最终完整版全套笔记(一维+多维+交错+引用类型+对象数组)
java·笔记·c#