日志与错误
包总览
| 包 | 核心类型/函数 | 用途 |
|---|
fmt | Print, Sprint, Errorf | 格式化 I/O |
log | Logger, Print, Fatal | 基础日志 |
log/slog | Logger, Info, Error, With | 结构化日志(Go 1.21+) |
errors | New, Is, As, Join, Unwrap | 错误处理(详见 03-错误处理) |
fmt 包
格式化动词
| 动词 | 类型 | 说明 | 示例 |
|---|
%v | 任意 | 默认格式 | {小徐 28} |
%+v | struct | 带字段名 | {Name:小徐 Age:28} |
%#v | 任意 | Go 语法表示 | main.User{Name:"小徐", Age:28} |
%T | 任意 | 类型名 | main.User |
%% | - | 字面 % | % |
%d | 整数 | 十进制 | 42 |
%b | 整数 | 二进制 | 101010 |
%o | 整数 | 八进制 | 52 |
%x | 整数 | 十六进制小写 | 2a |
%X | 整数 | 十六进制大写 | 2A |
%c | 整数 | Unicode 字符 | * |
%q | 字符串 | 带引号 | "hello" |
%s | 字符串 | 原样输出 | hello |
%f | 浮点 | 小数 | 3.140000 |
%e | 浮点 | 科学计数 | 3.140000e+00 |
%g | 浮点 | 紧凑表示 | 3.14 |
%t | bool | 布尔 | true |
%p | 指针 | 地址 | 0xc0000140a0 |
宽度与精度
fmt.Sprintf("%5d", 42) // " 42"(宽度5,右对齐)
fmt.Sprintf("%-5d|", 42) // "42 |"(左对齐)
fmt.Sprintf("%05d", 42) // "00042"(零填充)
fmt.Sprintf("%.2f", 3.14159) // "3.14"(精度2)
fmt.Sprintf("%10.2f", 3.14) // " 3.14"(宽度10精度2)
fmt.Sprintf("%-10.2f|", 3.14) // "3.14 |"
fmt 函数族
// Print 系列(输出到 stdout)
fmt.Print("hello") // 无换行
fmt.Println("hello") // 带换行
fmt.Printf("hello %s\n", name)
// Sprint 系列(返回字符串)
s := fmt.Sprint("hello", " ", "world")
s := fmt.Sprintln("hello", "world")
s := fmt.Sprintf("hello %s", name)
// Fprint 系列(写入 io.Writer)
fmt.Fprint(os.Stderr, "error")
fmt.Fprintln(file, "log line")
fmt.Fprintf(w, "status: %d", code)
// Errorf(创建格式化错误)
err := fmt.Errorf("user %s not found", id)
err := fmt.Errorf("query failed: %w", dbErr) // 包装错误
fmt.Scanner 接口
// 实现 Stringer 接口自定义 %v 输出
type Color int
func (c Color) String() string {
names := []string{"Red", "Green", "Blue"}
if int(c) < len(names) {
return names[c]
}
return "Unknown"
}
c := Color(1)
fmt.Printf("%v\n", c) // "Green"
log 包
import "log"
// 基本用法
log.Println("server started") // 2026/08/04 20:34:00 server started
log.Printf("user %s logged in", name)
log.Fatal("cannot start server") // 打印后 os.Exit(1)
log.Panic("unexpected state") // 打印后 panic
// 自定义 Logger
logger := log.New(os.Stdout, "APP: ", log.LstdFlags|log.Lshortfile)
logger.Println("custom log") // APP: 2026/08/04 20:34:00 main.go:10: custom log
// 日志标志
log.SetFlags(log.LstdFlags | log.Lshortfile | log.Lmicroseconds)
| 标志 | 说明 | 示例输出前缀 |
|---|
Ldate | 日期 | 2026/08/04 |
Ltime | 时间 | 20:34:00 |
Lmicroseconds | 微秒 | 20:34:00.123456 |
Llongfile | 完整文件路径 | /home/user/main.go:10 |
Lshortfile | 文件名+行号 | main.go:10 |
LUTC | 使用 UTC 时间 | - |
Lmsgprefix | 前缀在消息前 | APP: message |
写入文件
f, _ := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
defer f.Close()
log.SetOutput(f) // 全局日志输出到文件
log.Println("this goes to file")
// 同时输出到文件和控制台
io.MultiWriter(f, os.Stdout)
log.SetOutput(io.MultiWriter(f, os.Stdout))
log/slog 包(Go 1.21+)
slog 是 Go 1.21 引入的结构化日志库,支持 JSON/文本输出,是官方推荐的生产级日志方案。
基本用法
import "log/slog"
// 默认 logger(文本格式)
slog.Info("server started", "port", 8080, "env", "production")
// 2026/08/04 20:34:00 INFO server started port=8080 env=production
// 默认 logger(JSON 格式)
slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, nil)))
slog.Info("server started", "port", 8080)
// {"time":"2026-08-04T20:34:00","level":"INFO","msg":"server started","port":8080}
// 日志级别
slog.Debug("debug message", "key", "value") // 默认不输出
slog.Info("info message")
slog.Warn("warning message")
slog.Error("error message", "err", err)
配置 Handler
// JSON Handler 配置
handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug, // 最低级别
AddSource: true, // 添加源文件信息
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
// 自定义属性
if a.Key == slog.TimeKey {
a.Key = "@timestamp"
}
return a
},
})
logger := slog.New(handler)
slog.SetDefault(logger)
带上下文的日志
// With:添加固定字段(类似 logger.WithField)
logger := slog.With("service", "care-mate", "version", "1.0.0")
logger.Info("request received", "method", "GET", "path", "/api/users")
// {"time":"...","level":"INFO","msg":"request received","service":"care-mate","version":"1.0.0","method":"GET","path":"/api/users"}
// 从 context 提取 trace ID
func LoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
traceID := r.Header.Get("X-Trace-ID")
logger := slog.With("trace_id", traceID)
ctx := context.WithValue(r.Context(), "logger", logger)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// 使用 context 中的 logger
func handler(w http.ResponseWriter, r *http.Request) {
logger := r.Context().Value("logger").(*slog.Logger)
logger.Info("processing request", "user_id", 123)
}
slog.Attr 与类型安全
// 使用 slog.Attr 明确指定类型
slog.Info("user created",
slog.Int("user_id", 123),
slog.String("name", "小徐"),
slog.Duration("processing_time", elapsed),
slog.Any("metadata", user.Metadata),
)
// 分组
slog.Info("request",
slog.Group("user",
slog.Int("id", 123),
slog.String("name", "小徐"),
),
slog.Group("response",
slog.Int("status", 200),
slog.Int("size", 1024),
),
)
// {"msg":"request","user":{"id":123,"name":"小徐"},"response":{"status":200,"size":1024}}
自定义 LogValuer
type User struct {
ID int
Name string
}
// 实现 LogValuer 接口,自定义日志输出
func (u User) LogValue() slog.Value {
return slog.GroupValue(
slog.Int("id", u.ID),
slog.String("name", u.Name),
)
}
user := User{ID: 1, Name: "小徐"}
slog.Info("user action", "user", user)
// {"msg":"user action","user":{"id":1,"name":"小徐"}}
日志库对比
| 库 | 性能 | 结构化 | API 风格 | 推荐度 |
|---|
| log/slog | 高 | ✓ JSON | 键值对 | ★★★★★(Go 1.21+首选) |
| zap | 最高 | ✓ JSON | zap.String("k", v) | ★★★★★ |
| zerolog | 最高 | ✓ JSON | 链式 | ★★★★☆ |
| logrus | 中 | ✓ | WithField | ★★★☆☆(维护模式) |
| log | 低 | ✗ | 字符串拼接 | ★★☆☆☆(仅简单场景) |
日志最佳实践
| 实践 | 说明 |
|---|
| ✅ 用结构化日志 | JSON 格式,方便 ELK 采集 |
| ✅ 日志包含上下文 | trace_id、user_id 等 |
| ✅ 错误日志记录堆栈 | debug.Stack() 或 slog.Any("stack", ...) |
| ✅ 不要在热路径打日志 | 高频循环中避免 Debug 日志 |
| ✅ 日志级别合理 | 生产环境 Info 级别 |
| ✅ 敏感信息不记录 | 密码、token、身份证号 |
| ✅ 日志轮转 | 用 lumberjack 或外部工具 |