GitHub star 加星程序员必看!8 个日志实战技巧,让 Bug 排查效率翻倍

32 阅读8分钟

刚入职的程序员小阿巴,开发的批量导入数据程序一上线就崩了。用户反馈报错中断,可服务器上的报错信息少得可怜,他硬生生花了 8 小时,才通过逐行测试用户数据定位到问题,原本的摸鱼时光全泡汤了。

Taimili 艾米莉 ( 一款专业的 GitHub star 管理和github 加星涨星工具taimili.com )

艾米莉 是一款优雅便捷的 GitHub star 管理和github 加星涨星工具,基于 PHP & javascript 构建, 能对github 得 star fork follow watch 管理和提升,最适合github 的深度用户

WX20251021-210346@2x.png

导师鱼皮看到他愁眉苦脸的样子,一语道破关键:“你没打日志吧?” 小阿巴满脸疑惑:“谁是日志?为什么要打它?” 这一幕,恐怕很多刚入行的程序员都似曾相识。日志作为程序的 “运行日记”,能精准记录执行状态,可太多人不懂怎么正确使用。今天就把鱼皮沉淀多年的 8 个日志实战技巧分享给大家,让你少走弯路!

一、先搞懂:日志到底是什么?

日志是程序运行时的 “状态记录仪”,能详细记录代码执行的关键节点、参数信息和异常情况,当系统出现 Bug 时,无需反复测试,通过日志就能快速定位问题根源。

比如批量导入用户的核心代码,合理的日志能让执行过程一目了然:

java

运行

@Slf4j
public class UserService {
    public void batchImport(List<UserDTO> userList) {
        log.info("开始批量导入用户,总数:{}", userList.size());
        
        int successCount = 0;
        int failCount = 0;
        
        for (UserDTO userDTO : userList) {
            try {
                log.info("正在导入用户:{}", userDTO.getUsername());
                validateUser(userDTO);
                saveUser(userDTO);
                successCount++;
                log.info("用户 {} 导入成功", userDTO.getUsername());
            } catch (Exception e) {
                failCount++;
                log.error("用户 {} 导入失败,原因:{}", userDTO.getUsername(), e.getMessage(), e);
            }
        }
        
        log.info("批量导入完成,成功:{},失败:{}", successCount, failCount);
    }
}

代码中的log.info(正常流程)和log.error(异常情况),就是最基础的日志记录方式。

二、入门:日志框架怎么用?

每种编程语言都有成熟的日志框架,Java 生态中常用 Log4j 2、Logback 等。如果使用 Spring Boot,无需额外引入依赖,它默认集成了 Logback,直接使用即可。

1. 获取日志对象(3 种方式)

  • 方式 1:手动通过 LoggerFactory 获取

java

运行

public class MyService {
    private static final Logger logger = LoggerFactory.getLogger(MyService.class);
}
  • 方式 2:通过this.getClass()获取当前类类型

java

运行

public class MyService {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
}
  • 方式 3:用 Lombok 的@Slf4j注解(推荐)无需手动定义日志对象,注解会自动生成:

java

运行

@Slf4j
public class MyService {
    public void doSomething() {
        log.info("执行了一些操作");
    }
}

等同于自动生成:

java

运行

private static final org.slf4j.Logger log = 
  org.slf4j.LoggerFactory.getLogger(MyService.class);

2. 千万别用 System.out.println 替代日志!

很多新手会图方便用System.out.println输出信息,但这存在两大致命问题:

  • 性能差:同步方法,频繁调用会触发耗时 I/O 操作,拖慢程序;
  • 不灵活:只能输出到控制台,无法保存到文件,也不能控制格式和级别,历史日志无法追溯。

而日志框架能完美解决这些问题,还支持灵活配置,这也是打日志的核心价值。

三、进阶:8 个日志最佳实践

1. 合理选择日志级别(避免信息泛滥)

日志按重要程度分为多个级别,核心常用的有 4 种,不同场景必须严格区分:

  • DEBUG:调试用的详细信息(如对象完整属性),仅开发 / 测试环境使用;
  • INFO:正常业务流程记录(如 “开始导入”“执行完成”);
  • WARN:潜在问题但不影响主流程(如邮箱格式异常但仍可导入);
  • ERROR:异常或错误(如导入失败、接口调用超时),需包含完整异常信息。

示例代码:

java

运行

log.debug("用户对象的详细信息:{}", userDTO);  // 调试信息
log.info("用户 {} 开始导入", username);  // 正常流程
log.warn("用户 {} 的邮箱格式可疑,但仍然导入", username);  // 警告信息
log.error("用户 {} 导入失败", username, e);  // 错误信息(e为异常对象)

关键原则:生产环境需调高日志级别(如 INFO 或 WARN),避免 DEBUG 日志淹没重要信息,同时减少磁盘占用。

2. 用参数化日志,拒绝字符串拼接

很多人习惯用字符串拼接记录日志:

java

运行

// 不推荐!
log.info("用户 " + username + " 开始导入");

这种方式的问题是:无论日志是否最终输出,拼接操作都会执行,浪费性能。

正确做法是使用参数化日志({}作为占位符):

java

运行

// 推荐!
log.info("用户 {} 开始导入", username);

日志框架会在运行时动态替换参数,未输出的日志不会执行额外操作,性能更优。

记录异常时更要注意,需传入完整异常对象e,才能保留堆栈信息:

java

运行

try {
    // 业务逻辑
} catch (Exception e) {
    log.error("用户 {} 导入失败", username, e);  // 必须传入e
}

3. 把握日志时机,不冗余不遗漏

日志不是越多越好,关键是 “关键时刻有记录”:

  • 必记场景:方法入口(参数)、方法出口(返回值)、核心业务步骤、异常捕获处;
  • 减少冗余:循环中避免每条数据都打日志(如 10 万条数据无需逐条记录导入中状态);
  • 高效技巧:用 AOP 切面编程,自动为所有业务方法添加入口 / 出口日志,减少重复代码;
  • 安全提醒:日志中严禁记录敏感信息(如密码、手机号、身份证号),防止信息泄露。

4. 控制输出量,避免日志刷屏

当处理大量数据时,需通过以下方式控制日志数量:

  • 条件输出:每处理 N 条数据记录一次进度(如每 100 条);

java

运行

if ((i + 1) % 100 == 0) {
    log.info("批量导入进度:{}/{}", i + 1, userList.size());
}
  • 批量拼接:循环中用 StringBuilder 积累信息,结束后统一输出;

java

运行

StringBuilder logBuilder = new StringBuilder("处理结果:");
for (UserDTO userDTO : userList) {
    processUser(userDTO);
    logBuilder.append(String.format("成功[ID=%s], ", userDTO.getId()));
}
log.info(logBuilder.toString());
  • 级别过滤:通过配置文件只输出 INFO 及以上级别日志,屏蔽 DEBUG 冗余信息。

5. 统一日志格式,方便排查

混乱的日志格式会增加排查难度,需在配置文件中定义统一格式,包含关键信息:

xml

<!-- 控制台日志格式配置 -->
<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>

格式说明:

  • %d:时间戳(精确到毫秒);
  • [%thread]:线程名称;
  • %-5level:日志级别(左对齐,占 5 位);
  • %logger{36}:类名(最长 36 字符);
  • %msg%n:日志内容 + 换行。

进阶技巧:用 MDC 添加上下文信息(如请求 ID、用户 ID),方便追踪分布式系统中的调用链路:

java

运行

@PostMapping("/user/import")
public Result importUsers(@RequestBody UserImportRequest request) {
    // 设置MDC上下文
    MDC.put("requestId", generateRequestId());
    MDC.put("userId", String.valueOf(request.getUserId()));
    try {
        log.info("用户请求处理开始");
        userService.batchImport(request.getUserList());
        return Result.success();
    } finally {
        MDC.clear(); // 必须清理,避免内存泄漏
    }
}

配置文件中引用 MDC 变量:

xml

<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - [%X{requestId}] [%X{userId}] %msg%n</pattern>

6. 开启异步日志,提升程序性能

默认情况下,日志输出是同步操作(写入文件时阻塞主线程),高并发场景会影响性能。开启异步日志后,写日志操作会交给独立线程执行,不阻塞业务流程:

xml

<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
    <queueSize>512</queueSize>  <!-- 队列大小 -->
    <discardingThreshold>0</discardingThreshold>  <!-- 不丢弃日志 -->
    <neverBlock>false</neverBlock>  <!-- 队列满时阻塞(避免日志丢失) -->
    <appender-ref ref="FILE" />  <!-- 关联文件输出目标 -->
</appender>

<root level="INFO">
    <appender-ref ref="ASYNC" />
</root>

注意:异步日志可能因程序崩溃丢失缓冲区日志,需根据业务场景权衡(性能优先可开启,日志完整性优先则关闭)。

7. 日志管理:自动切分 + 清理,避免磁盘爆满

日志文件长期不处理会占用大量磁盘空间,甚至导致服务器崩溃。通过配置滚动策略,让框架自动管理日志:

xml

<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
    <fileNamePattern>logs/app-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
    <maxFileSize>10MB</maxFileSize>  <!-- 单个文件最大10MB -->
    <maxHistory>30</maxHistory>  <!-- 保留30天日志 -->
    <totalSizeCap>1GB</totalSizeCap>  <!-- 日志总大小不超过1GB -->
</rollingPolicy>

配置说明:

  • 按日期 + 大小切分:每天生成新文件,单个文件超 10MB 自动创建新文件;
  • 自动压缩:.gz 后缀自动压缩日志文件,节省磁盘空间;
  • 自动清理:只保留最近 30 天日志,总大小超 1GB 时自动删除旧日志。

8. 分布式系统:集成日志收集系统

当项目发展为多服务分布式架构时,登录多台服务器查看日志效率极低,需搭建集中式日志系统,常用组合 ELK(Elasticsearch+Logstash+Kibana):

  • Logstash:收集各服务日志,统一格式化;
  • Elasticsearch:存储并索引日志,支持快速搜索;
  • Kibana:可视化界面,可按关键词、时间范围、服务名等筛选日志。

注意:ELK 搭建和运维成本较高,小团队或小型项目可选用轻量级方案(如 Graylog、Loki),按需选择即可。

四、最后提醒:日志是写给 “人” 看的

日志的核心价值是 “便于排查问题”,无论是未来的自己,还是团队同事,都能通过日志快速理解程序运行状态。记住:

  • 不打日志的程序员,不是好程序员;
  • 乱打日志的程序员,会给队友挖坑;
  • 规范打日志,才能让 Bug 无所遁形。