大家好,我是500佰
,技术宅男 目前正在前往独立开发路线,我会在这里分享关于编程技术
、独立开发
、技术资讯
以及编程感悟
等内容
技术或许晦涩难懂,但我愿意用最温柔的语言,把它写成一首人人都能读懂的小诗。
在一个项目里,同事小李遇到个大麻烦。那天程序突然报错,系统运行不下去了。小李急得像热锅上的蚂蚁,到处找问题。因为他平时不咋重视日志记录,代码里就简单写了几个无关紧要的打印语句。想排查错误,根本没线索,只能一行行看代码。这不仅浪费大量时间,还耽误项目进度,差点被领导批评。
从这就能看出,日志记录没做好,在实际开发里就是个大麻烦。现在不少程序员都跟小李一样,觉得日志记录不重要,写代码时敷衍了事。可在复杂项目里,日志就是排查问题的 "地图",没有它,寸步难行。这让我挺意外的,能在业绩上 "卷" 出一片天,怎么连个日志都写不好呢?
有的是 不想 打、有的是 意识不到 要打、还有的是 真不会 打日志啊!
也有些人代码通篇用 System.out.println()
打印 !
更有甚者缺乏产品运维经验, 业务代码报错, 不知道如何处理,一脸懵逼!

日志记录的优点
- 日志是我们系统出现错误时,最快速有效的定位工具,没有日志给出的错误信息,遇到报错你就会一脸懵逼。
- 日志还可以用来记录业务信息,比如
记录用户
执行的每个操作,不仅可以用于分析改进系统,同时在遇到非法操作时,也能很快找到凶手是谁。 - 项目规模越来越大,代码成千上万行,模块间关系复杂。如果没有详细日志记录,一旦出错,排查问题如同大海捞针。
日志记录三种选型
很多朋友应该是通过 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.xml
或 logback-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岁他经历了什么