学会使用golang zap日志库
日志
- 一、日志是什么
- 二、 Sugared Logger vs Logger
- 三、 zap的基本配置
- zap配置包含哪几方面
- 四、 自定义logger
- core
- options
- 五、 使用Lumberjack进行日志切割归档
一、日志是什么
首先需要明确什么是日志
在程序运行的过程中,我们不可能总是能在控制台看到全部信息
因为
-
程序是黑盒运行的
一旦程序启动,内部状态、变量值、执行路径都是“看不到的”,除非你在特定位置打日志或调试
-
生产环境不能用调试器(debug)
在开发环境你可以断点调试,但上线后的服务不能暂停调试,只能通过日志了解程序的行为
-
问题多是间歇性或难复现的
比如用户在某个特定时间点请求超时,过一会又正常,这类问题无法稳定复现,只有日志能还原问题。
-
多人协作/远程运维需要可追溯
如果有运维、测试、后端、前端一起协作,只靠“肉眼”观察或靠猜是远远不够的
这时我们要想知道程序运行过程中更多的信息,就需要用到日志,给出日志的概念
Go语言中的日志(logging),是指在程序运行过程中记录信息的机制,通常用于追踪程序执行流程、调试问题、记录错误、监控运行状态等
说白了,日志就是记录把程序运行时关键的信息记录下来(作用是 调试程序、监控服务、追溯时间、异常报警)
举个例子,下面是一条比较完整的日志信息
2025/07/30 10:25:00 [ERROR] [user_handler.go:45] 登录失败:用户名不存在,
userID=1234, IP=192.168.1.10
这条日志告诉我们:
-
时间:2025/07/30 10:25:00
-
级别:ERROR(表示错误)
-
文件位置:user_handler.go 的第 45 行
-
内容:用户登录失败
-
附加信息:userID=1234,IP=192.168.1.10
所以总的来说,日志是你程序“说话”的方式,它尽可能多地还原程序在某一刻的状态、操作内容、上下文,帮助开发者和运维人员快速定位问题
已经知道了日志是什么,接下来就该了解如何使用日志了
二、 Sugared Logger vs Logger
zap提供了两种日志类型:Sugared Logger 和 Logger
-
在性能很好但不是很关键的上下文中,使用 SugaredLogger 。它比其他结构化日志记录包快4-10倍,并且支持结构化和printf风格的日志记录。
-
在每一微秒和每一次内存分配都很重要的上下文中,使用 Logger 。它甚至比 SugaredLogger 更快,内存分配次数也更少,但它只支持强类型的结构化日志记录。
这样说还是太吃理解了,能不能说人话!
举个例子,想象你在写日志,其实就是“说一句话”告诉别人发生了什么:
Logger
就像一个非常讲究的人,说话必须格式规范、用词准确。
SugaredLogger
就像你平时聊天,语法宽松,说什么都行,只要别人听懂。
-
zap.Logger(原生 logger)
🧱 特点:强类型、高性能、格式固定你必须这么说:
logger.Info("用户登录", zap.String("用户名", "alice"), zap.Int("ID", 1001))
就像你必须填一张表格,每一栏都得按格式来写,不允许乱来
优点:格式清晰、适合机器分析、性能高
缺点:写起来略麻烦,不像日常说话 -
*zap.SugaredLogger(带糖 logger)
🍬 特点:语法更轻松,像聊天一样记录日志你可以这样说:
sugar.Infof("用户 %s 登录成功,ID=%d", "alice", 1001) sugar.Infow("用户登录成功", "用户名", "alice", "ID", 1001)
就像你发微信说话,不拘小节,怎么说都行。
优点:写起来简单、省事
缺点:性能略低一点(但对大多数业务没影响)
开发阶段我们常用 SugaredLogger,
高性能系统或日志采集模块则更推荐用 Logger
如果你现在用的是 zap.NewProduction() 创建的 logger,只要加一行就可以用带糖版本:
sugar := logger.Sugar()
反过来,如果你用了 SugaredLogger,也可以通过 sugar.Desugar() 还原成 Logger
三、 zap的基本配置
前面提到了日志包含的几部分内容,这些内容是需要配置的
zap给我们提供了两个预设工厂函数zap.NewProduction() 和 zap.NewDevelopment() ,可以快速创建一个配置好的logger,当然了,如果不想用这两个函数,也可以自己配置一个logger,这里我们稍后再说
默认情况下,zap.NewProduction() 和 zap.NewDevelopment() 创建的 logger 都会把日志打印到终端(标准输出,也就是 os.Stdout)。具体表现如下:
-
zap.NewProduction()
输出格式是 JSON,适合生产环境日志采集和分析
默认日志级别是 Info 及以上
日志内容会打印到终端(控制台) -
zap.NewDevelopment()
输出格式是易读的控制台文本格式,适合开发调试
默认日志级别是 Debug,能打印更多详细信息
日志内容也打印到终端,方便开发时即时查看
它们的作用总结如下:
函数名 | 适用场景 | 日志格式 | 默认日志级别 | 额外特性 |
---|---|---|---|---|
zap.NewProduction() | 生产环境 | JSON 格式 | Info | 自动添加 caller 信息、stacktrace |
zap.NewDevelopment() | 开发调试 | 人类可读文本 | Debug | 更详细、更宽松、更适合调试 |
zap配置包含哪几方面
zap的基本配置,可以理解为一个日志要包含哪些方面的内容:
-
输出位置(Output Paths)
决定日志打印到哪:
- 终端(console)
- 文件(如 logs/app.log)
- 同时打印多个位置
-
日志级别(Level)
你可以控制哪些日志会被打印:
- debug:调试用,最详细
- info:普通日志(业务日志)
- warn:警告,不影响业务但需要注意
- error:错误日志(业务或系统异常)
- fatal:致命错误,程序会崩溃
你可以通过配置或代码动态设置级别,过滤不需要的日志。
-
编码方式(Encoding)
决定日志内容的格式:- json:结构化,适合线上,便于日志系统解析
- console:可读性强,适合本地开发调试
-
字段格式(EncoderConfig)
控制时间戳、日志级别、调用位置的格式,示例:zapcore.EncoderConfig{TimeKey: "time",LevelKey: "level",NameKey: "logger",CallerKey: "caller",MessageKey: "msg",StacktraceKey: "stacktrace",EncodeLevel: zapcore.CapitalColorLevelEncoder,EncodeTime: zapcore.ISO8601TimeEncoder,EncodeCaller: zapcore.ShortCallerEncoder, }
这个是非常重要的配置,决定你打印出来的日志长什么样。
-
是否打印调用位置(Caller)
打印日志时是否带上调用该日志的文件和行号:logger = logger.WithOptions(zap.AddCaller())
-
开发/生产环境区别
-
zap.NewDevelopment():
默认日志级别是 debug
编码是 console
更适合本地调试 -
zap.NewProduction():
默认日志级别是 info
编码是 json
默认输出文件和标准输出
-
四、 自定义logger
想自定义一个logger,需要用到函数zap.New()
zap.New() 是 zap 库中最底层、最核心的函数之一,用于创建一个 Logger 实例。你可以把它理解为 “构建一个完整日志器的工厂函数”。它的作用是把你配置好的日志核心(Core)和可选的日志选项(Options)组合在一起,生成一个真正能用的 Logger
函数签名
func New(core zapcore.Core, options ...Option) *Logger
参数解释:
参数 | 作用 |
---|---|
core | 必须;日志的核心组件,定义“日志要写到哪、写什么、写多少级别”。必须使用 zapcore.NewCore() 来创建。 |
options… | 可选;附加功能,用于增强 logger,比如是否记录调用行号、是否打印堆栈、默认字段等。 |
core
我们先看第一个参数core
core的类型是 zapcore.Core, 我们找到Core的源码,可以知道core是一个接口类型
zapCore.Core:
type Core interface {LevelEnablerWith([]Field) CoreCheck(Entry, *CheckedEntry) *CheckedEntryWrite(Entry, []Field) errorSync() error
}
core必须使用zapcore.NewCore() 来创建
func NewCore(enc Encoder, ws WriteSyncer, enab LevelEnabler) Core {return &ioCore{LevelEnabler: enab,enc: enc,out: ws,}
}
可以看到返回一个 *ioCore 类型的结构体
那么再看一下 ioCore 的源码
type ioCore struct {LevelEnablerenc Encoderout WriteSyncer
}
可以看到这个结构体由三部分组成,
-
encoder:日志内容格式化方式
决定了日志内容以什么格式输出
常用的 encoder 有:-
zapcore.NewConsoleEncoder(cfg):适合开发环境,输出人类能看懂的日志(例如 key=value 的形式)
-
zapcore.NewJSONEncoder(cfg):适合生产环境,输出 JSON 格式的结构化日志,方便 ELK 等系统采集
-
-
writeSyncer:写日志的地方
告诉 zap:日志要写到哪去,可以是:-
os.Stdout:控制台
-
文件:通过 lumberjack 实现日志切割
-
组合:zapcore.NewMultiWriteSyncer() 支持同时写多个地方
-
-
logLevel:最低日志级别
只有大于等于这个级别的日志才会被记录,比如:go语言中关于日志级别的定义:
const ( DebugLevel Level = iota - 1 InfoLevel WarnLevel ErrorLevel DPanicLevel PanicLevel FatalLevel_minLevel = DebugLevel _maxLevel = FatalLevelInvalidLevel = _maxLevel + 1
options
后面的参数是可选的,它们是一些 功能性增强选项,比如:
可选参数 | 含义 |
---|---|
zap.AddCaller() | 日志中加入调用的源码文件名和行号 |
zap.AddCallerSkip(n) | 调整调用栈深度(比如封装日志函数时常用) |
zap.AddStacktrace(lv) | 在某个级别及以上打印调用堆栈 |
zap.Fields(…) | 给 logger 添加默认的字段信息(结构化日志) |
zap.Development() | 启用开发模式(比如日志校验更严格) |
五、 使用Lumberjack进行日志切割归档
当服务产生日志很多,且希望控制文件大小/数量/时长时,就该用 Lumberjack 来切割归档日志
Lumberjack
是 Go 中一个轻量级的日志滚动库,常和 zap 搭配使用,用于日志切割与归档。这在生产环境中非常实用,能避免日志无限增长导致磁盘爆满
什么是 Lumberjack?
Lumberjack 是一个实现了 io.Writer 接口的日志文件滚动库
用一句话说:它可以根据文件大小、备份数量、保留天数等规则自动切割日志文件。
常用配置项
使用 *lumberjack.Logger 配置日志行为:
&lumberjack.Logger{Filename: "./logs/server.log", // 日志文件路径MaxSize: 100, // 每个日志文件最大尺寸(MB)MaxBackups: 5, // 最多保留的旧日志文件数量MaxAge: 30, // 最长保留时间(天)Compress: true, // 是否压缩归档旧日志
}
和 zap 结合使用
因为 zapcore.AddSync(io.Writer) 接收的是 io.Writer,而 lumberjack.Logger 实现了这个接口,所以可以无缝对接。
完整示例:
import ("go.uber.org/zap""go.uber.org/zap/zapcore""gopkg.in/natefinch/lumberjack.v2"
)func main() {// 1. 创建 lumberjack loggerwriteSyncer := zapcore.AddSync(&lumberjack.Logger{Filename: "./logs/server.log",MaxSize: 10, // 10 MBMaxBackups: 3, // 最多3个备份MaxAge: 7, // 保留7天Compress: true,})// 2. 设置编码器encoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())// 3. 创建 corecore := zapcore.NewCore(encoder, writeSyncer, zapcore.InfoLevel)// 4. 创建 loggerlogger := zap.New(core)defer logger.Sync()// 5. 打印日志logger.Info("这是一个日志条目", zap.String("user", "tinG"))
}
Lumberjack 的优势
优点 | 说明 |
---|---|
自动切割 | 避免单个日志文件过大 |
自动清理 | 根据时间或数量清理旧日志 |
自动压缩 | 节省磁盘空间 |
与 zap 无缝配合 | 不需要额外适配代码 |
补充:zap 自带不支持切割 zap 默认是将日志打印到文件或终端,但不自带日志切割能力