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/zap

3. 三种快速构建方法

zap提供了三个开箱即用的构造函数:

方法特点
zap.NewDevelopment()开发环境专用,输出格式易读,带颜色(默认),有行号,级别为Debug。最常用
zap.NewExample()示例用途,无时间戳和行号,输出JSON格式
zap.NewProduction()生产环境推荐,JSON格式,带时间戳和行号,级别为Info

示例:

go

logger, _ := zap.NewDevelopment() logger.Info("hello dev")

4. 基本使用

zap.Logger提供了DebugInfoWarnErrorPanicFatal等方法。

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.Stringzap.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 prefix

11. 日志双写(控制台 + 文件)

两种常见方式:

方式一:使用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 官方文档