程序员都知道日志记录重要,为何还有人在这基本功上栽跟头?

大家好,我是500佰,技术宅男 目前正在前往独立开发路线,我会在这里分享关于编程技术独立开发技术资讯以及编程感悟等内容

技术或许晦涩难懂,但我愿意用最温柔的语言,把它写成一首人人都能读懂的小诗。

在一个项目里,同事小李遇到个大麻烦。那天程序突然报错,系统运行不下去了。小李急得像热锅上的蚂蚁,到处找问题。因为他平时不咋重视日志记录,代码里就简单写了几个无关紧要的打印语句。想排查错误,根本没线索,只能一行行看代码。这不仅浪费大量时间,还耽误项目进度,差点被领导批评。

从这就能看出,日志记录没做好,在实际开发里就是个大麻烦。现在不少程序员都跟小李一样,觉得日志记录不重要,写代码时敷衍了事。可在复杂项目里,日志就是排查问题的 "地图",没有它,寸步难行。这让我挺意外的,能在业绩上 "卷" 出一片天,怎么连个日志都写不好呢?

有的是 不想 打、有的是 意识不到 要打、还有的是 真不会 打日志啊!

也有些人代码通篇用 System.out.println() 打印 !

更有甚者缺乏产品运维经验, 业务代码报错, 不知道如何处理,一脸懵逼!

日志记录的优点

  1. 日志是我们系统出现错误时,最快速有效的定位工具,没有日志给出的错误信息,遇到报错你就会一脸懵逼。
  2. 日志还可以用来记录业务信息,比如记录用户执行的每个操作,不仅可以用于分析改进系统,同时在遇到非法操作时,也能很快找到凶手是谁。
  3. 项目规模越来越大,代码成千上万行,模块间关系复杂。如果没有详细日志记录,一旦出错,排查问题如同大海捞针

日志记录三种选型

很多朋友应该是通过 System.out.println 输出信息来调试程序的,简单方便。

但是,System.out.println 存在很严重的问题!

原因: 相比之下,System.out.println 每次调用都会进行一次同步的 I/O 操作,频繁使用会显著影响程序性能,因此在生产环境中并不推荐使用。而且它只能将简单信息输出到控制台,无法灵活地设置日志级别、格式或输出目标,难以满足实际运维和排查问题的需求。

因此我们会选择以下三种日志框架:Apache Log4j 、它的升级版 Log4j 2 、Spring Boot 默认集成的 Logback

Java 日志框架或工具库:

能灵活调整格式、设置日志级别、将日志写入到文件中、压缩日志等。

SLF4J (Simple Logging Facade for Java),这玩意并不是一个具体的日志实现,而是为各种日志框架提供简单统一接口的日志门面(抽象)。

门面是啥ne?

意思是:现在我们要写日志了,首先要联系"前台接待"SLF4J。它会先让我们选好日志级别(比如 debug、info、warn、error),并把日志内容交给它。SLF4J 本身不直接写日志 ,而是把这些信息转交给后台的具体日志实现(比如 Logback),最后由 Logback 负责把日志真正写到文件或控制台里。

好处:无论我们用的是哪种日志框架,或者以后想更换日志实现,日志的调用方式始终保持一致,不需要因为底层实现的变化而修改日志相关的代码,比如不用把 log.info 改成 log.printInfo,只需要专注于业务本身即可。

Log4j、Log4j 2 和 Logback 应该选择哪一个呢?

不难发现,SLF4J、Log4j 和 Logback 竟然都是同一个作者(俄罗斯程序员 Ceki Gülcü)。

Log4j 已经过时,直接pass掉。Log4j 2 和 Logback 对比一下

性能方面,Log4j 2 借助 LMAX Disruptor 实现了更高效的异步日志处理,性能略胜一筹。稳定性上,虽然各自都出现过安全漏洞,但 Log4j 2 的问题更严重一些,相对来说 Logback 更让人放心。易用性方面,两者差别不大,不过 Logback 是 SLF4J 的原生实现,而 Log4j 2 需要额外配置 SLF4J 适配器。

日志框架使用

非常非常简单,获取 Logger对象,然后调用 logger.info(error等)就能输出日志了。

arduino 复制代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
​
public class WubaiService {
   private static final Logger logger = LoggerFactory.getLogger(MyService.class);
​
   public void doWork() {
       logger.info("执行了某个xx");
  }
}

优化一下以上代码 go go go

我们可以使用 this.getClass 动态获取当前类的实例,来创建 Logger 对象就这么简单:

java 复制代码
public class WubaiService {
   private final Logger logger = LoggerFactory.getLogger(this.getClass());
​
   public void doWork() {
       logger.info("执行了某个xx");
  }
}

要想更省事呢

还有更简单的方式,使用 Lombok 工具库提供的 @Slf4j 注解,可以自动为当前类生成一个名为 log 的 SLF4J Logger 对象,简化了 Logger 的定义过程。示例代码如下:

typescript 复制代码
import lombok.extern.slf4j.Slf4j;
​
@Slf4j
public class WubaiService {
   public void doWork() {
       log.info("执行了某个xx");
  }
}

这个是我比较推荐的方式,效率拉满的 省事又方便 我常用

此外 ,你可以通过修改日志配置文件(比如 logback.xmllogback-spring.xml)来设置日志输出的格式、级别、输出路径等。日志配置文件比较复杂,不建议大家去记忆语法,随用随查即可,简单不展开说了。

记录日志的经验与实践

分享一些我个人记录日志的经验。内容较多,大家可以先了解一下,实际开发中按需运用。

日志级别:

TRACE:最细粒度的信息,通常只在开发过程中使用,用于跟踪程序的执行路径。and DEBUG、INFO、WARN、ERROR、FATAL,这几个日志细腻度依次递增。

其中,用的最多的当属 DEBUG、INFO、WARN 和 ERROR 了。最最多INFO、ERROR

日志打印高级使用:

logger.debug("账号:" + userAccount + " 注册成功。"); 这种拼接式打印强烈不推荐

推荐 使用:当要输出的日志内容中存在变量时,建议使用参数化日志,也就是在日志信息中使用占位符(比如 {}),由日志框架在运行时替换为实际参数值。

比如输出一行用户注册日志:

php 复制代码
  
// 强烈不推荐
logger.debug("账号:" + userAccount + " 注册成功。");
​
// 推荐
logger.debug("账号:{} 注册成功。", userAccount);
​
// 异常日志打印示例
try {
   // 业务逻辑
} catch (Exception e) {
logger.error("处理账号:{} 时发生异常:", userAccount, e);
}

日志打印优化方法:

减少不关注的日志输出量:

原因:日志太多 占用大量磁盘空间,且对服务器增加IO和带宽开销,增加硬件成本

场景1: 避免在循环中输出大量日志

可以添加条件来控制,在批量处理时,每处理 100 条数据时才记录一次日志,

或者在循环中利用 StringBuilder 进行字符串拼接,循环结束后统一输出:

less 复制代码
StringBuilder loggerBuilder = new StringBuilder("结果:");
for (List list : lists) {
   try {
       logger.append("成功...{}",userAccount)
  } catch (Exception e) {
       logger.append(String.format("失败[userAccount=%s, 原因=%s], ", list.getUserAccount(), e.getMessage()));
  }
}
logger.info(loggerBuilder.toString());

场景2: 当前日志级别进行级别检查,从而避免多余的逻辑:

erlang 复制代码
if (logger.isDebugEnabled()) {
   logger.debug("xxx");
}

场景3: 另外,还可以通过更改日志配置文件整体过滤掉特定级别的日志,来防止日志刷屏

xml 复制代码
<!-- Logback 示例 -->
<appender name="LIMITED" class="ch.qos.logback.classic.AsyncAppender">
<!-- 只允许 INFO 级别及以上的日志通过 -->
   <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
       <level>INFO</level>
   </filter>
   <!-- 配置其他属性 -->
</appender>

日志打印的最佳时机:

对于调用链较长的操作,确保在每个环节都有日志,以便追踪到问题所在的环节。

我的建议是尽量在前期多记录一些日志,后面再慢慢移除掉不需要的日志。比如可以利用 AOP 切面编程在每个业务方法执行前输出执行信息:

less 复制代码
@Aspect
@Component
public class LoggingAspect {
​
   @Before("execution(* com.wubai.service..*(..))")
   public void logBeforeMethod(JoinPoint joinPoint) {
       Logger logger = LoggerFactory.getLogger(joinPoint.getTarget().getClass());
       logger.info("方法 {} 开始执行", joinPoint.getSignature().getName());
  }
}

利用 AOP,还可以自动打印每个 Controller 接口的请求参数和返回值,这样就不会错过任何一次调用信息了。

不过这样做也有一个很重要的点,注意不要在日志中记录了敏感信息,比如用户手机号。万一你的日志不小心泄露 出去,就麻烦大了

统一的日志格式:

统一的日志格式有助于日志的解析、搜索和分析,特别是在分布式系统中。

我举个例子大家就能感受到这么做的重要性了。

统一的日志格式:

ini 复制代码
2025-05-31 17:50:39:555 [main] INFOcom.wubai.service.UserService-账号:wubai注册成功
2025-05-31 17:50:39:533 [main] ERRORcom.wubai.service.UserService-账号:wubai2注册失败,原因:密码复杂度过低
2025-05-31 17:50:39:632 [main] DEBUGcom.wubai.dao.UserDao-执行SQL:[SELECT*FROMt_usersWHEREUserAccout=wubai]
2025-05-31 17:50:39:637 [main] WARNcom.wubai.config.AppConfig-配置项`Timeout`使用默认值:5000ms
2025-05-31 17:50:44:633 [main] INFOcom.wubai.Main-应用启动成功,耗时:5秒

这段日志整齐清晰,支持按照时间、线程、级别、类名和内容搜索。

不统一的日志格式:

sql 复制代码
注册成功 账号:wubai
错误 用账号:wubai2 注册失败!密码不满足
DEBUG 执行SQL SELECT * FROM t_users WHERE id=wubai
Timeout 时间
xx应用启动成功了

en,看到这种日志业务出了问题 就老实了!

异步日志:

对于对性能要求较高的业务,可以采用异步日志方式,将日志写入交给独立线程处理,避免主线程被阻塞,从而提升整体性能。

其实无需手动创建线程,只需调整Logback 的配置,即可开启异步日志。配置时,重点是合理设置缓冲队列的大小和日志丢弃策略,以防止日志堆积或丢失。

今天的分享就到这里。写了这么多,只希望能让大家更加关注日志记录,并在实际开发中逐步养成良好的日志习惯 ,学会的话,给500佰点个赞吧 ~

优秀从来不是一蹴而就,而是一次又一次的主动选择

愿你在这条路上,越走越轻松,也越走越有信心

我是程序员500佰,我正在前往独立开发路线,我会在这里分享编程技术、编程感悟。

往期推荐

< 上一篇

出生几乎文盲家庭 一个普通人的30岁他经历了什么

相关推荐
程序员的世界你不懂19 分钟前
(9)-Fiddler抓包-Fiddler如何设置捕获Https会话
前端·https·fiddler
MoFe124 分钟前
【.net core】天地图坐标转换为高德地图坐标(WGS84 坐标转 GCJ02 坐标)
java·前端·.netcore
去旅行、在路上1 小时前
chrome使用手机调试触屏web
前端·chrome
Aphasia3111 小时前
模式验证库——zod
前端·react.js
lexiangqicheng2 小时前
es6+和css3新增的特性有哪些
前端·es6·css3
拉不动的猪3 小时前
都25年啦,还有谁分不清双向绑定原理,响应式原理、v-model实现原理
前端·javascript·vue.js
烛阴3 小时前
Python枚举类Enum超详细入门与进阶全攻略
前端·python
孟孟~3 小时前
npm run dev 报错:Error: error:0308010C:digital envelope routines::unsupported
前端·npm·node.js
孟孟~3 小时前
npm install 报错:npm error: ...node_modules\deasync npm error command failed
前端·npm·node.js
狂炫一碗大米饭3 小时前
一文打通TypeScript 泛型
前端·javascript·typescript