Go语言Zap日志库入门与实践:从基础到高级用法
Go语言Zap日志库入门与实践:从基础到高级用法
在Go语言开发中,日志是不可或缺的一部分。一个好的日志库能够帮助我们快速定位问题、监控系统状态。Uber开源的zap日志库以高性能、结构化和灵活性著称,成为Go社区最受欢迎的日志库之一。本文将带你从零开始,逐步掌握zap的核心功能,并最终实现一个生产级的日志配置——包含时间格式化、颜色美化、双写、按时间/级别分片等。
1. 为什么选择zap?
- 性能极佳:zap采用零内存分配的设计,性能远超标准库log和logrus。
- 结构化日志:支持强类型的字段,便于日志检索和分析。
- 灵活配置:可自定义编码器、输出目标、日志级别等。
- 两种API:提供高性能的
Logger和便捷的SugaredLogger,满足不同场景。
2. 安装
使用Go模块安装:
bash
go get -u go.uber.org/zap3. 三种快速构建方法
zap提供了三个开箱即用的构造函数:
| 方法 | 特点 |
|---|---|
zap.NewDevelopment() | 开发环境专用,输出格式易读,带颜色(默认),有行号,级别为Debug。最常用 |
zap.NewExample() | 示例用途,无时间戳和行号,输出JSON格式 |
zap.NewProduction() | 生产环境推荐,JSON格式,带时间戳和行号,级别为Info |
示例:
go
logger, _ := zap.NewDevelopment() logger.Info("hello dev")4. 基本使用
zap.Logger提供了Debug、Info、Warn、Error、Panic、Fatal等方法。
go
funcmain(){logger,_:=zap.NewDevelopment()logger.Debug("this is debug")logger.Info("this is info")logger.Warn("this is warn")logger.Error("this is error")logger.Panic("panic")// 会触发panic,后续不执行logger.Fatal("fatal")// 会调用os.Exit(1),后续不执行}输出效果:
5. 设置日志级别
通过配置可以调整日志级别,只输出≥该级别的日志。
go
funcmain(){cfg:=zap.NewDevelopmentConfig()cfg.Level=zap.NewAtomicLevelAt(zap.ErrorLevel)// 只输出Error及以上logger,_:=cfg.Build()logger.Debug("debug")// 不输出logger.Info("info")// 不输出logger.Warn("warn")// 不输出logger.Error("error")// 输出}实例结果:
级别大小关系:Debug < Info < Warn < Error < DPanic < Panic < Fatal
6. 时间格式化
默认时间格式是RFC3339,我们可以自定义为更易读的格式。
go
funcmain(){cfg:=zap.NewDevelopmentConfig()cfg.EncoderConfig.EncodeTime=zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05")logger,_:=cfg.Build()logger.Info("自定义时间格式")}// 输出:2026-07-10 15:04:05 INFO ... 自定义时间格式7. 标准日志 vs Sugar日志
- Logger:强类型,性能极致,需使用
zap.String、zap.Int等包装字段。 - SugaredLogger:更灵活,支持
Infof(格式化)和Infow(键值对),性能依然优秀。
两者可以轻松转换:
go
logger,_:=zap.NewDevelopment()// 标准用法logger.Info("hello",zap.String("name","tom"),zap.Int("age",20))// Sugar用法sugar:=logger.Sugar()sugar.Infof("hello %s","tom")sugar.Infow("hello","name","tom","age",20)8. 结构化日志(添加字段)
可以在日志消息后面附加任意键值对,方便后续查询。
go
logger.Info("this is a logger",zap.String("name","tom"),zap.Int("age",8),zap.Bool("isAnimal",true),)输出(JSON或console模式):
9. 输出美化(颜色区分级别)
在开发环境中,给不同级别加上颜色能提高可读性。我们可以自定义EncodeLevel。
go
const(Blue="\033[34m"Yellow="\033[33m"Red="\033[31m"Purple="\033[35m"Reset="\033[0m")funcencodeLevel(level zapcore.Level,enc zapcore.PrimitiveArrayEncoder){switchlevel{casezapcore.DebugLevel:enc.AppendString(Blue+"DEBUG"+Reset)casezapcore.InfoLevel:enc.AppendString(Yellow+"INFO"+Reset)casezapcore.WarnLevel:enc.AppendString(Purple+"WARN"+Reset)casezapcore.ErrorLevel,zapcore.DPanicLevel,zapcore.PanicLevel,zapcore.FatalLevel:enc.AppendString(Red+"ERROR"+Reset)default:enc.AppendString(level.String())}}funcmain(){cfg:=zap.NewDevelopmentConfig()cfg.EncoderConfig.EncodeLevel=encodeLevel logger,_:=cfg.Build()logger.Info("info in yellow")logger.Error("error in red")}10. 添加日志前缀(自定义Encoder)
有时候我们希望每条日志都带上应用名称或服务标识,可以通过包装Encoder实现。
go
typemyEncoderstruct{zapcore.Encoder}func(m myEncoder)EncodeEntry(entry zapcore.Entry,fields[]zapcore.Field)(*buffer.Buffer,error){buff,err:=m.Encoder.EncodeEntry(entry,fields)iferr!=nil{returnbuff,err}data:=buff.String()buff.Reset()buff.AppendString("[myApp] "+data)// 添加前缀returnbuff,nil}funcmain(){core:=zapcore.NewCore(myEncoder{Encoder:zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig())},os.Stdout,zapcore.InfoLevel,)logger:=zap.New(core,zap.AddCaller())logger.Info("with prefix")}// 输出:[myApp] 2026-07-10T... INFO ... with prefix11. 日志双写(控制台 + 文件)
两种常见方式:
方式一:使用zapcore.NewTee组合多个core
go
funcmain(){cfg:=zap.NewDevelopmentConfig()cfg.EncoderConfig.EncodeTime=zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05")consoleCore:=zapcore.NewCore(zapcore.NewConsoleEncoder(cfg.EncoderConfig),os.Stdout,zapcore.DebugLevel,)file,_:=os.OpenFile("app.log",os.O_CREATE|os.O_APPEND|os.O_WRONLY,0666)fileCore:=zapcore.NewCore(zapcore.NewConsoleEncoder(cfg.EncoderConfig),file,zapcore.DebugLevel,)teeCore:=zapcore.NewTee(consoleCore,fileCore)logger:=zap.New(teeCore,zap.AddCaller())logger.Info("both console and file")}方式二:使用zapcore.NewMultiWriteSyncer
go
funcmain(){cfg:=zap.NewDevelopmentConfig()cfg.EncoderConfig.EncodeTime=zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05")file,_:=os.OpenFile("app.log",os.O_CREATE|os.O_APPEND|os.O_WRONLY,0666)ws:=zapcore.NewMultiWriteSyncer(os.Stdout,file)core:=zapcore.NewCore(zapcore.NewConsoleEncoder(cfg.EncoderConfig),ws,zapcore.DebugLevel,)logger:=zap.New(core,zap.AddCaller())logger.Info("dual output")}12. 日志分片(按时间)
实际生产环境需要按天或按小时切割日志文件。我们可以自定义WriteSyncer,在写入时检查日期,若变化则新建文件。
go
typemyWriterstruct{file*os.File mutex sync.Mutex currentDatestring}func(w*myWriter)Write(p[]byte)(nint,errerror){w.mutex.Lock()deferw.mutex.Unlock()now:=time.Now().Format("2006-01-02")ifw.currentDate!=now{ifw.file!=nil{w.file.Close()}name:=fmt.Sprintf("logs/%s.log",now)os.MkdirAll("logs",0755)file,err:=os.OpenFile(name,os.O_CREATE|os.O_APPEND|os.O_WRONLY,0666)iferr!=nil{return0,err}w.file=file w.currentDate=now}returnw.file.Write(p)}funcmain(){cfg:=zap.NewDevelopmentConfig()cfg.EncoderConfig.EncodeTime=zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05")ws:=zapcore.NewMultiWriteSyncer(os.Stdout,zapcore.AddSync(&myWriter{}))core:=zapcore.NewCore(zapcore.NewConsoleEncoder(cfg.EncoderConfig),ws,zapcore.DebugLevel,)logger:=zap.New(core,zap.AddCaller())fori:=0;i<10;i++{logger.Sugar().Infof("log %d",i)time.Sleep(time.Second)// 模拟跨天}}13. 按日志级别分片
例如,将Error级别的日志单独写入err.log。
go
typelevelEncoderstruct{zapcore.Encoder errFile*os.File}func(e levelEncoder)EncodeEntry(entry zapcore.Entry,fields[]zapcore.Field)(*buffer.Buffer,error){buf,err:=e.Encoder.EncodeEntry(entry,fields)iferr!=nil{returnbuf,err}ifentry.Level==zapcore.ErrorLevel{ife.errFile==nil{e.errFile,_=os.OpenFile("err.log",os.O_CREATE|os.O_APPEND|os.O_WRONLY,0666)}e.errFile.WriteString(buf.String())}returnbuf,nil}funcmain(){core:=zapcore.NewCore(levelEncoder{Encoder:zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig())},os.Stdout,zapcore.InfoLevel,)logger:=zap.New(core,zap.AddCaller())logger.Info("info")logger.Error("error")// 会额外写入 err.log}14. 综合应用:生产级日志封装
将上述所有功能整合:时间格式化、颜色美化、预设前缀、双写(控制台+文件)、按时间分片、错误日志单独分片,并使用全局日志。
go
packagemainimport("fmt""os""sync""time""go.uber.org/zap""go.uber.org/zap/buffer""go.uber.org/zap/zapcore")// 自定义 Encoder,实现前缀、按时间分片、错误单独分片typelogEncoderstruct{zapcore.Encoder errFile*os.File outFile*os.File currentDatestringmu sync.Mutex}const(Blue="\033[0;34m"Yellow="\033[0;33m"Red="\033[0;31m"Reset="\033[0m")func(e*logEncoder)EncodeEntry(entry zapcore.Entry,fields[]zapcore.Field)(*buffer.Buffer,error){// 调用原始编码buf,err:=e.Encoder.EncodeEntry(entry,fields)iferr!=nil{returnbuf,err}// 添加前缀data:=buf.String()buf.Reset()buf.AppendString("[MyApp] "+data)data=buf.String()e.mu.Lock()defere.mu.Unlock()now:=time.Now().Format("2006-01-02")ife.currentDate!=now{// 创建日期目录dir:=fmt.Sprintf("logs/%s",now)os.MkdirAll(dir,0755)// 常规日志文件outName:=fmt.Sprintf("%s/out.log",dir)outFile,_:=os.OpenFile(outName,os.O_CREATE|os.O_APPEND|os.O_WRONLY,0666)e.outFile=outFile// 错误日志文件(单独)errName:=fmt.Sprintf("%s/err.log",dir)errFile,_:=os.OpenFile(errName,os.O_CREATE|os.O_APPEND|os.O_WRONLY,0666)e.errFile=errFile e.currentDate=now}// 写入常规文件ife.outFile!=nil{e.outFile.WriteString(data)}// 如果是 Error 级别,额外写入 errFileifentry.Level==zapcore.ErrorLevel&&e.errFile!=nil{e.errFile.WriteString(data)}returnbuf,nil}// 自定义级别颜色funcencodeLevel(level zapcore.Level,enc zapcore.PrimitiveArrayEncoder){switchlevel{casezapcore.InfoLevel:enc.AppendString(Blue+"INFO"+Reset)casezapcore.WarnLevel:enc.AppendString(Yellow+"WARN"+Reset)casezapcore.ErrorLevel,zapcore.DPanicLevel,zapcore.PanicLevel,zapcore.FatalLevel:enc.AppendString(Red+"ERROR"+Reset)default:enc.AppendString(level.String())}}// 初始化全局 loggerfuncInitLogger()*zap.Logger{cfg:=zap.NewDevelopmentConfig()cfg.EncoderConfig.EncodeTime=zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05")cfg.EncoderConfig.EncodeLevel=encodeLevel encoder:=&logEncoder{Encoder:zapcore.NewConsoleEncoder(cfg.EncoderConfig),}core:=zapcore.NewCore(encoder,os.Stdout,// 同时输出到控制台(由WriteSyncer决定,这里我们只把控制台作为输出,但我们在Encoder里已经写了文件,所以控制台也输出)zapcore.InfoLevel,)logger:=zap.New(core,zap.AddCaller())zap.ReplaceGlobals(logger)// 替换全局 loggerreturnlogger}funcmain(){InitLogger()// 使用全局 loggerzap.L().Info("this is info")zap.L().Warn("this is warn")zap.L().Error("this is error")// 也可以使用 Sugaredzap.S().Infof("sugar %s","example")}这个封装已经可以满足多数生产需求:每天自动创建文件夹,out.log记录所有日志,err.log单独记录错误,控制台输出彩色,同时带有前缀和时间格式化。
15. 总结
通过本文的学习,你已经掌握了zap日志库的常用功能:
- 快速上手(Development/Production)
- 日志级别控制
- 时间格式化
- 结构化日志
- 颜色美化
- 自定义前缀
- 双写(控制台+文件)
- 按时间、按级别分片
- 封装全局日志
Zap的灵活性极高,你可以基于这些基础实现更复杂的定制,例如输出JSON格式、接入ELK、动态调整级别等。希望本文能帮助你在项目中更好地使用zap,让日志成为你排查问题的得力助手。
参考资料:
- Zap GitHub
- Zap 官方文档