Go的一些好用的第三方库
2025/11/9大约 5 分钟
go.uber.org/zap:Go的高性能结构化日志库
安装
go get -u go.uber.org/zapAI示范
package main
import (
"fmt"
"os"
"time"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
// initLogger 初始化 Zap Logger
// 它配置了日志同时输出到控制台和文件,并设置为全局 Logger
func initLogger() {
// 1. 设置日志写入器 (Writer)
// 同时写入文件和控制台
logFile := "app.log"
fileWriter, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
panic(fmt.Sprintf("Failed to open log file: %v", err))
}
// 使用 MultiWriter 将日志同时输出到文件和标准输出
// os.Stdout 代表控制台
multiWriter := zapcore.NewMultiWriteSyncer(zapcore.AddSync(fileWriter), zapcore.AddSync(os.Stdout))
// 2. 设置日志编码器 (Encoder)
// 用于定义日志的格式,比如时间格式、日志级别如何显示等
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO86011TimeEncoder // 使用 ISO8601 格式的时间
encoderConfig.TimeKey = "time" // 时间字段的 Key
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder // 日志级别大写,如 "INFO"
encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder // 调用者信息,短路径格式
// 使用 JSON 格式的编码器
jsonEncoder := zapcore.NewJSONEncoder(encoderConfig)
// 3. 创建核心(Core)
// Core 需要三样东西:Encoder, WriteSyncer, Log Level
// 这里我们设置日志级别为 InfoLevel,意味着 Info、Warn、Error、Fatal 级别的日志都会被记录
core := zapcore.NewCore(jsonEncoder, multiWriter, zap.InfoLevel)
// 4. 创建 Logger
// zap.AddCaller() 会在日志中添加调用代码的位置信息 (文件名和行号)
logger := zap.New(core, zap.AddCaller(), zap.AddStacktrace(zapcore.ErrorLevel))
// 5. 替换全局 Logger
// 通过调用 zap.ReplaceGlobals,之后就可以在任何地方使用 zap.L() 和 zap.S() 获取这个 logger 实例了
zap.ReplaceGlobals(logger)
zap.S().Info("Logger initialized successfully")
}
// GinZapMiddleware 创建一个 Gin 中间件,用于记录每个请求的结构化日志
func GinZapMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
// 处理请求
c.Next()
end := time.Now()
latency := end.Sub(start)
// 使用 SugaredLogger 的 Infow 方法记录结构化日志
// "w" in "Infow" means "with" (structured context)
zap.S().Infow("Gin request handled",
"status", c.Writer.Status(),
"method", c.Request.Method,
"path", c.Request.URL.Path,
"ip", c.ClientIP(),
"latency", latency,
"user_agent", c.Request.UserAgent(),
)
}
}
// simulateNanoFrameworkTask 模拟来自另一个框架(如 lanng/nano)的日志调用
func simulateNanoFrameworkTask() {
zap.S().Info("Executing a task from 'nano' framework...")
// SugaredLogger 的 *w 方法 (Warnw, Errorw) 非常适合添加键值对上下文
zap.S().Warnw("A potential issue detected in 'nano' task",
"task_id", "task-12345",
"component", "nano_worker",
)
}
// myCustomDebugLog 演示在您自己的业务逻辑中如何使用 SugaredLogger
func myCustomDebugLog() {
// 注意:因为我们在initLogger中设置的日志级别是InfoLevel,所以Debug级别的日志不会被输出。
// 如果需要输出,请将 newCore 的第三个参数改为 zap.DebugLevel
zap.S().Debug("This debug message will not be shown.")
// 使用 Infof 进行模板字符串格式的日志记录
userID := 1001
zap.S().Infof("Processing data for user: %d", userID)
// 使用 Errorw 记录带有结构化上下文的错误
err := fmt.Errorf("failed to connect to database")
zap.S().Errorw("Database operation failed",
"error", err,
"user_id", userID,
"retry_attempts", 3,
)
}
func main() {
// 1. 初始化我们自定义的全局 Logger
initLogger()
// defer a Sync call to make sure all buffered logs are written out before the program exits
defer zap.S().Sync()
// 2. 演示业务逻辑和模拟框架的日志
myCustomDebugLog()
simulateNanoFrameworkTask()
// 3. 设置 Gin 框架
// 使用 gin.New() 创建一个不带默认中间件的引擎
r := gin.New()
// 添加我们自定义的 Zap 日志中间件和 Gin 的 Recovery 中间件
// Recovery 中间件能捕获任何 panic 并防止服务器崩溃
r.Use(GinZapMiddleware(), gin.Recovery())
// 4. 定义路由
r.GET("/hello", func(c *gin.Context) {
// 演示在 handler 内部记录日志
traceID := "abc-xyz-123"
zap.S().Infow("Inside /hello handler", "trace_id", traceID)
c.JSON(200, gin.H{
"message": "Hello, world!",
})
})
r.GET("/error", func(c *gin.Context) {
// 演示一个会产生错误的请求
zap.S().Error("This handler will simulate an error.")
c.JSON(500, gin.H{
"message": "An error occurred!",
})
})
// 5. 启动服务器
zap.S().Info("Starting Gin server on :8080")
if err := r.Run(":8080"); err != nil {
zap.S().Fatalf("Failed to start Gin server: %v", err)
}
}{"level":"INFO","time":"2025-11-09T17:30:00.123+08:00","caller":"main/main.go:50","msg":"Logger initialized successfully"}
{"level":"INFO","time":"2025-11-09T17:30:00.123+08:00","caller":"main/main.go:121","msg":"Processing data for user: 1001"}
{"level":"ERROR","time":"2025-11-09T17:30:00.123+08:00","caller":"main/main.go:127","msg":"Database operation failed","error":"failed to connect to database","user_id":1001,"retry_attempts":3,"stacktrace":"..."}
{"level":"INFO","time":"2025-11-09T17:30:00.123+08:00","caller":"main/main.go:102","msg":"Executing a task from 'nano' framework..."}
{"level":"WARN","time":"2025-11-09T17:30:00.123+08:00","caller":"main/main.go:105","msg":"A potential issue detected in 'nano' task","task_id":"task-12345","component":"nano_worker"}
{"level":"INFO","time":"2025-11-09T17:30:00.124+08:00","caller":"main/main.go:167","msg":"Starting Gin server on :8080"}相关信息
一站式日志持久化 (initLogger):
通过 os.OpenFile 创建或打开一个日志文件。
使用 zapcore.NewMultiWriteSyncer 将文件写入器和控制台写入器 (os.Stdout) 合并,实现了日志的同时输出。
zap.NewProductionEncoderConfig() 和 zapcore.NewJSONEncoder 确保了日志是结构化的 JSON 格式,便于机器解析。
易用性 (SugaredLogger):
通过 zap.ReplaceGlobals(logger) 将我们配置好的 logger 设置为全局实例。
之后在项目的任何地方,都可以通过 zap.S() 获取 SugaredLogger 实例。
zap.S().Infof(...) 提供了类似 printf 的格式化功能。
zap.S().Infow(...)、Warnw(...)、Errorw(...) 是 SugaredLogger 的精髓,它们以键值对(key, value, key, value, ...)的形式接收参数,非常适合记录结构化上下文,可读性强且易于使用。
Gin 框架集成 (GinZapMiddleware):
我们创建了一个自定义中间件,而不是简单地替换 Gin 的默认 Writer。
这样做的好处是我们可以完全控制日志的内容和结构,可以记录请求路径、状态码、IP、耗时等丰富的上下文信息,这正是结构化日志的威力所在。
gin.New() 创建了一个干净的引擎,然后我们使用 .Use() 方法加载了我们的日志中间件和 gin.Recovery(),实现了对日志的完全掌控。