前言
在完成了用户模块 CRUD、统一响应结构与错误码、分页封装之后,我们已经搭建出一个较为完整的 Gin 项目基础骨架。
但要想让项目在生产环境中更加可观测、易排查、可追踪,日志模块就必不可少了。
日志模块封装(使用 zap 实现)
我们使用 Uber 出品的高性能日志库 zap
来封装日志模块。
步骤一:安装 zap 日志库
go get go.uber.org/zap
步骤二:创建日志模块
在 logger/logger.go
中初始化日志模块:
// logger/logger.go
package logger
import (
"go.uber.org/zap"
)
var Log *zap.SugaredLogger
func InitLogger() {
// 创建生产环境的日志配置(带时间戳、调用行号等)
zapLogger, err := zap.NewProduction()
if err != nil {
panic("❌ 初始化日志失败: " + err.Error())
}
// 使用 SugaredLogger 封装,支持格式化输出
Log = zapLogger.Sugar()
}
步骤三:在 main.go 中初始化日志
// 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() {
// 设置日志输出格式为 JSON
encoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
// 设置输出位置(文件写入器)
writer := getLogWriter()
// 设置日志等级
level := zapcore.InfoLevel
// 创建 core
core := zapcore.NewCore(encoder, writer, level)
// 创建 logger
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() {
// 设置日志输出格式为 JSON
encoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
// 设置输出位置(文件写入器)
writer := getLogWriter()
// 设置日志等级
level := zapcore.InfoLevel
// 创建 core
core := zapcore.NewCore(encoder, writer, level)
// 创建 logger
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 {
// 自动创建 logs 目录(如果不存在)
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 // ✅ 新增:zap + lumberjack 日志封装
│
├── 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?..."
// 日志写入 logs/app.log(写死的路径)
但在实际开发中,这些配置一般都会写在
.env
或config.yaml
等配置文件中,而不是硬编码到逻辑里。
所以 接下来我们将使用 config.yaml
实现全局配置管理:
- 🛠️ 管理数据库连接信息
- ⚙️ 设置服务端口
- 📝 配置日志路径、日志等级
- ✅ 封装成统一的
config
模块,项目启动时自动加载
实现更灵活的配置方式,便于后续环境切换、配置热更新、部署管理等场景
本篇对应代码提交记录
commit: 3cafdb4018d626705c6673f3299a16a98db0f1e6
👉 GitHub 源码地址:github.com/luokakale-k…