1. 日志基础:日志是什么?为什么需要日志?日志的级别
好,咱们开始第一讲。说实话,每次带新人,我第一件事不是教他们写代码,而是教他们怎么看日志。为什么?因为日志就是系统的「黑匣子」。你想想看,一个线上服务跑着跑着突然挂了,你连它死前说了什么都没留下,那你怎么查?
1.1 日志到底是什么?
日志,说白了就是程序在运行过程中留下的「脚印」。每一条日志记录了一个时间点、一个位置、发生了什么。我习惯把日志比作飞机的飞行数据记录仪——平时没人看它,但一出事,它就是唯一的线索。
从技术角度讲,日志是系统对外输出的、带有时间戳的结构化或半结构化文本。它记录了:
- 什么时候(时间戳)
- 谁(哪个进程、哪个线程)
- 在哪(哪个模块、哪个函数)
- 发生了什么(事件描述、状态变化)
- 结果如何(成功、失败、异常信息)
核心观点:没有日志的系统,就像没有仪表盘的飞机。你敢坐吗?
1.2 为什么我们需要日志?
这个问题我问过不少刚入行的同学。有人说是为了调试,有人说是为了监控。都对,但不全。我个人把日志的价值归纳为四个层面:
- 问题定位 —— 系统出故障了,日志是第一手证据。我记得有一次线上服务半夜告警,CPU 飙到 99%,就是靠一条 WARN 日志锁定了死循环的位置。
- 行为审计 —— 谁在什么时候做了什么操作。这在金融、电商系统里尤其重要。我曾经帮客户排查过一笔异常扣款,最后在日志里找到了那个「多按了一次提交」的请求。
- 性能分析 —— 通过日志里的耗时数据,你能看出哪个接口慢、哪个数据库查询拖后腿。我习惯在关键路径上加耗时日志,比任何 profiling 工具都直观。
- 监控告警 —— 日志是监控系统的「原料」。ERROR 日志数量一涨,告警就该响了。
我的经验:日志不是写给机器看的,是写给「明天凌晨三点被电话吵醒的你」看的。写日志的时候,想想那个困得要死的自己能不能看懂。
1.3 日志的五个级别
日志级别这东西,看着简单,但用错了会出大问题。我见过一个项目,所有日志都用 INFO,结果一天产生 200GB 日志,真正有用的 ERROR 被淹没了。嗯,这里要注意——级别不是摆设,它是你的「过滤器」。
标准日志级别从低到高依次是:
| 级别 | 含义 | 典型场景 | 线上是否开启 |
|---|---|---|---|
| DEBUG | 调试信息 | 变量值、函数入口/出口、循环次数 | 通常关闭 |
| INFO | 普通信息 | 服务启动、请求到达、任务完成 | 开启 |
| WARN | 警告 | 配置降级、重试、即将过期 | 开启 |
| ERROR | 错误 | 请求失败、数据库连接断开 | 开启 |
| FATAL | 致命错误 | 内存溢出、配置错误导致无法启动 | 开启 |
DEBUG 级别
DEBUG 是开发者的「放大镜」。我习惯在开发环境打开它,看每一步的执行细节。但线上环境我从来不开——你想想看,一个每秒处理几千请求的服务,每条请求打 5 条 DEBUG 日志,磁盘 IO 直接被打满。我曾经见过一个团队把 DEBUG 日志开到了线上,结果日志把磁盘撑爆了,服务直接挂了。
INFO 级别
INFO 记录的是「正常流程中的重要节点」。比如服务启动完成、用户登录成功、订单创建成功。我个人习惯用 INFO 勾勒出一次请求的「骨架」——什么时候进来、什么时候出去、耗时多少。这样排查问题时,光看 INFO 就能知道大概路径。
WARN 级别
WARN 是「还没出问题,但快了」。比如重试机制触发、配置使用了默认值、缓存命中率下降。我曾经在项目中加了一条 WARN 日志,专门记录数据库连接池使用率超过 80% 的情况。结果上线第二天就收到了告警,赶在连接池耗尽之前扩容了。这就是 WARN 的价值——给你一个「提前量」。
ERROR 级别
ERROR 表示「当前操作失败了,但系统还能继续跑」。比如一次 HTTP 请求返回 500、一条 SQL 执行超时。注意,ERROR 不意味着系统要挂了,它只是告诉你「这件事没办成」。我要求团队:每个 ERROR 日志必须附带足够的上下文——请求 ID、参数、异常堆栈。否则你看到一条 ERROR,根本不知道是哪个请求出的问题。
FATAL 级别
FATAL 是最严重的级别,表示「系统活不下去了」。比如配置文件解析失败、关键依赖服务不可用、内存溢出。遇到 FATAL,通常意味着进程即将退出。我一般只在 main 函数和核心初始化流程中使用 FATAL,其他地方用 ERROR 就够了。
避坑指南:我曾经接手过一个项目,代码里到处都是 logger.fatal("something wrong"),结果一查,只是某个接口返回了空数据。FATAL 用多了,真正致命的问题反而被忽略了。记住:FATAL 只留给「不重启解决不了」的问题。
1.4 日志级别的选择原则
说了这么多,到底该怎么选?我总结了一个简单的「三问法」:
- 这条日志对排查问题有帮助吗? —— 没有?删掉。有?继续。
- 这条日志在正常流程中会频繁出现吗? —— 会?用 DEBUG。不会?继续。
- 这条日志代表异常吗? —— 代表潜在风险?用 WARN。代表当前失败?用 ERROR。代表系统要挂了?用 FATAL。
说白了,日志级别的选择就是「信息密度」和「噪音」之间的平衡。你希望线上日志里每一条都有价值,而不是一堆垃圾信息里翻找那一条有用的。
1.5 本章知识体系
下面这张图把日志的基础概念串起来了。你可以把它当作一个「思维导图」来理解:
1.6 一个简单的日志示例
光说不练假把式。咱们看一段 Python 代码,感受一下不同级别的日志长什么样:
import logging
import time
# 配置日志格式
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s [%(levelname)s] %(name)s - %(message)s'
)
logger = logging.getLogger("order_service")
def create_order(user_id, product_id):
logger.info(f"开始创建订单,用户: {user_id}, 商品: {product_id}")
# DEBUG:记录详细参数
logger.debug(f"请求参数详情: user_id={user_id}, product_id={product_id}")
# 模拟库存检查
stock = check_stock(product_id)
if stock < 1:
logger.warn(f"商品 {product_id} 库存不足,当前库存: {stock}")
return False
# 模拟数据库写入
try:
save_order(user_id, product_id)
logger.info(f"订单创建成功,用户: {user_id}, 商品: {product_id}")
except Exception as e:
logger.error(f"订单创建失败,用户: {user_id}, 商品: {product_id}, 错误: {str(e)}")
return False
return True
def check_stock(product_id):
# 模拟库存查询
logger.debug(f"查询商品 {product_id} 库存")
return 5
def save_order(user_id, product_id):
# 模拟数据库操作
logger.debug(f"写入订单表: user_id={user_id}, product_id={product_id}")
time.sleep(0.1)
# 模拟成功
pass
# 运行
create_order("U001", "P1001")
运行这段代码,你会看到类似这样的输出:
2025-01-15 14:23:45,123 [INFO] order_service - 开始创建订单,用户: U001, 商品: P1001
2025-01-15 14:23:45,123 [DEBUG] order_service - 请求参数详情: user_id=U001, product_id=P1001
2025-01-15 14:23:45,123 [DEBUG] order_service - 查询商品 P1001 库存
2025-01-15 14:23:45,124 [DEBUG] order_service - 写入订单表: user_id=U001, product_id=P1001
2025-01-15 14:23:45,225 [INFO] order_service - 订单创建成功,用户: U001, 商品: P1001
你看,INFO 日志勾勒了流程骨架,DEBUG 日志填充了细节。如果线上出了问题,先看 INFO 知道哪一步失败了,再决定要不要开 DEBUG 看细节。这就是级别的妙用。
一个小建议:日志格式里一定要带上时间戳和级别。我见过有人只打消息不打级别,排查问题时根本分不清是 INFO 还是 ERROR,只能逐行看代码。嗯,别干这种事。