Go 语言中 zap 日志库的高效使用指南

Go语言中zap日志库的高效使用指南在现代Go语言项目中,日志记录是不可或缺的组件之一。Go自带的log包提供了基础的日志记录功能,但对于需要高性能、结构化、分级日志的应用场景,zap是更为强大和灵活的选择。本文将介绍如何在Go项目中使用zap库进行高效的日志记录,涵盖基

Go 语言中 zap 日志库的高效使用指南

在现代 Go 语言项目中,日志记录是不可或缺的组件之一。Go 自带的 log 包提供了基础的日志记录功能,但对于需要高性能、结构化、分级日志的应用场景,zap 是更为强大和灵活的选择。本文将介绍如何在 Go 项目中使用 zap 库进行高效的日志记录,涵盖基础用法和实际应用示例。

本文概述了 Go 语言中 zap 日志库的安装与使用,展示了如何通过 zap 提供的两种日志记录器:Logger 和 SugaredLogger,在不同场景下进行灵活应用。结合实际代码示例,介绍了日志记录的基本操作,包括信息级别日志的记录、错误处理、以及将日志以 JSON 格式输出,适用于生产环境中的高性能日志需求。

默认的 Go log

log:<https://pkg.go.dev/log>

package main

import (
 "log"
 "os"
)

func init() {
 log.SetPrefix("LOG: ") // 设置前缀

 f, err := os.OpenFile("./log.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
 if err != nil {
  log.Fatalf("open log file failed with error: %v", err)
 }
 log.SetOutput(f) // 设置输出

 log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds | log.Llongfile)

 // const (
 //  Ldate         = 1 &lt;&lt; iota // 1 &lt;&lt; 0 = 000000001 = 1
 //  Ltime                     // 1 &lt;&lt; 1 = 000000010 = 2
 //  Lmicroseconds             // 1 &lt;&lt; 2 = 000000100 = 4
 //  Llongfile                 // 1 &lt;&lt; 3 = 000001000 = 8
 //  Lshortfile                // 1 &lt;&lt; 4 = 000010000 = 16
 //  ...
 // )
}

func main() {
 log.Println("1234")

 // log.Fatalln("1234")

 // log.Panicln("1234")

 // log.Panic("1234")
 // log.Panicf("1234, %d", 5678)
}

log 包是一个简单的日志包。

Package log implements a simple logging package. It defines a type, Logger, with methods for formatting output. It also has a predefined 'standard' Logger accessible through helper functions Print[f|ln], Fatal[f|ln], and Panic[f|ln], which are easier to use than creating a Logger manually. That logger writes to standard error and prints the date and time of each logged message. Every log message is output on a separate line: if the message being printed does not end in a newline, the logger will add one. The Fatal functions call os.Exit(1) after writing the log message. The Panic functions call panic after writing the log message.

uber-go zap

log:<https://pkg.go.dev/log>

github zap:<https://github.com/uber-go/zap>

pkg zap:<https://pkg.go.dev/go.uber.org/zap#section-readme>

Blazing fast, structured, leveled logging in Go.

zap 包提供快速、结构化、分级的日志记录。

对于在热路径中记录日志的应用程序,基于反射的序列化和字符串格式化非常昂贵 - 它们占用大量 CPU 并进行许多小分配。换句话说,使用 json.Marshal 和 fmt.Fprintf 记录大量 interface{} 会使您的应用程序变慢。

Zap 采用了不同的方法。它包含一个无反射、零分配的 JSON 编码器,并且基本 Logger 力求尽可能避免序列化开销和分配。通过在此基础上构建高级 SugaredLogger,zap 让用户可以选择何时需要计算每个分配以及何时他们更喜欢更熟悉的松散类型 API。

安装

go get -u go.uber.org/zap

在性能良好但不是关键的上下文中,使用 SugaredLogger。它比其他结构化日志包快4-10倍,包括结构化和 printf 风格的 API。

logger, _ := zap.NewProduction()
defer logger.Sync() // flushes buffer, if any
sugar := logger.Sugar()
sugar.Infow("failed to fetch URL",
  // Structured context as loosely typed key-value pairs.
  "url", url,
  "attempt", 3,
  "backoff", time.Second,
)
sugar.Infof("Failed to fetch URL: %s", url)

当性能和类型安全至关重要时,请使用Logger。它甚至比SugaredLogger 更快,分配也少得多,但它只支持结构化日志记录。

logger, _ := zap.NewProduction()
defer logger.Sync()
logger.Info("failed to fetch URL",
  // Structured context as strongly typed Field values.
  zap.String("url", url),
  zap.Int("attempt", 3),
  zap.Duration("backoff", time.Second),
)

zap文档:<https://pkg.go.dev/go.uber.org/zap#section-readme>

Logger 简单使用

package main

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

// 定义一个全局 logger 实例
// Logger提供快速、分级、结构化的日志记录。所有方法对于并发使用都是安全的。
// Logger是为每一微秒和每一个分配都很重要的上下文设计的,
// 因此它的API有意倾向于性能和类型安全,而不是简便性。
// 对于大多数应用程序,SugaredLogger在性能和人体工程学之间取得了更好的平衡。
var logger *zap.Logger

func main() {
 // 初始化
 InitLogger()
 // Sync调用底层Core的Sync方法,刷新所有缓冲的日志条目。应用程序在退出之前应该注意调用Sync。
 // 在程序退出之前,把缓冲区里的日志刷到磁盘上
 defer logger.Sync()
 simpleHttpGet("www.baidu.com")
 simpleHttpGet("http://www.baidu.com")
}

func InitLogger() {
 // NewProduction构建了一个合理的生产Logger,它将infollevel及以上的日志以JSON的形式写入标准错误。
 // It's a shortcut for NewProductionConfig().Build(...Option).
 logger, _ = zap.NewProduction()
}

func simpleHttpGet(url string) {
 // Get向指定的URL发出Get命令。如果响应是以下重定向代码之一,则Get跟随重定向,最多可重定向10个:
 // 301 (Moved Permanently)
 // 302 (Found)
 // 303 (See Other)
 // 307 (Temporary Redirect)
 // 308 (Permanent Redirect)
 // Get is a wrapper around DefaultClient.Get.
 // 使用NewRequest和DefaultClient.Do来发出带有自定义头的请求。
 resp, err := http.Get(url)
 if err != nil {
  // Error在ErrorLevel记录消息。该消息包括在日志站点传递的任何字段,以及日志记录器上积累的任何字段。
  logger.Error(
   "Error fetching url..",
   zap.String("url", url), // 字符串用给定的键和值构造一个字段。
   zap.Error(err))         // // Error is shorthand for the common idiom NamedError("error", err).
 } else {
  // Info以infollevel记录消息。该消息包括在日志站点传递的任何字段,以及日志记录器上积累的任何字段。
  logger.Info("Success..",
   zap.String("statusCode", resp.Status),
   zap.String("url", url))
  resp.Body.Close()
 }
}

运行

Code/go/zap_demo via 🐹 v1.20.3 via 🅒 base took 11.0s 
➜ go run main.go 
{"level":"error","ts":1686929392.121357,"caller":"zap_demo/main.go:42","msg":"Error fetching url..","url":"www.google.com","error":"Get \"www.google.com\": unsupported protocol scheme \"\"","stacktrace":"main.simpleHttpGet\n\t/Users/qiaopengjun/Code/go/zap_demo/main.go:42\nmain.main\n\t/Users/qiaopengjun/Code/go/zap_demo/main.go:21\nruntime.main\n\t/usr/local/go/src/runtime/proc.go:250"}
{"level":"error","ts":1686929422.123222,"caller":"zap_demo/main.go:42","msg":"Error fetching url..","url":"http://www.google.com","error":"Get \"http://www.google.com\": dial tcp 103.252.115.59:80: i/o timeout","stacktrace":"main.simpleHttpGet\n\t/Users/qiaopengjun/Code/go/zap_demo/main.go:42\nmain.main\n\t/Users/qiaopengjun/Code/go/zap_demo/main.go:22\nruntime.main\n\t/usr/local/go/src/runtime/proc.go:250"}

Code/go/zap_demo via 🐹 v1.20.3 via 🅒 base took 30.3s 
➜ go run main.go
{"level":"error","ts":1686929534.4672909,"caller":"zap_demo/main.go:42","msg":"Error fetching url..","url":"www.baidu.com","error":"Get \"www.baidu.com\": unsupported protocol scheme \"\"","stacktrace":"main.simpleHttpGet\n\t/Users/qiaopengjun/Code/go/zap_demo/main.go:42\nmain.main\n\t/Users/qiaopengjun/Code/go/zap_demo/main.go:21\nruntime.main\n\t/usr/local/go/src/runtime/proc.go:250"}
{"level":"info","ts":1686929535.561184,"caller":"zap_demo/main.go:47","msg":"Success..","statusCode":"200 OK","url":"http://www.baidu.com"}

Code/go/zap_demo via 🐹 v1.20.3 via 🅒 base 
➜ 

源码

// Level reports the minimum enabled level for this logger.
//
// For NopLoggers, this is [zapcore.InvalidLevel].
func (log *Logger) Level() zapcore.Level {
 return zapcore.LevelOf(log.core)
}

// Check returns a CheckedEntry if logging a message at the specified level
// is enabled. It's a completely optional optimization; in high-performance
// applications, Check can help avoid allocating a slice to hold fields.
func (log *Logger) Check(lvl zapcore.Level, msg string) *zapcore.CheckedEntry {
 return log.check(lvl, msg)
}

// Log logs a message at the specified level. The message includes any fields
// passed at the log site, as well as any fields accumulated on the logger.
func (log *Logger) Log(lvl zapcore.Level, msg string, fields ...Field) {
 if ce := log.check(lvl, msg); ce != nil {
  ce.Write(fields...)
 }
}

// Debug logs a message at DebugLevel. The message includes any fields passed
// at the log site, as well as any fields accumulated on the logger.
func (log *Logger) Debug(msg string, fields ...Field) {
 if ce := log.check(DebugLevel, msg); ce != nil {
  ce.Write(fields...)
 }
}

// Info logs a message at InfoLevel. The message includes any fields passed
// at the log site, as well as any fields accumulated on the logger.
func (log *Logger) Info(msg string, fields ...Field) {
 if ce := log.check(InfoLevel, msg); ce != nil {
  ce.Write(fields...)
 }
}

// Warn logs a message at WarnLevel. The message includes any fields passed
// at the log site, as well as any fields accumulated on the logger.
func (log *Logger) Warn(msg string, fields ...Field) {
 if ce := log.check(WarnLevel, msg); ce != nil {
  ce.Write(fields...)
 }
}

// Error logs a message at ErrorLevel. The message includes any fields passed
// at the log site, as well as any fields accumulated on the logger.
func (log *Logger) Error(msg string, fields ...Field) {
 if ce := log.check(ErrorLevel, msg); ce != nil {
  ce.Write(fields...)
 }
}

// DPanic logs a message at DPanicLevel. The message includes any fields
// passed at the log site, as well as any fields accumulated on the logger.
//
// If the logger is in development mode, it then panics (DPanic means
// "development panic"). This is useful for catching errors that are
// recoverable, but shouldn't ever happen.
func (log *Logger) DPanic(msg string, fields ...Field) {
 if ce := log.check(DPanicLevel, msg); ce != nil {
  ce.Write(fields...)
 }
}

// Panic logs a message at PanicLevel. The message includes any fields passed
// at the log site, as well as any fields accumulated on the logger.
//
// The logger then panics, even if logging at PanicLevel is disabled.
func (log *Logger) Panic(msg string, fields ...Field) {
 if ce := log.check(PanicLevel, msg); ce != nil {
  ce.Write(fields...)
 }
}

// Fatal logs a message at FatalLevel. The message includes any fields passed
// at the log site, as well as any fields accumulated on the logger.
//
// The logger then calls os.Exit(1), even if logging at FatalLevel is
// disabled.
func (log *Logger) Fatal(msg string, fields ...Field) {
 if ce := log.check(FatalLevel, msg); ce != nil {
  ce.Write(fields...)
 }
}

每个zapcore.Field其实就是一组键值对参数

// Field is an alias for Field. Aliasing this type dramatically
// improves the navigability of this package's API documentation.
type Field = zapcore.Field

输出的是 JSON 格式的日志

SugaredLogger 简单使用

NewProduction

package main

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

// 定义一个全局 logger 实例
// Logger提供快速、分级、结构化的日志记录。所有方法对于并发使用都是安全的。
// Logger是为每一微秒和每一个分配都很重要的上下文设计的,
// 因此它的API有意倾向于性能和类型安全,而不是简便性。
// 对于大多数应用程序,SugaredLogger在性能和人体工程学之间取得了更好的平衡。
var logger *zap.Logger

var sugarLogger *zap.SugaredLogger

func main() {
 // 初始化
 InitLogger()
 // Sync调用底层Core的Sync方法,刷新所有缓冲的日志条目。应用程序在退出之前应该注意调用Sync。
 // 在程序退出之前,把缓冲区里的日志刷到磁盘上
 defer logger.Sync()
 simpleHttpGet("www.baidu.com")
 simpleHttpGet("http://www.baidu.com")
}

func InitLogger() {
 // NewProduction构建了一个合理的生产Logger,它将infollevel及以上的日志以JSON的形式写入标准错误。
 // It's a shortcut for NewProductionConfig().Build(...Option).
 logger, _ = zap.NewProduction()
 sugarLogger = logger.Sugar()
}

func simpleHttpGet(url string) {
 // Get向指定的URL发出Get命令。如果响应是以下重定向代码之一,则Get跟随重定向,最多可重定向10个:
 // 301 (Moved Permanently)
 // 302 (Found)
 // 303 (See Other)
 // 307 (Temporary Redirect)
 // 308 (Permanent Redirect)
 // Get is a wrapper around DefaultClient.Get.
 // 使用NewRequest和DefaultClient.Do来发出带有自定义头的请求。
 resp, err := http.Get(url)
 if err != nil {
  // Error在ErrorLevel记录消息。该消息包括在日志站点传递的任何字段,以及日志记录器上积累的任何字段。
  //logger.Error(
  sugarLogger.Error(
   "Error fetching url..",
   zap.String("url", url), // 字符串用给定的键和值构造一个字段。
   zap.Error(err))         // // Error is shorthand for the common idiom NamedError("error", err).
 } else {
  // Info以infollevel记录消息。该消息包括在日志站点传递的任何字段,以及日志记录器上积累的任何字段。
  //logger.Info("Success..",
  sugarLogger.Info("Success..",
   zap.String("statusCode", resp.Status),
   zap.String("url", url))
  resp.Body.Close()
 }
}

运行

Code/go/zap_demo via 🐹 v1.20.3 via 🅒 base 
➜ go run main.go
{"level":"error","ts":1686930454.798492,"caller":"zap_demo/main.go:47","msg":"Error fetching url..{url 15 0 www.baidu.com &lt;nil>} {error 26 0  Get \"www.baidu.com\": unsupported protocol scheme \"\"}","stacktrace":"main.simpleHttpGet\n\t/Users/qiaopengjun/Code/go/zap_demo/main.go:47\nmain.main\n\t/Users/qiaopengjun/Code/go/zap_demo/main.go:23\nruntime.main\n\t/usr/local/go/src/runtime/proc.go:250"}
{"level":"info","ts":1686930454.83406,"caller":"zap_demo/main.go:54","msg":"Success..{statusCode 15 0 200 OK &lt;nil>} {url 15 0 http://www.baidu.com &lt;nil>}"}

Code/go/zap_demo via 🐹 v1.20.3 via 🅒 base 
➜ 

NewDevelopment

package main

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

// 定义一个全局 logger 实例
// Logger提供快速、分级、结构化的日志记录。所有方法对于并发使用都是安全的。
// Logger是为每一微秒和每一个分配都很重要的上下文设计的,
// 因此它的API有意倾向于性能和类型安全,而不是简便性。
// 对于大多数应用程序,SugaredLogger在性能和人体工程学之间取得了更好的平衡。
var logger *zap.Logger

// SugaredLogger将基本的Logger功能封装在一个较慢但不那么冗长的API中。任何Logger都可以通过其Sugar方法转换为sugardlogger。
//与Logger不同,SugaredLogger并不坚持结构化日志记录。对于每个日志级别,它公开了四个方法:
//   - methods named after the log level for log.Print-style logging
//   - methods ending in "w" for loosely-typed structured logging
//   - methods ending in "f" for log.Printf-style logging
//   - methods ending in "ln" for log.Println-style logging

// For example, the methods for InfoLevel are:
//
// Info(...any)           Print-style logging
// Infow(...any)          Structured logging (read as "info with")
// Infof(string, ...any)  Printf-style logging
// Infoln(...any)         Println-style logging
var sugarLogger *zap.SugaredLogger

func main() {
 // 初始化
 InitLogger()
 // Sync调用底层Core的Sync方法,刷新所有缓冲的日志条目。应用程序在退出之前应该注意调用Sync。
 // 在程序退出之前,把缓冲区里的日志刷到磁盘上
 defer logger.Sync()
 simpleHttpGet("www.baidu.com")
 simpleHttpGet("http://www.baidu.com")
}

func InitLogger() {
 // NewProduction构建了一个合理的生产Logger,它将infollevel及以上的日志以JSON的形式写入标准错误。
 // It's a shortcut for NewProductionConfig().Build(...Option).
 //logger, _ = zap.NewProduction()
 // NewDevelopment构建一个开发日志,它以一种人性化的格式将DebugLevel及以上的日志写入标准错误。
 logger, _ = zap.NewDevelopment()
 // Sugar封装了Logger,以提供更符合人体工程学的API,但速度略慢。糖化一个Logger的成本非常低,
 // 因此一个应用程序同时使用Logger和sugaredlogger是合理的,在性能敏感代码的边界上在它们之间进行转换。
 sugarLogger = logger.Sugar()
}

func simpleHttpGet(url string) {
 // Get向指定的URL发出Get命令。如果响应是以下重定向代码之一,则Get跟随重定向,最多可重定向10个:
 // 301 (Moved Permanently)
 // 302 (Found)
 // 303 (See Other)
 // 307 (Temporary Redirect)
 // 308 (Permanent Redirect)
 // Get is a wrapper around DefaultClient.Get.
 // 使用NewRequest和DefaultClient.Do来发出带有自定义头的请求。
 resp, err := http.Get(url)
 if err != nil {
  // Error在ErrorLevel记录消息。该消息包括在日志站点传递的任何字段,以及日志记录器上积累的任何字段。
  //logger.Error(

  // 错误使用fmt。以Sprint方式构造和记录消息。
  sugarLogger.Error(
   "Error fetching url..",
   zap.String("url", url), // 字符串用给定的键和值构造一个字段。
   zap.Error(err))         // // Error is shorthand for the common idiom NamedError("error", err).
 } else {
  // Info以infollevel记录消息。该消息包括在日志站点传递的任何字段,以及日志记录器上积累的任何字段。
  //logger.Info("Success..",

  // Info使用fmt。以Sprint方式构造和记录消息。
  sugarLogger.Info("Success..",
   zap.String("statusCode", resp.Status),
   zap.String("url", url))
  resp.Body.Close()
 }
}

运行

Code/go/zap_demo via 🐹 v1.20.3 via 🅒 base 
➜ go run main.go
2023-06-16T23:52:00.384+0800    ERROR   zap_demo/main.go:48     Error fetching url..{url 15 0 www.baidu.com &lt;nil>} {error 26 0  Get "www.baidu.com": unsupported protocol scheme ""}
main.simpleHttpGet
        /Users/qiaopengjun/Code/go/zap_demo/main.go:48
main.main
        /Users/qiaopengjun/Code/go/zap_demo/main.go:23
runtime.main
        /usr/local/go/src/runtime/proc.go:250
2023-06-16T23:52:00.439+0800    INFO    zap_demo/main.go:55     Success..{statusCode 15 0 200 OK &lt;nil>} {url 15 0 http://www.baidu.com &lt;nil>}

Code/go/zap_demo via 🐹 v1.20.3 via 🅒 base 
➜ 

总结

zap 作为 Go 语言中的高性能日志库,凭借其结构化日志、快速响应和灵活的日志级别管理,为开发者提供了强大的工具。在实际项目中,通过选择 SugaredLogger 或 Logger,可以根据需求在易用性与性能之间取得平衡。本文所展示的使用方法和代码示例,能够帮助开发者在生产环境中轻松实现高效的日志管理。

参考

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
寻月隐君
寻月隐君
0x750E...B6f5
不要放弃,如果你喜欢这件事,就不要放弃。如果你不喜欢,那这也不好,因为一个人不应该做自己不喜欢的事。