找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2616

积分

0

好友

364

主题
发表于 8 小时前 | 查看: 0| 回复: 0

日志在系统开发和运维过程中扮演着极其重要的角色。无论是定位线上问题、分析异常行为,还是复盘一次生产事故,日志往往是还原现场、定位问题的首要依据。很多时候,系统“看不见”的地方,最终只能通过日志来一探究竟。

门面模式:SLF4J

日志框架能够帮助我们快速定位错误并进行有效的故障排查。你可能听说过一些与日志框架相关的名词,比如 slf4jlog4jlogbackJDK Logging ,但它们之间到底是什么关系可能会让人感到困惑。

SLF4J 是一个日志框架的简单门面,它提供了一个统一的日志接口,使得开发者可以在应用中使用统一的 API。SLF4J 本身并不提供日志的实现,而是通过与其他日志框架(如 Log4j、Logback 等)结合使用,来实现日志记录的功能。

SLF4J日志门面架构示意图

图中,Logback、Log4j 都是具体的日志框架,每种框架都有自己单独的 API。如果直接使用这些 API,就会大大增加应用程序代码与特定日志框架的耦合性。

为了解决这个问题,就是在日志框架和应用程序之间架设一个沟通的桥梁——门面。对于应用程序来说,无论底层的日志框架如何变化,都不需要有任何感知。只要门面服务做得足够好,随意更换另外一个日志框架,应用程序无需修改任何一行代码。

短信平台 SDK 模块里,我们并没有直接依赖具体的日志框架,而是仅仅依赖 SLF4J。

Maven依赖SLF4J API配置

SDK 中需要打印日志的类会定义日志对象 Logger:

private final static Logger logger = LoggerFactory.getLogger(SmsSenderClient.class);

下图是代码简化图,Logger 对象定义了不同级别的日志输出方式。

SLF4J Logger接口方法定义

在业务应用中,我们一般使用 debug(调试)、info(信息)、warn(警告)、error(错误)这几个级别,等级由低到高。

我们开发测试时一般输出 DEBUG 级别的日志,便于调试;而在生产环境,通常会配置为只输出 INFO 级别甚至只输出 ERROR 级别的日志,以减少日志量并关注核心问题。

Spring Boot 与 Logback

Spring Boot 默认集成的日志框架是 SLF4J + Logback。官方推荐优先使用带有 -spring 后缀的文件名作为日志配置文件(如使用 logback-spring.xml,而不是 logback.xml),并将 XML 文件放在 src/main/resources 目录下。

Spring Boot项目中的日志配置文件

下图是一个基础的 Logback 配置文件示例:

Logback基础配置文件示例

在 Logback 日志框架的配置中,有三个核心元素:appenderloggerroot

1. Appender(输出器):用于指定日志输出目的地的组件。上面的配置定义了两个输出器,一个用于控制台输出(CONSOLE),另一个用于文件输出(FILE)。

2. Logger(记录器):用于记录日志事件的组件,负责接收应用程序中产生的日志事件并将它们传送到相应的 Appender

  • 对于 com.example.Main 类,将其日志级别设置为 DEBUG,这意味着只有 DEBUG 级别及以上的日志事件才会被记录,并且仅会输出到控制台。
  • 对于 com.example.service 包,将其日志级别设置为 WARN。只有 WARN 级别及以上的日志事件才会被记录,并且仅会输出到文件。

3. Root(根Logger):这是所有日志记录器树结构的根节点。我们将根 Logger 的日志级别设置为 INFO,并将其绑定到 CONSOLEFILE 两个输出器。这意味着所有未特别指定 Logger 的日志事件都将遵循此配置。

Logback 配置实战参考

下图是一个短信服务实际使用的 Logback 配置文件:

短信服务的Logback配置文件

这个配置中有两个关键点值得关注:

1. 日志格式
我们定义了两个 Appender,分别输出到控制台和文件,它们使用了统一的日志格式:

<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %p %t %logger{36}:%L - %msg%n</pattern>

这个格式中各个占位符的含义如下:

Logback日志格式占位符详解

服务启动后,输出的日志格式如下图所示:

Spring Boot服务启动日志输出示例

从启动日志中,我们可以清晰地看到线程名、类名以及代码行号,这在定位复杂问题时非常有用。

2. 备份历史文件
由于本地磁盘容量有限,我们不可能永久保存所有日志文件。笔者曾遇到过因 Log4j 日志文件写满磁盘,导致 Tomcat 所有线程阻塞的场景。通过 jstack 命令定位,发现线程阻塞在 Log4j 写日志的代码上。

因此,合理的配置文件的保留策略至关重要。我们可以设置当日志文件大小达到指定阈值,或按天生成新日志文件时进行滚动备份,并只保留最近一定数量的历史文件。

有同学可能会问:那全量的日志数据去哪查呢?我们可以在服务器上部署 Filebeat 等日志采集代理,将日志实时发送到 Elasticsearch 或其他存储系统进行集中管理和分析。

日志采集与集中存储架构图

日志打印规范

掌握了框架和配置,日常开发中遵循良好的日志打印习惯同样重要。

1. 打印关键方法的入参和出参
核心接口及关键方法的入参和返回值,都建议打印日志。这对于追踪数据流向、复现问题场景至关重要。

2. 日志级别判断
这一条主要针对 debugtrace 这类低级别日志。为了避免在线上环境(日志级别为 INFO 或更高)中,仍然执行日志消息的字符串拼接操作(造成不必要的性能浪费),应先进行级别判断:

User user = new User(666L, "xxxx", "xxxx");
if (log.isDebugEnabled()) {
    log.debug("userId is: {}", user.getId());
}

3. 坚持使用 SLF4J 的门面 API
再次强调,请务必使用 SLF4J 提供的 API 来打印日志,这能最大限度地降低应用程序与具体日志实现框架的耦合度。

4. 使用占位符{},而非字符串连接符+
使用占位符性能更高,代码也更优雅。

Object[] paramArray = {newVal, below, above};
logger.debug("Value {} was inserted between {} and {}.", paramArray);

5. 完整打印异常堆栈信息
下面的日志输出方式不规范,丢失了最重要的堆栈信息:

try {
//业务代码处理
} catch (Exception e) {
// 错误
    LOG.error('你的程序有异常啦');
}

正确的做法是将异常对象作为最后一个参数传入:

try{
// 业务代码处理
}catch(Exception e){
  log.error("你的程序有异常啦",e);
}

另外请注意,仅使用 e.getMessage() 不会记录详细的堆栈信息,只包含基本的错误描述,不利于排查复杂问题。

6. 禁止在线上环境开启 debug
除了业务代码可能产生大量 debug 日志,一些框架本身也可能输出 debug 日志。在线上环境开启 debug 级别,极易导致日志文件急剧膨胀,最终占满磁盘空间,并可能引发 CPU 和磁盘 I/O 瓶颈,直接影响系统稳定性。

7. 避免使用 e.printStackTrace()
使用 e.printStackTrace() 会将堆栈信息直接打印到标准错误输出(通常是控制台),这会与业务代码的日志交错混合在一起,格式不统一,给日志收集和排查带来不便。应始终使用日志框架的 API 来记录异常。

遵循以上这些规范和技巧,能让你打出的日志更具价值,在关键时刻成为定位问题的利器。如果你对更多后端开发与运维实践感兴趣,欢迎在云栈社区交流探讨。




上一篇:Python 魔法方法高阶应用指南:7个关键Dunder方法深度解读
下一篇:手把手教你部署Redis哨兵集群,实现高可用(Redis 5.0/6.0)
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-24 16:16 , Processed in 0.238184 second(s), 43 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表