李光磊 發表於 2025-11-17 10:35:35

GO语言zap日志库理解和使用方法示例

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li><a href="#_label0">1.&nbsp;zap日志库介绍</a></li><li><a href="#_label1">2.安装zap库</a></li><li><a href="#_label2">3.配置日志记录器</a></li><ul class="second_class_ul"><li><a href="#_lab2_2_0">3.1 Logger</a></li><li><a href="#_lab2_2_1">3.2 Sugared Logger</a></li></ul><li><a href="#_label3">4. 定制logger</a></li><ul class="second_class_ul"><li><a href="#_lab2_3_2">4.1&nbsp;将日志写入文件</a></li><li><a href="#_lab2_3_3">4.2 将JSON Encoder更改为普通的Log Encoder</a></li><li><a href="#_lab2_3_4">4.3 更改时间编码并添加调用者详细信息</a></li><li><a href="#_lab2_3_5">4.4 将日志输出到多个位置</a></li></ul><li><a href="#_label4">5.&nbsp;使用Lumberjack进行日志切割文档</a></li><ul class="second_class_ul"><li><a href="#_lab2_4_6">5.1 安装</a></li><li><a href="#_lab2_4_7">5.2&nbsp; zap logger种加入Lumberjack</a></li></ul><li><a href="#_label5">6.&nbsp;日志中间件实现</a></li><ul class="second_class_ul"></ul></ul></div><p class="maodian"><a name="_label0"></a></p><h2>1.&nbsp;zap日志库介绍</h2>
<p><span>目前我所了解的日志库有go内置的默认日志标准库,这个日志包简单,无需额外的依赖,无需安装,缺乏日志格式化能力,无法切割日志,无法日志分级,适合小项目,对日志功能要求不高的场景;</span></p>
<p><span>然后就是logrus第三方日志库,支持日志分级,支持结构化日志,支持钩子机制,但性能一般,适合不对性能要求的中小型项目;</span></p>
<p><span>最后就是zap日志库,相比于logrus有极致的性能,同样结构化日志,类型安全,灵活配置,但学习成本高,配置复杂,适合高性能要求的生产环境。</span></p>
<p class="maodian"><a name="_label1"></a></p><h2>2.安装zap库</h2>
<div class="jb51code"><pre class="brush:ps;">go get -u go.uber.org/zap</pre></div>
<p class="maodian"><a name="_label2"></a></p><h2>3.配置日志记录器</h2>
<p>Zap提供了两种类型的日志记录器:<strong>Suared Logger</strong>和<strong>Logger</strong>。</p>
<p>在性能很好但不是很关键&middot;1的上下文中,使用SugaredLogger。它比其他结构化日志记录包快4-10倍,并且支持结构化和printf风格的日志记录。</p>
<p>在每一微秒和每一次内存分配都很重要的上下文中,使用Logger。它比SugaredLogger更快,内存分配次数也更少,但它只支持强类型的结构化日志记录。</p>
<p class="maodian"><a name="_lab2_2_0"></a></p><h3>3.1 Logger</h3>
<blockquote><ul><li>通过调用<code>zap.NewProduction()</code>/<code>zap.NewDevelopment()</code>或者<code>zap.Example()</code>创建一个Logger。</li><li>上面的每一个函数都将创建一个logger。唯一的区别在于它将记录的信息不同。例如production logger默认记录调用函数信息、日期和时间等。</li><li>通过Logger调用Info/Error等。</li><li>默认情况下日志都会打印到应用程序的console界面。</li></ul></blockquote>
<div class="jb51code"><pre class="brush:go;">package main

import (
        "go.uber.org/zap"
        "net/http"
)

var logger *zap.Logger //声明全局的zap日志记录器变量

func main() {
        InitLogger()
        defer logger.Sync()                  //程序退出前刷新日志缓冲区,确保缓冲区中的信息刷新到输出
        simpleHttpGet("https://www.baidu.com") //自定义函数,用于发送HTTP请求
}

// 日志初始化函数
func InitLogger() {
        //创建生产环境级别的日志器
        //默认输出 JSON 格式的日志,包含时间戳、日志级别、调用位置等信息
        logger, _ = zap.NewProduction()
}

// HTTP请求函数
func simpleHttpGet(url string) {
        //发送HTTP GET请求
        //返回值是请求响应数据和错误信息
        resp, err := http.Get(url)
        if err != nil {
                //如果请求出错,记录错误日志
                logger.Error("http get error", //错误描述
                        zap.String("url", url), //记录错误的URL
                        zap.Error(err))         //记录错误的信息
        } else {
                //如果请求成功,记录信息日志
                logger.Info("success",
                        zap.String("statusCode", resp.Status), //响应状态码
                        zap.String("url", url))                //请求的URL
                err := resp.Body.Close() //关闭响应体
                if err != nil {
                        // 如果关闭响应体出错,记录错误日志
                        logger.Error("body read error")
                }
        }
}
</pre></div>
<p>这段代码是使用Go语言编写的简单HTTP请求客户端程序,主要功能是向指定的URL发送HTTP GET 请求,并使用zap日志库记录请求过程中的关键信息。</p>
<p class="maodian"><a name="_lab2_2_1"></a></p><h3>3.2 Sugared Logger</h3>
<p>大部分实现与Logger相同,唯一的区别是我们通过调用logger的Sugar()方法来获取一个SugaredLogger,然后使用SugaredLogger以printf格式记录语句。下面是用SugaredLogger日志记录器实现上述Logger记录器相同功能的代码:</p>
<div class="jb51code"><pre class="brush:go;">// 声明一个全局日志器变量
// SugaredLogger支持格式化字符串,使用时更接近打印日志习惯
// 比基础的 zap.Logger 更易用(但性能略低)
var sugarLogger *zap.SugaredLogger

func main() {
        InitLoger()            //初始化日志器
        defer sugarLogger.Sync() //程序退出前刷新日志缓冲区
        simpleHttpGet("https://www.baidu.com")
}
func InitLoger() {
        logger, _ := zap.NewProduction() //创建生产环境日志器(JSON格式,包含元数据)
        sugarLogger = logger.Sugar()   //转换为SugaredLogger,支持格式化日志
}
func simpleHttpGet(url string) {
        //记录调试日志:表示开始尝试发送请求
        sugarLogger.Debugf("Tring to GET %s", url)
        //发送HTTP请求
        resp, err := http.Get(url)
        if err != nil {
                sugarLogger.Errorf("Failed to GET %s: %s", url, err)
        } else {
                sugarLogger.Infof("Successfully GET %s", url)
                resp.Body.Close()
                //sugarLogger.Errorf("Failed to GET %s: %s", url, err)
                //后面不需要执行 resp.Body.Close(),核心原因是:当 http.Get(url)
                //返回错误时,resp 变量可能为 nil(空值),
                //此时调用 resp.Body.Close() 会导致程序 panic(崩溃)
        }
}</pre></div>
<p>在zap日志库中的Logger和SugaredLogger时基础与封装的关系,两者本质上共享一套日志核心功能,</p>
<p><code>logger</code>(<code>zap.Logger</code>)是 zap 的 <strong>基础核心</strong>,专注于性能和类型安全;<code>sugarLogger</code>(<code>zap.SugaredLogger</code>)是其 <strong>便捷封装</strong>,专注于开发效率。</p>
<p class="maodian"><a name="_label3"></a></p><h2>4. 定制logger</h2>
<p class="maodian"><a name="_lab2_3_2"></a></p><h3>4.1&nbsp;将日志写入文件</h3>
<p>我们将使用zap.New(...)方法来手动传递所有配置,而不是使用像zap.NewPriduction()这样的预置方法来创建logger</p>
<div class="jb51code"><pre class="brush:go;">func New(core zapcore.Core, options ...Option) *Logger</pre></div>
<p>zapcore.Core需要三个配置:Encoder,WriteSyncer,LogLevel。</p>
<p><strong>1. Encoder</strong>:编码器(如何写入日志)。我们将使用开箱即用的<code>NewJSONEncoder()</code>,并使用预先设置的<code>ProductionEncoderConfig()</code>。</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202511/2025111710310459.png" /></p>
<p>2.&nbsp;<strong>WriteSyncer</strong>:指定日志将写到哪里去。我们使用<code>zapcore.AddSync()</code>函数并且将打开的文件句柄传进去。</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202511/2025111710310460.png" /></p>
<p><strong>3. Log Level:</strong>哪种级别的日志将被写入</p>
<div class="jb51code"><pre class="brush:go;">// 声明一个全局日志器变量
// SugaredLogger支持格式化字符串,使用时更接近打印日志习惯
// 比基础的 zap.Logger 更易用(但性能略低)
var sugarLogger *zap.SugaredLogger

func main() {
        InitLoger()            //初始化日志器
        defer sugarLogger.Sync() //程序退出前刷新日志缓冲区
        simpleHttpGet("https://www.baidu.com")
}
func InitLoger() {
        writeSyncer := getLogWriter() //获取输出目标
        encoder := getEncoder()       //获取日志格式编码器(JSON格式)
        //创建日志核心组件,关联编码器,输出目标和日志级别
        core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)
        logger := zap.New(core)      //基于核心组件创建基础日志器
        sugarLogger = logger.Sugar() //转换为SugaredLogger,支持格式化日志
}
func getEncoder() zapcore.Encoder {
        // 使用生产环境的编码器配置,返回 JSON 格式编码器
        return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
}
func getLogWriter() zapcore.WriteSyncer {
        file, _ := os.Create("./zap.log")
        return zapcore.AddSync(file) //将文件转换为zap支持的同步写入器
}
func simpleHttpGet(url string) {
        //记录调试日志:表示开始尝试发送请求
        sugarLogger.Debugf("Tring to GET %s", url)
        //发送HTTP请求
        resp, err := http.Get(url)
        if err != nil {
                sugarLogger.Errorf("Failed to GET %s: %s", url, err)
        } else {
                sugarLogger.Infof("Successfully GET %s", url)
                resp.Body.Close()
                //sugarLogger.Errorf("Failed to GET %s: %s", url, err)
                //后面不需要执行 resp.Body.Close(),核心原因是:当 http.Get(url)
                //返回错误时,resp 变量可能为 nil(空值),
                //此时调用 resp.Body.Close() 会导致程序 panic(崩溃)
        }
}
</pre></div>
<p class="maodian"><a name="_lab2_3_3"></a></p><h3>4.2 将JSON Encoder更改为普通的Log Encoder</h3>
<p>我们希望将编码器从JSON Encoder更改为普通Encoder。为此,我们需要将<code>NewJSONEncoder()</code>更改为<code>NewConsoleEncoder()</code>。</p>
<div class="jb51code"><pre class="brush:go;">return zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())
</pre></div>
<p>然后打印结果(就不是JSON格式了):</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202511/2025111710310486.png" /></p>
<p class="maodian"><a name="_lab2_3_4"></a></p><h3>4.3 更改时间编码并添加调用者详细信息</h3>
<p>鉴于我们对配置所做的更改,有下面两个问题:</p>
<ul><li>时间是以非人类可读的方式展示,例如1.572161051846623e+09</li><li>调用方函数的详细信息没有显示在日志中</li></ul>
<p>我们要做的第一件事是覆盖默认的<code>ProductionConfig()</code>,并进行以下更改:</p>
<ul><li>修改时间编码器</li><li>在日志文件中使用大写字母记录日志级别</li></ul>
<div class="jb51code"><pre class="brush:go;">func getEncoder() zapcore.Encoder {
        encoderConfig := zap.NewProductionEncoderConfig()
        encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
        encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
        return zapcore.NewConsoleEncoder(encoderConfig)
}</pre></div>
<p>接下来,我们将修改zap logger代码,添加将调用函数信息记录到日志中的功能。为此,我们将在<code>zap.New(..)</code>函数中添加一个<code>Option</code>。</p>
<div class="jb51code"><pre class="brush:go;">logger := zap.New(core, zap.AddCaller())
</pre></div>
<p class="maodian"><a name="_lab2_3_5"></a></p><h3>4.4 将日志输出到多个位置</h3>
<div class="jb51code"><pre class="brush:go;">var sugarLogger *zap.SugaredLogger

func main() {
        InitLoger()            //初始化日志器
        defer sugarLogger.Sync() //程序退出前刷新日志缓冲区
        simpleHttpGet("https://www.baidu.com")
}
func InitLoger() {
        encoder := getEncoder()       //获取日志格式编码器(JSON格式)
        core := getLogWriter(encoder) //获取输出目标
        //创建日志核心组件,关联编码器,输出目标和日志级别
        //core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)
        logger := zap.New(core, zap.AddCaller()) //基于核心组件创建基础日志器
        sugarLogger = logger.Sugar()             //转换为SugaredLogger,支持格式化日志
}
func getEncoder() zapcore.Encoder {
        encoderConfig := zap.NewProductionEncoderConfig()
        encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
        encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
        return zapcore.NewJSONEncoder(encoderConfig)

}
func getLogWriter(encoder zapcore.Encoder) zapcore.Core {
        file01, _ := os.Create("./zap.log")
        c1 := zapcore.NewCore(encoder, zapcore.AddSync(file01), zapcore.DebugLevel)
        file02, _ := os.Create("./zap_error.log")
        c2 := zapcore.NewCore(encoder, zapcore.AddSync(file02), zapcore.ErrorLevel)
        return zapcore.NewTee(c1, c2)

}
func simpleHttpGet(url string) {
        //记录调试日志:表示开始尝试发送请求
        sugarLogger.Debugf("Tring to GET %s", url)
        //发送HTTP请求
        resp, err := http.Get(url)
        if err != nil {
                sugarLogger.Errorf("Failed to GET %s: %s", url, err)
        } else {
                sugarLogger.Infof("Successfully GET %s", url)
                resp.Body.Close()
                //sugarLogger.Errorf("Failed to GET %s: %s", url, err)
                //后面不需要执行 resp.Body.Close(),核心原因是:当 http.Get(url)
                //返回错误时,resp 变量可能为 nil(空值),
                //此时调用 resp.Body.Close() 会导致程序 panic(崩溃)
        }
}
</pre></div>
<p class="maodian"><a name="_label4"></a></p><h2>5.&nbsp;使用Lumberjack进行日志切割文档</h2>
<p class="maodian"><a name="_lab2_4_6"></a></p><h3>5.1 安装</h3>
<div class="jb51code"><pre class="brush:go;">go get gopkg.in/natefinch/lumberjack.v2</pre></div>
<p class="maodian"><a name="_lab2_4_7"></a></p><h3>5.2&nbsp; zap logger种加入Lumberjack</h3>
<p>要在zap中加入Lumberjack支持,我们需要修改<code>WriteSyncer</code>代码。我们将按照下面的代码修改<code>getLogWriter()</code>函数:</p>
<div class="jb51code"><pre class="brush:go;">var sugarLogger *zap.SugaredLogger

func main() {
        InitLoger()            //初始化日志器
        defer sugarLogger.Sync() //程序退出前刷新日志缓冲区
        simpleHttpGet("https://www.baidu.com")
}
func InitLoger() {
        writeSyncer := getLogWriter() //获取输出目标
        encoder := getEncoder()       //获取日志格式编码器(JSON格式)
        //创建日志核心组件,关联编码器,输出目标和日志级别
        core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)
        logger := zap.New(core)      //基于核心组件创建基础日志器
        sugarLogger = logger.Sugar() //转换为SugaredLogger,支持格式化日志
}
func getEncoder() zapcore.Encoder {
        // 使用生产环境的编码器配置,返回 JSON 格式编码器
        return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
}
func getLogWriter() zapcore.WriteSyncer {
        lumberjackLogger := &amp;lumberjack.Logger{
                Filename:   "./log/sugar.log",
                MaxSize:    10,
                MaxBackups: 10,
                MaxAge:   30,
                Compress:   true,
        }
        return zapcore.AddSync(lumberjackLogger) //将文件转换为zap支持的同步写入器
}
func simpleHttpGet(url string) {
        //记录调试日志:表示开始尝试发送请求
        sugarLogger.Debugf("Tring to GET %s", url)
        //发送HTTP请求
        resp, err := http.Get(url)
        if err != nil {
                sugarLogger.Errorf("Failed to GET %s: %s", url, err)
        } else {
                sugarLogger.Infof("Successfully GET %s", url)
                resp.Body.Close()
                //sugarLogger.Errorf("Failed to GET %s: %s", url, err)
                //后面不需要执行 resp.Body.Close(),核心原因是:当 http.Get(url)
                //返回错误时,resp 变量可能为 nil(空值),
                //此时调用 resp.Body.Close() 会导致程序 panic(崩溃)
        }
}
</pre></div>
<p class="maodian"><a name="_label5"></a></p><h2>6.&nbsp;日志中间件实现</h2>
<div class="jb51code"><pre class="brush:go;">// 声明一个日志记录器实例
var sugarLogger *zap.SugaredLogger

// 中间件函数
func Logger() gin.HandlerFunc {
        InitLogger()
        return func(c *gin.Context) {
                //在请求处理前,记录一条Debug级别的日志,包含请求的URL路径
                sugarLogger.Debugf("Trying to access logger %s", c.Request.URL.Path)
                c.Next()                        //执行后续中间件和路由处理函数
                statusCode := c.Writer.Status() //获取响应状态码
                if len(c.Errors) &gt; 0 {
                        sugarLogger.Error(c.Errors.String())
                }
                if statusCode &gt;= 400 {
                        sugarLogger.Warnf("客户端警告: %d", statusCode)
                } else if statusCode &gt;= 500 {
                        sugarLogger.Errorf("服务器错误: %d", statusCode)
                } else {
                        sugarLogger.Infof("请求正常: %d", statusCode)
                }

        }

}

// 初始化日志器
func InitLogger() {
        encoder := getEncoder()       //获取编码器(定义日志格式)
        writeSyncer := getLogWriter() //获取输出目标(日志写到哪里)
        //创建一个核心日志处理器,相当于日志处理引擎
        core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)
        //基于core创建Logger实例
        // zap.AddCaller()是可选配置,作用是让日志种自动包含调用日志的代码位置信息(即文件名和行号)
        logger := zap.New(core, zap.AddCaller())
        //从一个标准的zap.Logger实例创建并获取一个zap.SugaredLogger实例
        sugarLogger = logger.Sugar()
}

// 使用JSON格式的编码器,并采用开发环境的默认配置,包括日志级别和时间戳等字段
func getEncoder() zapcore.Encoder {
        config := zap.NewProductionEncoderConfig()
        //修改时间格式
        config.EncodeTime = func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
                enc.AppendString(t.Format("2006-01-02 15:04:05"))
        }
        //将日志级别显示为中文
        config.EncodeLevel = func(level zapcore.Level, enc zapcore.PrimitiveArrayEncoder) {
                switch level {
                case zapcore.DebugLevel:
                        enc.AppendString("调试")
                case zapcore.InfoLevel:
                        enc.AppendString("信息")
                case zapcore.WarnLevel:
                        enc.AppendString("警告")
                case zapcore.ErrorLevel:
                        enc.AppendString("错误")
                case zapcore.DPanicLevel:
                        enc.AppendString("严重错误")
                case zapcore.PanicLevel:
                        enc.AppendString("恐慌")
                case zapcore.FatalLevel:
                        enc.AppendString("致命")
                default:
                        enc.AppendString(level.String())
                }
        }
        // 修改字段名为中文
        config.MessageKey = "消息"
        config.LevelKey = "级别"
        config.TimeKey = "时间"
        config.CallerKey = "位置"
        config.StacktraceKey = "堆栈"

        return zapcore.NewJSONEncoder(config)
}

// 日志输出配置
func getLogWriter() zapcore.WriteSyncer {
        //创建日志文件轮转管理器实例
        //使用lumberjack库实现日志轮转
        lumberjackLogger := &amp;lumberjack.Logger{
                Filename:   "./logs/gin.log", //文件路径,不存在会自动创建
                MaxSize:    200,            //单个文件的最大大小(MB)
                MaxBackups: 10,               //保留的最大备份文件数
                MaxAge:   30,               //日志文件的最大保存天数
                Compress:   true,             //是否压缩备份文件
        }
        return zapcore.AddSync(lumberjackLogger)
}
</pre></div>
<p>总结&nbsp;</p>
頁: [1]
查看完整版本: GO语言zap日志库理解和使用方法示例