先将zap的包导入到项目中
"go.uber.org/zap"
了解一下zap日志存储信息的两种方式
zap.NewProduction()
创建的日志对象输出格式为 JSON
zap.NewDevelopment()
创建的日志对象输出格式为 Text
gin项目中 常用的两种注册路由方式
gin.Default()
,这是一个默认的路由引擎,已经配置了一些常用的中间件,比如日志和恢复中间件
gin.New()
,这是一个空的路由引擎,然后根据需要手动添加中间件和路由处理函数
Logger中间件
:用于记录请求和响应的详情信息,包括请求方法,路径,状态码,处理时间等。会自动在请求处理的前后自动打印日志,默认情况下输出到了控制台。
Recovery中间件
:用来捕获并处理程序汇总的panic异常。如果发生panic异常,该中间件会恢复程序运行并返回一个500内部服务器错误响应,同时打印引发panic的错误信息。
gin.New()添加两个中间件的方法
如果此时使用gin.New并且手动添加了这两个中间件,那基本和gin.Default是一样的了
下面是两个中间件的写法:
// GinLogger 接收gin框架默认的日志
func GinLogger() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
query := c.Request.URL.RawQuery
c.Next()
cost := time.Since(start)
lg.Info(path,
zap.Int("status", c.Writer.Status()),
zap.String("method", c.Request.Method),
zap.String("path", path),
zap.String("query", query),
zap.String("ip", c.ClientIP()),
zap.String("user-agent", c.Request.UserAgent()),
zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),
zap.Duration("cost", cost),
)
}
}
// GinRecovery recover掉项目可能出现的panic,并使用zap记录相关日志
func GinRecovery(stack bool) gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
// Check for a broken connection, as it is not really a
// condition that warrants a panic stack trace.
var brokenPipe bool
if ne, ok := err.(*net.OpError); ok {
if se, ok := ne.Err.(*os.SyscallError); ok {
if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
brokenPipe = true
}
}
}
httpRequest, _ := httputil.DumpRequest(c.Request, false)
if brokenPipe {
lg.Error(c.Request.URL.Path,
zap.Any("error", err),
zap.String("request", string(httpRequest)),
)
// If the connection is dead, we can't write a status to it.
c.Error(err.(error)) // nolint: errcheck
c.Abort()
return
}
if stack {
lg.Error("[Recovery from panic]",
zap.Any("error", err),
zap.String("request", string(httpRequest)),
zap.String("stack", string(debug.Stack())),
)
} else {
lg.Error("[Recovery from panic]",
zap.Any("error", err),
zap.String("request", string(httpRequest)),
)
}
c.AbortWithStatus(http.StatusInternalServerError)
}
}()
c.Next()
}
}
添加了之后,然后再启动路由的地方,将两个中间件添加进去
g := gin.New()
g.Use(logger.GinLogger(),logger.GinRecovery(true))
zap日志如果添加到gin项目中
无论使用哪种注册路由的方法,都需要有下面这些步骤!
首先调用getLogWriter()
获取日志写入目标,该例子 使用了 lumberjack.Logger 实现日志文件的处理
接着调用 getEncoder()
获取日志编码器,该例子是用 JSON 编码器,然后对编码器相关配置进行设置
最后在 Init()
进行日志初始化
- 解析配置文件中的日志级别配置,创建相应的日志级别对象。
- 根据模式选择日志输出方式,如果是开发模式则将日志输出到控制台以便调试,否则只创建一个日志核心。
- 创建日志核心(core),指定编码器和输出目标。
- 根据需要添加其他日志核心,例如同时输出到文件和控制台。
- 调用
zap.New()
函数创建日志记录器,将上述步骤中创建的核心加入到记录器中。 - 通过
zap.ReplaceGlobals()
函数替换全局的日志记录器,以便在任何位置都可以使用该记录器。
var lg *zap.Logger
func Init(cfg *settings.LogConfig, mode string) (err error) {
// 获取 日志 写入目标
writeSyncer := getLogWriter()
// 获取 日志编码器
encoder := getEncoder()
// 解析 日志级别配置
var l = new(zapcore.Level)
err = l.UnmarshalText([]byte(cfg.Level))
if err != nil {
return
}
var core zapcore.Core
// 根据模式选择日志输出方式
if mode == "dev" {
// 进入开发模式,日志输出到终端
// 创建控制台编码器
consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig())
// 创建多个 日志核心(core),分别指定不同的编码器和输出目标
core = zapcore.NewTee(
zapcore.NewCore(encoder, writeSyncer, l), // JSON 编码器和指定的写入目标
zapcore.NewCore(consoleEncoder, zapcore.Lock(os.Stdout), zapcore.DebugLevel), // 控制台编码器和标准输出目标
)
} else {
core = zapcore.NewCore(encoder, writeSyncer, l) // 默认模式下,只创建一个日志核心
}
lg = zap.New(core, zap.AddCaller()) // 创建日志记录器
zap.ReplaceGlobals(lg) // 替换全局日志记录器
zap.L().Info("init logger success") // 打印初始化成功的日志消息
return
}
// 设置 zap 日志库的 日志编码器
func getEncoder() zapcore.Encoder {
// 创建一个默认的生产环境编码器配置
encoderConfig := zap.NewProductionEncoderConfig()
// 将时间戳格式设置为 ISO8601 格式
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
// 设置时间键名为 "time"
encoderConfig.TimeKey = "time"
// 将日志级别编码为大写形式
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
// 将日志记录中的持续时间编码为以秒为单位的浮点数
encoderConfig.EncodeDuration = zapcore.SecondsDurationEncoder
// 将日志记录中的调用者信息编码为短格式(仅显示文件名和行号)
encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder
// 使用以上设置创建一个 JSON 编码器
return zapcore.NewJSONEncoder(encoderConfig)
}
// 创建一个 zapcore.WriteSyncer对象,用户将日志写入指定的文件中
func getLogWriter() zapcore.WriteSyncer {
// 创建一个 lumberjack.Logger 对象,用于日志文件的处理
lumberJackLogger := &lumberjack.Logger{
Filename: "./app/logger/myapp.log", // 日志文件名
MaxSize: 10, // 单个日志文件的大小上限 (MB为单位)
MaxBackups: 10, // 保留旧日志文件最大个数
MaxAge: 10, // 日志保留的时间
}
// 将 lumberjack.Logger 转换为 zapcore.WriteSyncer 对象
return zapcore.AddSync(lumberJackLogger)
}
// 结构体的信息
type LogConfig struct {
Level string `mapstructure:"level"`
Filename string `mapstructure:"filename"`
MaxSize int `mapstructure:"max_size"`
MaxAge int `mapstructure:"max_age"`
MaxBackups int `mapstructure:"max_backups"`
}
// 下面是配置yaml中的信息
mode: "dev"
port: 8080
log:
level: "debug"
filename: "./log/bluebell.log"
max_size: 1000
max_age: 3600
max_backups: 5
最终在项目启动的时候,进行调用初始化日志的函数
// 1.加载配置
if err := settings.Init(); err != nil {
fmt.Printf("配置信息加载失败: %v\n", err)
return
}
// 2.初始化日志
if err := logger.Init(settings.Conf.LogConfig, settings.Conf.Mode); err != nil {
fmt.Printf("init logger failed, err:%v\n", err)
return
}
这样一个日志就成功添加到了项目中。
项目中自定义记录信息到zap日志中
在项目中我们可以根据需要是使用 zap.L().Xxx()
方法来记录自定义日志信息。
就比如:zap.L().Debug("日志初始化成功")
zap.L()
返回一个 全局的 日志记录器,通过该记录器可以进行日志的输出
Debug()
是记录器提供的一个方法,用户输出调试级别的日志信息
("日志初始化成功")
是要输出的具体日志信息内容。
除了 Debug() 还有其他一些的方法
Info()
: 输出信息级别的日志消息。Warn()
: 输出警告级别的日志消息。Error()
: 输出错误级别的日志消息。DPanic()
: 输出严重错误级别的日志消息,并触发 panic。Panic()
: 输出严重错误级别的日志消息,并触发 panic。Fatal()
: 输出严重错误级别的日志消息,并调用 os.Exit(1) 终止程序。