Go标准库log仅支持前缀开关和输出重定向,不支持结构化日志;需用zap等专用库实现JSON、level等字段化功能。
Go 标准库 log 包本身不支持自定义字段(如 JSON、level 字段),但能通过 log.SetFlags() 控制时间、文件名、行号等前缀开关。默认只输出内容,没时间戳——这是新手最常忽略的“日志看起来没格式”的原因。
log.Ldate | log.Ltime:输出类似 2025/05/12 14:23:56 message
log.Lshortfile:显示 main.go:23;log.Llongfile 显示完整路径log.SetFlags(log.Lda
te | log.Ltime | log.Lshortfile)
0 可完全禁用所有前缀,只留原始字符串默认输出到 os.Stderr,但容易和错误流混在一起。生产环境通常要写入文件,或区分 info/warn/error 流。注意:直接传 *os.File 是安全的,但别传已关闭的文件句柄,否则后续 log.Print() 会 panic 并报 write /dev/stderr: bad file descriptor。
log.SetOutput(newFile),记得用 os.OpenFile(..., os.O_CREATE|os.O_APPEND|os.O_WRONLY)
io.MultiWriter
SetOutput —— 它不是并发安全的,且会覆盖全局实例当你需要 {"level":"info","msg":"user login","uid":1001} 这种格式时,log 包无能为力。它不解析内容,只拼接字符串前缀 + fmt.Sprint() 结果。强行用 log.Printf("{\"level\":\"info\",...}") 会导致转义混乱、无法统一字段顺序、缺失堆栈上下文。
zap(高性能)、zerolog(零分配)、logrus(易上手)log.Print(),但无法享受结构化解析优势log.SetPrefix() 只加固定前缀(如 [INFO]),不能动态插入变量package main
import (
"log"
"os"
)
func main() {
// 启用日期、时间、短文件名
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
// 写入文件而非 stderr
f, _ := os.OpenFile("app.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
log.SetOutput(f)
defer f.Close()
log.Println("server started")
log.Printf("listening on %s", ":8080")
}
标准库 log 的格式能力仅限于前缀开关和输出目标,真要字段化、分级、采样、Hook,得换专用日志库——这点在项目初期就该明确,别等到日志查不出问题才回头重构。