前言
在完成了用户模块 CRUD、统一响应结构与错误码、分页封装之后,我们已经搭建出一个较为完整的 Gin 项目基础骨架。
但要想让项目 在生产环境中更加可观测、易排查、可追踪 ,日志模块就必不可少了。
日志模块封装(使用 zap 实现)
我们使用 Uber 出品的高性能日志库 zap 来封装日志模块。
步骤一:安装 zap 日志库
go get go.uber.org/zap
#技术分享 #掘金步骤二:创建日志模块
在 logger/logger.go 中初始化日志模块:
package logger
import ( "go.uber.org/zap" )
var Log *zap.SugaredLogger
func InitLogger() { zapLogger, err := zap.NewProduction() if err != nil { panic(" 初始化日志失败: " +
}
Log = zapLogger.Sugar() }
步骤三:在 main.go 中初始化日志
import (
"gin-learn-notes/logger"
)
func main() { logger.InitLogger() defer logger.Log.Sync()
}
这里我要说一下 我们在上面的 main.go 里面加入了这行 defer logger.Log.Sync() 。这个我刚开始也不了解 后面才知道:
zap 的日志输出是异步 + 缓冲的
默认情况下,zap 会将日志内容写入内存缓冲区,然后异步地刷新到标准输出或文件中,这样做可以提升日志性能,但也带来一个问题 :
如果程序还没来得及把日志写入目标输出,主程序就退出了,那些缓冲中的日志就可能“丢失
所以我们要在程序退出前,主动调用 .Sync() 方法
这个方法的作用就是:
- 刷新 zap 的缓冲区
- 确保所有日志都已经被写入(终端 or 文件)
步骤四:项目中任意位置调用日志
import "gin-learn-notes/logger"
logger.Log.Info("用户列表:", users)
使用 zap.SugaredLogger,可以像 Printf 一样使用格式化日志,同时性能也非常优秀。后续也可以替换为自定义配置、输出到文件、JSON 格式等。
我们就可以在控制台看到日志输出:
配置化日志 + 写入文件 + 按天切割
一般我们日志都会存放到专门的日志目录里面 不会像这样直接输出到控制台中 所以我们可以把日志写入日志文件,并自动切割。
实现方案:zap + lumberjack 搭配使用
lumberjack 是一个非常轻量、稳定的日志切割工具,支持:
- 最大文件大小
- 最多保留几个日志文件
- 是否压缩历史日志
- 按天/按大小切割等
安装 lumberjack
go get github.com/natefinch/lumberjack
我们可以在 logger.go 里加如下功能:
import (
"go.uber.org/zap/zapcore"
"github.com/natefinch/lumberjack"
)
func getLogWriter() zapcore.WriteSyncer { lumberJackLogger := &lumberjack.Logger{ Filename: "logs/app.log", MaxSize: 10, MaxBackups: 5, MaxAge: 30, Compress: true, } return zapcore.AddSync(lumberJackLogger) }
然后我们修改下之前的 InitLogger 函数:
func InitLogger() {
encoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
writer := getLogWriter()
level := zapcore.InfoLevel
core := zapcore.NewCore(encoder, writer, level)
logger := zap.New(core, zap.AddCaller()) Log = logger.Sugar() }
完整文件示例(logger/logger.go):
package logger
import ( "github.com/natefinch/lumberjack" "go.uber.org/zap" "go.uber.org/zap/zapcore" )
var Log *zap.SugaredLogger
func InitLogger() { encoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
writer := getLogWriter()
level := zapcore.InfoLevel
core := zapcore.NewCore(encoder, writer, level)
logger := zap.New(core, zap.AddCaller()) Log = logger.Sugar() }
func getLogWriter() zapcore.WriteSyncer { lumberJackLogger := &lumberjack.Logger{ Filename: "logs/app.log", MaxSize: 10, MaxBackups: 5, MaxAge: 30, Compress: true, } return zapcore.AddSync(lumberJackLogger) }
我们需要在项目根目录提前创建 logs/ 目录:
mkdir logs
否则第一次运行时 zap 会找不到路径报错。当然我们也可以创建日志目录判断(避免首次运行时报错):
func getLogWriter() zapcore.WriteSyncer {
if _, err := os.Stat("logs"); os.IsNotExist(err) {
_ = os.Mkdir("logs", 0755)
}
lumberJackLogger := &lumberjack.Logger{ Filename: "logs/app.log", MaxSize: 10, MaxBackups: 5, MaxAge: 30, Compress: true, } return zapcore.AddSync(lumberJackLogger) }
我们在此运行一下之前写日志的地方接口后看看是否记录到 logs/app.log 里面:
项目结构继续演进(新增日志模块)
gin-learn-notes/
├── config/
│ └── database.go
│
├── controller/
│ ├── hello.go
│ ├── index.go
│ └── user.go
│
├── core/
│ └── response/
│ ├── code.go
│ ├── page.go
│ └── response.go
│
├── logger/
│ └── logger.go
│
├── logs/
│ └── app.log
│
├── model/
│ └── user.go
│
├── request/
│ ├── page_request.go
│ └── user_request.go
│
├── router/
│ └── router.go
│
├── service/
│ └── user_service.go
│
├── utils/
│ ├── paginate.go
│ ├── response.go
│ └── validator.go
│
├── main.go
├── go.mod
├── .gitignore
└── README.md
最后
本篇我们完成了日志模块的初步封装,包括:
- 使用 zap 构建高性能日志组件
- 搭配 lumberjack 实现日志自动切割
- 日志输出到 logs/ 目录,便于运维分析
- 可在项目中任意位置使用统一日志输出格式
到这为止,我们的 Gin 项目已经具备了清晰的日志体系,可以支撑后续开发与调试。
但是呢 我们注意到:目前数据库连接信息、端口号、日志路径等,还是直接写在代码里的 。
dsn := "root:123456@tcp(127.0.0.1:3306)/demo?..."
但在实际开发中,这些配置一般都会写在 .env 或 config.yaml 等配置文件中, 而不是硬编码到逻辑里 。
所以 接下来我们将使用 config.yaml 实现全局配置管理:
- 管理数据库连接信息
- 设置服务端口
- 配置日志路径、日志等级
- 封装成统一的 config 模块,项目启动时自动加载
实现更灵活的配置方式,便于后续环境切换、配置热更新、部署管理等场景
本篇对应代码提交记录
commit: 3cafdb4018d626705c6673f3299a16a98db0f1e6
GitHub 源码地址:github.com/luokakale-k…