1. 日志基础:什么是日志、日志的级别、日志的作用与价值
好,咱们直接开始。日志这东西,说白了就是程序运行时的「黑匣子」。你想想看,一个系统跑在生产环境,你没法像调试本地代码那样打断点。那出了问题怎么办?靠的就是日志。
我个人习惯把日志看作系统的「体检报告」。它记录了什么时间、在哪个模块、发生了什么事。没有日志,你就像蒙着眼睛修车——全靠摸。
1.1 什么是日志
日志就是程序在运行过程中输出的、带有时间戳的文本记录。它可以是简单的「服务启动成功」,也可以是复杂的协议报文转储。
举个例子,一个 HTTP 请求进来,日志可能会记录:
2025-01-15 14:23:01.456 [http-nio-8080-exec-3] INFO com.example.OrderController - 收到下单请求,订单号: ORD20250115001
2025-01-15 14:23:01.789 [http-nio-8080-exec-3] DEBUG com.example.OrderService - 查询用户余额: userId=1024, balance=500.00
2025-01-15 14:23:02.012 [http-nio-8080-exec-3] WARN com.example.PaymentService - 支付接口响应超时,触发重试,第1次
2025-01-15 14:23:02.345 [http-nio-8080-exec-3] ERROR com.example.OrderController - 下单失败: 支付超时,订单已回滚
你看,每一行都包含了时间、线程、级别、类名和具体消息。这就是日志的原始形态。
1.2 日志的五个级别
日志级别不是随便定的。它有一套通用的优先级体系,从低到高依次是:
| 级别 | 含义 | 典型场景 | 生产环境是否开启 |
|---|---|---|---|
| DEBUG | 调试信息 | 变量值、函数入口/出口、SQL 参数 | 通常关闭 |
| INFO | 普通信息 | 服务启动、请求到达、任务完成 | 开启 |
| WARN | 警告 | 接口超时、资源即将耗尽、降级触发 | 开启 |
| ERROR | 错误 | 数据库连接失败、业务异常、请求处理失败 | 开启 |
| FATAL | 致命错误 | 内存溢出、配置加载失败、进程即将退出 | 开启 |
这里有个关键点:级别是过滤用的。你设置日志级别为 WARN,那么 DEBUG 和 INFO 的日志就不会输出。这能帮你控制日志量,避免磁盘被撑爆。
1.3 每个级别怎么用
DEBUG —— 开发阶段的主力。我在项目中习惯把关键变量的值、分支判断的结果、循环的迭代次数都打出来。但注意,千万别把敏感信息(密码、token)打进去。我曾经见过同事把用户密码明文打到了 DEBUG 日志里,结果被安全审计抓了个正着。
INFO —— 系统运行的「脉搏」。服务启动、请求到达、任务完成、定时调度执行,这些都应该用 INFO 记录。它告诉你系统在正常工作。
WARN —— 潜在风险的「预警灯」。比如接口响应时间超过阈值但还没失败、磁盘使用率达到 80%、重试机制被触发。WARN 不是错误,但值得关注。
ERROR —— 需要立即处理的问题。数据库连不上、请求处理抛异常、消息消费失败。ERROR 日志通常意味着功能受损,需要人工介入。
FATAL —— 最严重的级别。进程即将退出、JVM 内存溢出、核心配置加载失败。FATAL 一出,基本意味着系统不可用。
1.4 日志的作用与价值
日志的价值,我总结为四个字:追、定、监、审。
- 追(问题追踪):线上出 Bug 了,日志是第一手证据。通过日志可以还原请求链路、定位异常代码行、分析数据状态。
- 定(系统定界):一个请求跨了 5 个服务,到底哪个环节慢了?日志里的时间戳能帮你精确到毫秒级。
- 监(监控告警):ERROR 日志数量突然飙升,告警系统立刻通知你。没有日志,你连系统挂了都不知道。
- 审(安全审计):谁在什么时间操作了什么数据?日志是合规审计的基石。
说白了,日志就是系统的「后悔药」。没有它,出了问题你只能靠猜。
1.5 日志体系结构图
下面这张图展示了日志从产生到消费的完整链路。我把它画出来,方便你建立整体认知。
嗯,这张图你看懂了吗?从上到下,日志从代码里产生,经过级别过滤,输出到不同目的地,最后被我们用来做各种分析。每一层都可以独立配置,这也是日志框架灵活的地方。
1.6 日志配置示例
光说不练假把式。下面是一个 Logback 的配置片段,展示了如何设置日志级别和输出:
<configuration>
<!-- 控制台输出 -->
<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>
<!-- 文件输出,按天滚动 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>/var/log/myapp/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>/var/log/myapp/app.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 根日志级别 -->
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</root>
<!-- 某个包单独设置 DEBUG 级别 -->
<logger name="com.example.myapp.service" level="DEBUG"/>
</configuration>
- 根日志级别设为 INFO,生产环境不会输出 DEBUG 日志
- 某个包可以单独调低级别,方便调试
- 文件按天滚动,保留 30 天,避免磁盘撑爆
1.7 日志的「坑」与建议
最后聊几个我踩过的坑,你注意避开:
- 别打无意义日志:比如在循环里打「处理第 i 条数据」,数据量一大,日志文件直接爆炸。我见过一个项目,一天产生了 200GB 日志,全是这种废话。
- 别吞异常:catch 块里只打日志不处理,等于掩耳盗铃。我曾经排查过一个线上问题,发现 ERROR 日志打了,但业务逻辑继续往下走,数据全乱了。
- 别忘加时间戳:没有时间戳的日志,等于没有坐标的地图。你根本不知道问题发生在什么时候。
- 别用 System.out.println:这玩意儿不走日志框架,没法控制级别,也没法归档。生产环境千万别用。
好了,日志基础就讲到这里。记住一句话:日志是你和系统对话的语言。写得好,系统帮你说话;写得差,系统对你沉默。