元数据卡
- 前置知识:第6章·读懂栈调用
- 预计时间:15 分钟
- 完成标志:能在项目中配置日志库并写入日志文件
场景:程序半夜崩了
"你的程序昨天半夜崩了,"同事说。"但你睡着了——那你怎么知道发生了什么?"
你愣住了。"我可以在终端里开着程序?"
"那不是办法。"工坊主人摇头。"程序需要自己记录——像航海日志一样,每一件大事小情都写下来。哪怕没人看着,记录也在。"
真正的程序不会把错误只打到你终端上。它们把运行中的一切事件记到日志文件里。
为什么不用 System.out?
System.out.println("到了这里") 停——问题有三:
- 只输出到 stdout,不进日志文件,重启就丢
- 生产环境没人看 stdout,服务器控制台一闪而过
- 没有级别——重要错误和调试信息混在一起
规则:用日志库,永远不用 System.out / print() 来 Debug。
日志级别
标准级别从低到高——越低越详细:
| 级别 | 什么时候用 | 示例 |
|---|---|---|
| TRACE | 极端调试才开 | 循环的每一轮 |
| DEBUG | 开发阶段信息 | SQL 语句、API 参数 |
| INFO | 正常的里程碑事件 | 服务启动、任务完成 |
| WARN | 不致命但值得关注 | 配置缺失用默认值 |
| ERROR | 需要人工介入 | 数据库连不上、支付失败 |
开发时开 DEBUG,生产环境开 INFO,排查时临时把某包降到 DEBUG。
动手:加日志
python
# Python + loguru
# 安装: pip install loguru
# 运行: python3 app.py
from loguru import logger
def forge_item(item_id):
logger.info("开始锻造工件 #{}", item_id)
try:
result = heat_and_hammer(item_id)
logger.info("工件 #{} 锻造成功", item_id)
return result
except Exception as e:
logger.error("工件 #{} 锻造失败: {}", item_id, e)
raisejava
// Java + SLF4J / Logback
// pom.xml 加 spring-boot-starter-logging 依赖
// 运行: java -jar target/app.jar
private static final Logger log =
LoggerFactory.getLogger(ForgeService.class);
log.info("开始锻造工件 {}", id);
log.error("锻造失败", exception);javascript
// JavaScript + winston
// 安装: npm install winston
// 运行: node app.js
const logger = winston.createLogger({
level: 'info',
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'app.log' })
]
});
logger.info('开始锻造 #%s', itemId);看日志:grep 过滤
bash
grep "ERROR" application.log
grep "2026-06-23 14:" app.log | grep "ERROR"
grep "工件 #1024" app.log
tail -n 50 application.log # 最后50行
less application.log # 分页浏览为什么加日志比猜 bug 更值
出问题的时候,日志是你唯一的证人。关键路径(启动、请求入口、异常 catch、外部调用)上多花 5 秒写一行 logger.info(),比花半小时猜"到底发生了什么"划算得多。
本讲小结
- 用日志库替代
System.out.println——日志能写文件、分级、检索 - 日志级别:TRACE < DEBUG < INFO < WARN < ERROR
- 善用
grep、tail、less查看日志 - 关键路径加日志,出问题翻日志而不是翻代码
→ 下一步:写最小复现
你学会了读栈调用、加日志——但有的 bug 看不懂,需要请教别人。怎么问问题才高效?最小复现是你的终极武器。
→ 第6章:最小复现