Go标准库log包不支持日志级别,必须通过封装多个*log.Logger实例(如InfoLog、ErrorLog)实现轻量分级,各自配置前缀、输出目标和flags,且log.SetFlags/SetPrefix对log.New创建的实例无效。

Go 标准库 log 包压根不支持日志级别——调用 log.Printf 或 log.Println 永远只是“一条输出”,没有 Debug/Info/Warn 的语义区分,也无法按级别过滤或关闭。想靠 log.SetFlags、log.SetPrefix 或重写 log.Writer 实现分级,纯属白费劲。
标准 log 包怎么“假装”有级别(仅限小工具)
如果你不能引入第三方库(比如是嵌入式 CLI 工具、临时脚本),只能用标准库,那唯一可行的轻量做法是:为每个级别创建独立的 *log.Logger 实例。
- 每个实例可设不同前缀(如
"[INFO] ")、不同输出目标(os.Stdoutvsos.Stderr)、不同 flags(如log.Lshortfile) -
log.SetFlags和log.SetPrefix只影响默认 logger,对log.New创建的实例完全无效——必须在log.New的第三个参数里传 flags - 错误日志建议固定输出到
os.Stderr,避免被管道(| grep)意外截断 - 多个 logger 共享同一个
*os.File是安全的,*log.Logger本身已做并发保护
示例:
var (
InfoLog = log.New(os.Stdout, "[INFO] ", log.LstdFlags|log.Lshortfile)
WarnLog = log.New(os.Stderr, "[WARN] ", log.LstdFlags|log.Lshortfile)
ErrorLog = log.New(os.Stderr, "[ERROR] ", log.LstdFlags|log.Lshortfile)
)
InfoLog.Println("config loaded")
ErrorLog.Printf("connect timeout: %v", err)
zap.NewDevelopment() 和 zap.NewProduction() 别混用
两者行为差异极大,不是“换个配置就行”,而是设计目标完全不同:
-
zap.NewDevelopment():默认DebugLevel、彩色输出、带行号和调用栈、文本格式;适合本地调试,但上线后会拖慢性能、污染日志解析 -
zap.NewProduction():默认只输出InfoLevel及以上、禁用颜色、字段名压缩(如"level"→"l")、强制同步刷盘;JSON 字段名默认用zapcore.CapitalLevelEncoder,输出的是"LEVEL":"INFO",不是小写 - 别在代码里直接改全局 logger 级别——要用
logger.WithOptions(zap.IncreaseLevel(zap.DebugLevel))临时提级 - 自定义
zapcore.Core时,记得显式传zapcore.AddSync(os.Stdout),否则NewCore不会自动接 stdout
slog.HandlerOptions.Level 是阈值,不是开关
Go 1.21+ 推荐的 slog 本身不拦截日志调用——slog.Debug() 一定会执行,只是 handler 可能丢弃它。这带来两个关键后果:
- 副作用逻辑(比如拼接大字符串、调用
time.Now().String())仍会发生,CPU 和内存开销照常;必须手动加判断:if slog.Default().Enabled(context.Background(), slog.LevelDebug) { ... } - 设置
&slog.HandlerOptions{Level: slog.LevelWarn}表示“只输出 Warn 及更严重(Error/Critical)”,不是“只允许 Warn” - 别把
slog和zap混用——它们的上下文注入、字段序列化、handler 生命周期管理模型不兼容 - 自定义 handler 时,
Handle方法开头必须检查r.Level,否则分级彻底失效
logrus.SetLevel() 在运行时调用是危险操作
logrus.SetLevel 直接写全局变量 logrus.level,不是原子操作。多个 goroutine 并发调用 logrus.SetLevel(logrus.DebugLevel) 和 logrus.SetLevel(logrus.WarnLevel),结果可能错乱,导致 Debug 日志突然涌出。
- 启动时一次设好最安全:比如
logrus.SetLevel(logrus.LevelFromString(os.Getenv("LOG_LEVEL"))) - 真需要运行时切换,必须自己包一层
sync.RWMutex,且调用logrus.StandardLogger().SetLevel替代裸调 - logrus 已停止维护,新项目别选它;遗留项目务必确认所有
SetLevel调用都在初始化阶段完成
真正麻烦的从来不是“怎么写 Debug 日志”,而是“怎么确保它在生产环境不执行、不分配内存、不触发副作用”。级别控制的本质是编译期裁剪或运行时快速跳过,不是字符串前缀匹配。选库前先问自己:要不要结构化字段?要不要 JSON?要不要运行时热更新?要不要异步缓冲?答案一旦明确,zap、zerolog 或 slog 的取舍就非常清晰了。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











