樱桃小晰瓜 發表於 2025-11-16 15:10:55

golang zap日志库的具体使用

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li><a href="#_label0">一、日志是什么</a></li><li><a href="#_label1">二、 Sugared Logger vs Logger</a></li><li><a href="#_label2">三、 zap的基本配置</a></li><ul class="second_class_ul"><li><a href="#_lab2_2_0">zap配置包含哪几方面</a></li></ul><li><a href="#_label3">四、 自定义logger</a></li><ul class="second_class_ul"><li><a href="#_lab2_3_1">core</a></li><li><a href="#_lab2_3_2">options</a></li></ul><li><a href="#_label4">五、 使用Lumberjack进行日志切割归档</a></li><ul class="second_class_ul"></ul></ul></div><p class="maodian"><a name="_label0"></a></p><h2>一、日志是什么</h2>
<p>首先需要明确什么是日志</p>
<p>在程序运行的过程中,我们不可能总是能在控制台看到全部信息</p>
<p><strong>因为</strong></p>
<ul><li><p><strong>程序是黑盒运行的</strong></p>
<p>一旦程序启动,内部状态、变量值、执行路径都是&ldquo;看不到的&rdquo;,除非你在特定位置打日志或调试</p></li><li><p><strong>生产环境不能用调试器(debug)</strong></p>
<p>在开发环境你可以断点调试,但上线后的服务不能暂停调试,只能通过日志了解程序的行为</p></li><li><p><strong>问题多是间歇性或难复现的</strong></p>
<p>比如用户在某个特定时间点请求超时,过一会又正常,这类问题无法稳定复现,只有日志能还原问题。</p></li><li><p><strong>多人协作/远程运维需要可追溯</strong></p>
<p>如果有运维、测试、后端、前端一起协作,只靠&ldquo;肉眼&rdquo;观察或靠猜是远远不够的</p></li></ul>
<p>这时我们要想知道<strong>程序运行过程中更多的信息</strong>,就需要用到<strong>日志</strong>,给出日志的概念</p>
<blockquote><p>Go语言中的<strong>日志(logging)</strong>,是指在程序运行过程中记录信息的机制,通常用于追踪程序执行流程、调试问题、记录错误、监控运行状态等</p></blockquote>
<p>说白了,日志就是记录把程序运行时关键的信息记录下来(作用是 <strong>调试程序、监控服务、追溯时间、异常报警</strong>)</p>
<p>举个例子,下面是一条比较完整的日志信息</p>
<blockquote><p>2025/07/30 10:25:00 登录失败:用户名不存在,<br />userID=1234, IP=192.168.1.10</p></blockquote>
<p>这条日志告诉我们:</p>
<ul><li><strong>时间</strong>:2025/07/30 10:25:00</li><li><strong>级别</strong>:ERROR(表示错误)</li><li><strong>文件位置</strong>:user_handler.go 的第 45 行</li><li><strong>内容</strong>:用户登录失败</li><li><strong>附加信息</strong>:userID=1234,IP=192.168.1.10</li></ul>
<p>所以总的来说,日志是你程序&ldquo;说话&rdquo;的方式,它尽可能多地还原程序在某一刻的状态、操作内容、上下文,帮助开发者和运维人员快速定位问题</p>
<p>已经知道了日志是什么,接下来就该了解如何使用日志了</p>
<p class="maodian"><a name="_label1"></a></p><h2>二、 Sugared Logger vs Logger</h2>
<p>zap提供了两种日志类型:<strong>Sugared Logger 和 Logger</strong></p>
<ul><li>在性能很好但不是很关键的上下文中,使用 <strong>SugaredLogger</strong> 。它比其他结构化日志记录包快4-10倍,并且支持结构化和printf风格的日志记录。</li><li>在每一微秒和每一次内存分配都很重要的上下文中,使用 <strong>Logger</strong> 。它甚至比 SugaredLogger 更快,内存分配次数也更少,但它只支持强类型的结构化日志记录。</li></ul>
<p>这样说还是太吃理解了,能不能说人话!</p>
<p>举个例子,想象你在写日志,其实就是&ldquo;说一句话&rdquo;告诉别人发生了什么:</p>
<p><code>Logger</code> 就像一个非常讲究的人,说话必须格式规范、用词准确。</p>
<p><code>SugaredLogger</code> 就像你平时聊天,语法宽松,说什么都行,只要别人听懂。</p>
<ol><li><p><strong>zap.Logger(原生 logger)</strong><br />🧱 特点:强类型、高性能、格式固定</p>
<p>你必须这么说:</p>
<div class="jb51code"><pre class="brush:go;">logger.Info("用户登录", zap.String("用户名", "alice"), zap.Int("ID", 1001))
</pre></div>
<p>就像你必须填一张表格,每一栏都得按格式来写,不允许乱来</p>
<p><strong>优点</strong>:格式清晰、适合机器分析、性能高<br /><strong>缺点</strong>:写起来略麻烦,不像日常说话</p></li><li><p>*zap.SugaredLogger(带糖 logger)<br />🍬 特点:语法更轻松,像聊天一样记录日志</p>
<p>你可以这样说:</p>
<div class="jb51code"><pre class="brush:go;">sugar.Infof("用户 %s 登录成功,ID=%d", "alice", 1001)
sugar.Infow("用户登录成功", "用户名", "alice", "ID", 1001)
</pre></div>
<p>就像你发微信说话,不拘小节,怎么说都行。</p>
<p><strong>优点</strong>:写起来简单、省事<br /><strong>缺点</strong>:性能略低一点(但对大多数业务没影响)</p></li></ol>
<p>开发阶段我们常用 SugaredLogger,<br />高性能系统或日志采集模块则更推荐用 Logger</p>
<p>如果你现在用的是 zap.NewProduction() 创建的 logger,只要加一行就可以用带糖版本:</p>
<div class="jb51code"><pre class="brush:go;">sugar := logger.Sugar()
</pre></div>
<p>反过来,如果你用了 SugaredLogger,也可以通过 sugar.Desugar() 还原成 Logger</p>
<p class="maodian"><a name="_label2"></a></p><h2>三、 zap的基本配置</h2>
<p>前面提到了日志包含的几部分内容,这些内容是需要配置的<br />zap给我们提供了两个预设工厂函数<strong>zap.NewProduction()</strong> 和 <strong>zap.NewDevelopment()</strong> ,可以快速创建一个配置好的logger,当然了,如果不想用这两个函数,也可以自己配置一个logger,这里我们稍后再说</p>
<p>默认情况下,zap.NewProduction() 和 zap.NewDevelopment() 创建的 logger 都会把日志打印到终端(标准输出,也就是 os.Stdout)。具体表现如下:</p>
<ul><li><p><strong>zap.NewProduction()</strong><br />输出格式是 JSON,适合生产环境日志采集和分析<br />默认日志级别是 Info 及以上<br />日志内容会打印到终端(控制台)</p></li><li><p><strong>zap.NewDevelopment()</strong><br />输出格式是易读的控制台文本格式,适合开发调试<br />默认日志级别是 Debug,能打印更多详细信息<br />日志内容也打印到终端,方便开发时即时查看</p></li></ul>
<p>它们的作用总结如下:</p>
<table><thead><tr><th>函数名</th><th>适用场景</th><th>日志格式</th><th>默认日志级别</th><th>额外特性</th></tr></thead><tbody><tr><td>zap.NewProduction()</td><td>生产环境</td><td>JSON 格式</td><td>Info</td><td>自动添加 caller 信息、stacktrace</td></tr><tr><td>zap.NewDevelopment()</td><td>开发调试</td><td>人类可读文本</td><td>Debug</td><td>更详细、更宽松、更适合调试</td></tr></tbody></table>
<p class="maodian"><a name="_lab2_2_0"></a></p><h3>zap配置包含哪几方面</h3>
<p><strong>zap的基本配置</strong>,可以理解为一个日志要包含哪些方面的内容:</p>
<ol><li><p><strong>输出位置</strong>(Output Paths)</p>
<p>决定日志打印到哪:</p>
<ul><li>终端(console)</li><li>文件(如 logs/app.log)</li><li>同时打印多个位置</li></ul></li><li><p><strong>日志级别</strong>(Level)<br />你可以控制哪些日志会被打印:</p></li></ol>
<ul><li><strong>debug</strong>:调试用,最详细</li><li><strong>info</strong>:普通日志(业务日志)</li><li><strong>warn</strong>:警告,不影响业务但需要注意</li><li><strong>error</strong>:错误日志(业务或系统异常)</li><li><strong>fatal</strong>:致命错误,程序会崩溃</li></ul>
<p>你可以通过配置或代码动态设置级别,过滤不需要的日志。</p>
<ol start="3"><li><p><strong>编码方式(Encoding)</strong><br />决定日志内容的格式:</p>
<ul><li><strong>json</strong>:结构化,适合线上,便于日志系统解析</li><li><strong>console</strong>:可读性强,适合本地开发调试</li></ul></li><li><p><strong>字段格式(EncoderConfig)</strong><br />控制时间戳、日志级别、调用位置的格式,示例:</p>
<div class="jb51code"><pre class="brush:go;">zapcore.EncoderConfig{
    TimeKey:      "time",
    LevelKey:       "level",
    NameKey:      "logger",
    CallerKey:      "caller",
    MessageKey:   "msg",
    StacktraceKey:"stacktrace",
    EncodeLevel:    zapcore.CapitalColorLevelEncoder,
    EncodeTime:   zapcore.ISO8601TimeEncoder,
    EncodeCaller:   zapcore.ShortCallerEncoder,
}
</pre></div>
<p>这个是非常重要的配置,决定你打印出来的日志长什么样。</p></li><li><p>是否打印调用位置(Caller)<br />打印日志时是否带上调用该日志的文件和行号:</p>
<div class="jb51code"><pre class="brush:go;">logger = logger.WithOptions(zap.AddCaller())
</pre></div></li><li><p>开发/生产环境区别</p>
<ul><li><p>zap.NewDevelopment():<br />默认日志级别是 debug<br />编码是 console<br />更适合本地调试</p></li><li><p>zap.NewProduction():<br />默认日志级别是 info<br />编码是 json<br />默认输出文件和标准输出</p></li></ul></li></ol>
<p class="maodian"><a name="_label3"></a></p><h2>四、 自定义logger</h2>
<p>想自定义一个logger,需要用到函数zap.New()</p>
<blockquote><p>zap.New() 是 zap 库中最底层、最核心的函数之一,用于创建一个 Logger 实例。你可以把它理解为 <strong>&ldquo;构建一个完整日志器的工厂函数&rdquo;</strong>。它的作用是把你配置好的日志核心(Core)和可选的日志选项(Options)组合在一起,生成一个真正能用的 Logger</p></blockquote>
<p>函数签名</p>
<div class="jb51code"><pre class="brush:go;">func New(core zapcore.Core, options ...Option) *Logger
</pre></div>
<p>参数解释:</p>
<table><thead><tr><th>参数</th><th>作用</th></tr></thead><tbody><tr><td><strong>core</strong></td><td>必须;日志的核心组件,定义&ldquo;日志要写到哪、写什么、写多少级别&rdquo;。必须使用 zapcore.NewCore() 来创建。</td></tr><tr><td><strong>options&hellip;</strong></td><td>可选;附加功能,用于增强 logger,比如是否记录调用行号、是否打印堆栈、默认字段等。</td></tr></tbody></table>
<p class="maodian"><a name="_lab2_3_1"></a></p><h3>core</h3>
<p>我们先看第一个参数core</p>
<p>core的类型是 zapcore.Core, 我们找到Core的源码,可以知道core是一个接口类型</p>
<p>zapCore.Core:</p>
<div class="jb51code"><pre class="brush:go;">type Core interface {
        LevelEnabler
        With([]Field) Core
        Check(Entry, *CheckedEntry) *CheckedEntry
        Write(Entry, []Field) error
        Sync() error
}
</pre></div>
<p>core必须使用<strong>zapcore.NewCore()</strong> 来创建</p>
<div class="jb51code"><pre class="brush:go;">func NewCore(enc Encoder, ws WriteSyncer, enab LevelEnabler) Core {
        return &amp;ioCore{
                LevelEnabler: enab,
                enc:          enc,
                out:          ws,
        }
}
</pre></div>
<p>可以看到返回一个 *ioCore 类型的结构体<br />那么再看一下 ioCore 的源码</p>
<div class="jb51code"><pre class="brush:go;">type ioCore struct {
        LevelEnabler
        enc Encoder
        out WriteSyncer
}
</pre></div>
<p>可以看到这个结构体由三部分组成,</p>
<p><strong>encoder</strong>:日志内容格式化方式<br />决定了日志内容<strong>以什么格式输出</strong><br />常用的 encoder 有:</p>
<ul><li><strong>zapcore.NewConsoleEncoder(cfg)</strong>:适合开发环境,输出人类能看懂的日志(例如 key=value 的形式)</li><li><strong>zapcore.NewJSONEncoder(cfg)</strong>:适合生产环境,输出 JSON 格式的结构化日志,方便 ELK 等系统采集</li></ul>
<p><strong>writeSyncer</strong>:写日志的地方<br />告诉 zap:日志要<strong>写到哪去</strong>,可以是:</p>
<ul><li><strong>os.Stdout</strong>:控制台</li><li><strong>文件</strong>:通过 lumberjack 实现日志切割</li><li><strong>组合</strong>:zapcore.NewMultiWriteSyncer() 支持同时写多个地方</li></ul>
<p><strong>logLevel</strong>:最低日志级别<br />只有<strong>大于等于这个级别的日志才会被记录</strong>,比如:</p>
<p>go语言中关于日志级别的定义:</p>
<div class="jb51code"><pre class="brush:go;">const (
DebugLevel Level = iota - 1
InfoLevel
WarnLevel
ErrorLevel
DPanicLevel
PanicLevel
FatalLevel

_minLevel = DebugLevel
_maxLevel = FatalLevel

InvalidLevel = _maxLevel + 1
</pre></div>
<p class="maodian"><a name="_lab2_3_2"></a></p><h3>options</h3>
<p>后面的参数是可选的,它们是一些 功能性增强选项,比如:</p>
<table><thead><tr><th>可选参数</th><th>含义</th></tr></thead><tbody><tr><td>zap.AddCaller()</td><td>日志中加入调用的源码文件名和行号</td></tr><tr><td>zap.AddCallerSkip(n)</td><td>调整调用栈深度(比如封装日志函数时常用)</td></tr><tr><td>zap.AddStacktrace(lv)</td><td>在某个级别及以上打印调用堆栈</td></tr><tr><td>zap.Fields(&hellip;)</td><td>给 logger 添加默认的字段信息(结构化日志)</td></tr><tr><td>zap.Development()</td><td>启用开发模式(比如日志校验更严格)</td></tr></tbody></table>
<p class="maodian"><a name="_label4"></a></p><h2>五、 使用Lumberjack进行日志切割归档</h2>
<p>当服务产生日志很多,且希望控制文件大小/数量/时长时,就该用 <strong>Lumberjack</strong> 来切割归档日志</p>
<p><code>Lumberjack</code> 是 Go 中一个轻量级的日志滚动库,常和 zap 搭配使用,用于<strong>日志切割与归档</strong>。这在生产环境中非常实用,能避免日志无限增长导致磁盘爆满</p>
<p><strong>什么是 Lumberjack?</strong><br />Lumberjack 是一个实现了 io.Writer 接口的日志文件滚动库<br />用一句话说:它可以根据文件大小、备份数量、保留天数等规则自动切割日志文件。</p>
<p><strong>常用配置项</strong><br />使用 *lumberjack.Logger 配置日志行为:</p>
<div class="jb51code"><pre class="brush:go;">&amp;lumberjack.Logger{
    Filename:   "./logs/server.log", // 日志文件路径
    MaxSize:    100,               // 每个日志文件最大尺寸(MB)
    MaxBackups: 5,                   // 最多保留的旧日志文件数量
    MaxAge:   30,                  // 最长保留时间(天)
    Compress:   true,                // 是否压缩归档旧日志
}
</pre></div>
<p><strong>和 zap 结合使用</strong><br />因为 zapcore.AddSync(io.Writer) 接收的是 io.Writer,而 lumberjack.Logger 实现了这个接口,所以可以无缝对接。</p>
<p>完整示例:</p>
<div class="jb51code"><pre class="brush:go;">import (
    "go.uber.org/zap"
    "go.uber.org/zap/zapcore"
    "gopkg.in/natefinch/lumberjack.v2"
)

func main() {
    // 1. 创建 lumberjack logger
    writeSyncer := zapcore.AddSync(&amp;lumberjack.Logger{
      Filename:   "./logs/server.log",
      MaxSize:    10,// 10 MB
      MaxBackups: 3,   // 最多3个备份
      MaxAge:   7,   // 保留7天
      Compress:   true,
    })

    // 2. 设置编码器
    encoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())

    // 3. 创建 core
    core := zapcore.NewCore(encoder, writeSyncer, zapcore.InfoLevel)

    // 4. 创建 logger
    logger := zap.New(core)
    defer logger.Sync()

    // 5. 打印日志
    logger.Info("这是一个日志条目", zap.String("user", "tinG"))
}
</pre></div>
<p><strong>Lumberjack 的优势</strong></p>
<table><thead><tr><th>优点</th><th>说明</th></tr></thead><tbody><tr><td>自动切割</td><td>避免单个日志文件过大</td></tr><tr><td>自动清理</td><td>根据时间或数量清理旧日志</td></tr><tr><td>自动压缩</td><td>节省磁盘空间</td></tr><tr><td>与 zap 无缝配合</td><td>不需要额外适配代码</td></tr></tbody></table>
<blockquote><p>补充:zap 自带不支持切割 zap 默认是将日志打印到文件或终端,但不自带日志切割能力</p></blockquote>
頁: [1]
查看完整版本: golang zap日志库的具体使用