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/15
  • log.Ltime:时间,如 14:30:22
  • log.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、耗时、状态码都记录到日志文件里。这是后面搭建完整日志系统的基础。