1. 日志基础认知:为什么需要日志、日志级别定义、Go标准库log包初探
1.1 为什么需要日志?
说实话,我刚入行那会儿,也觉得日志这东西可有可无。代码能跑就行,干嘛还要费劲写日志?直到有一次线上事故,让我彻底改变了看法。
那次是一个支付服务,用户反馈说钱扣了但订单没生成。我登录服务器,翻遍了所有输出,愣是找不到任何线索。没有日志,就像在黑夜里摸路,你根本不知道程序到底经历了什么。
日志的核心价值,我总结为三点:
- 问题排查:程序出错了,日志就是你的第一手证据。它告诉你「在什么时间、什么位置、发生了什么」。
- 行为审计:谁调用了什么接口?参数是什么?返回了什么?这些信息对安全审计和业务分析至关重要。
- 性能监控:通过日志里的耗时数据,你可以快速定位慢查询、慢接口,找到系统的瓶颈。
核心观点:没有日志的系统,就像没有黑匣子的飞机。一旦出事,你连调查的方向都没有。
1.2 日志级别定义
日志级别这东西,说白了就是给日志分个「轻重缓急」。你想想看,如果所有信息都一股脑儿输出,那关键信息反而被淹没了。
我个人习惯用这五个级别,也是业界最通用的标准:
| 级别 | 数值 | 含义 | 使用场景 |
|---|---|---|---|
| DEBUG | 0 | 调试信息 | 开发阶段,用于跟踪变量值、函数调用流程 |
| INFO | 1 | 普通信息 | 记录系统正常运行状态,如服务启动、请求到达 |
| WARN | 2 | 警告信息 | 潜在问题,但不影响当前功能,如配置即将过期 |
| ERROR | 3 | 错误信息 | 功能不可用,需要立即关注,如数据库连接失败 |
| FATAL | 4 | 致命错误 | 程序无法继续运行,如端口被占用、配置文件缺失 |
小技巧:生产环境一般只开启 INFO 及以上级别。DEBUG 日志量太大,会拖慢性能。我见过有人把 DEBUG 开到线上,结果磁盘一天就写满了……
1.3 Go标准库log包初探
Go 自带的 log 包,虽然功能简单,但胜在开箱即用。咱们先看看它长什么样。
1.3.1 基本用法
package main
import (
"log"
)
func main() {
log.Println("这是一条普通日志")
log.Printf("用户 %s 登录成功", "张三")
log.Fatalln("致命错误,程序退出")
}
运行后你会看到类似这样的输出:
2025/01/15 14:30:22 这是一条普通日志
2025/01/15 14:30:22 用户 张三 登录成功
2025/01/15 14:30:22 致命错误,程序退出
嗯,这里要注意:log.Fatalln 会在输出日志后调用 os.Exit(1),程序直接终止。所以别在业务逻辑里随便用,我一般只在初始化阶段用。
1.3.2 自定义日志格式
标准库的日志默认只有时间戳,说实话有点简陋。你可以通过 log.SetFlags 来定制:
func main() {
// 添加文件名和行号
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
log.Println("这条日志会显示文件名和行号")
}
输出效果:
2025/01/15 14:30:22 main.go:10: 这条日志会显示文件名和行号
常用的 flag 有这些:
log.Ldate:日期,如 2025/01/15log.Ltime:时间,如 14:30:22log.Lmicroseconds:微秒级时间log.Lshortfile:短文件名+行号log.Llongfile:完整文件路径+行号
避坑指南:我曾经在生产环境用了 log.Llongfile,结果日志文件里全是长长的路径,占空间不说,还影响阅读。建议开发环境用 Llongfile,生产环境用 Lshortfile 就够了。
1.3.3 输出到文件
默认情况下,日志输出到控制台。但生产环境肯定要写到文件里:
func main() {
// 打开日志文件
f, err := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
log.Fatal("无法打开日志文件:", err)
}
defer f.Close()
// 设置输出目标
log.SetOutput(f)
log.Println("这条日志会写入文件")
}
这里有个细节:os.O_APPEND 表示追加写入,不会覆盖之前的日志。我刚开始写 Go 时忘了加这个,结果每次重启服务,之前的日志全没了……
1.3.4 标准库的局限性
说实话,标准库的 log 包只能算「能用」,离「好用」还有距离。我总结了几点不足:
- 没有日志级别:你没法区分 DEBUG 和 ERROR,所有日志都一个样
- 不支持结构化:输出的是纯文本,没法用 JSON 格式,不利于日志收集系统解析
- 性能一般:每次写日志都会加锁,高并发场景下会成为瓶颈
- 不支持异步:日志是同步写入的,如果磁盘 I/O 慢,会拖慢业务逻辑
我的建议:小项目、工具脚本可以用标准库。但如果是生产级的后端服务,还是建议用第三方日志库,比如 zap、logrus。后面的章节我会详细讲这些库的用法和优化技巧。
1.4 小结
这一章咱们聊了日志的重要性、日志级别的定义,以及 Go 标准库 log 包的基本用法。你想想看,日志就像程序的「黑匣子」,平时可能用不上,但一旦出问题,它就是救命稻草。
下一章我会带你深入 log 包的源码,看看它内部是怎么实现的。理解了底层原理,你才能更好地选择和使用第三方日志库。
课后练习:试着用标准库写一个简单的 HTTP 服务器,把每次请求的 URL、耗时、状态码都记录到日志文件里。这是后面搭建完整日志系统的基础。