Gin框架组合(Zap、lumberjack、ini)使用手册

Gin

Gin是一个golang的微框架,封装比较优雅,API友好,源码注释比较明确,已经发布了1.0版本。具有快速灵活,容错方便等特点。其实对于golang而言,web框架的依赖要远比Python,Java之类的要小。自身的net/http足够简单,性能也非常不错。框架更像是一些常用函数或者工具的集合。借助框架开发,不仅可以省去很多常用的封装带来的时间,也有助于团队的编码风格和形成规范。

下面就Gin的用法做一个简单的介绍。

首先需要安装,安装比较简单,使用go get即可:

go get gopkg.in/gin-gonic/gin.v1
import (
    "gopkg.in/gin-gonic/gin.v1"
    "net/http"
)

func main(){
    
    router := gin.Default()

    router.GET("/", func(c *gin.Context) {
        c.String(http.StatusOK, "Hello World")
    })
    router.Run(":8000")
}

日常我们是使用Gin框架编写接口等等,可能会使用到一下几种组件。

Zap 日志记录

lumberjack 日志切割

ini 配置文件读取

ini配置读取

下载

go get github.com/go-ini/ini

因为项目中要是到配置文件,我们可以采用ini模块来实现

首先,创建一个config.ini配置文件:

port = 9000
release = false

[mysql]
user = db1
password = db1
host = 10.10.10.6
port = 3306
db = db1

[log]
level = debug
filename = ./logs/info.log
maxsize = 1
max_age = 30
max_backups = 5

package setting

import (
	"gopkg.in/ini.v1"
)

var Conf = new(AppConfig)

// AppConfig 应用程序配置
type AppConfig struct {
	Release      bool `ini:"release"`
	Port         int  `ini:"port"`
	*MySQLConfig `ini:"mysql"`
	*LogConfig   `ini:"log"`
}

// MySQLConfig 数据库配置
type MySQLConfig struct {
	User     string `ini:"user"`
	Password string `ini:"password"`
	DB       string `ini:"db"`
	Host     string `ini:"host"`
	Port     int    `ini:"port"`
}

type LogConfig struct {
	Level      string `ini:"level"`
	Filename   string `ini:"filename"`
	MaxSize    int    `ini:"maxsize"`
	MaxAge     int    `ini:"max_age"`
	MaxBackups int    `ini:"max_backups"`
}

//把先关初始的参数加载到全局变量,然后方便调用
func Init(file string) error {
	return ini.MapTo(Conf, file)
}

看一下调用方式

	// 传入配置文件路径,加载配置文件,
	if err := setting.Init("conf/config.ini"); err != nil {
		fmt.Printf("load config from file failed, err:%v\n", err)
		return
	}
	fmt.Println("config.ini配置加载成功", setting.Conf.Port)

	// 创建数据库
	// sql: CREATE DATABASE bubble;
	// 连接数据库
	err := dao.InitMySQL(setting.Conf.MySQLConfig)
	if err != nil {
		fmt.Printf("init mysql failed, err:%v\n", err)
		return
	}
	fmt.Println("数据库配置初始化加载成功", setting.Conf.MySQLConfig)
	if err := log.InitLogger(setting.Conf.LogConfig); err != nil {
		fmt.Printf("init logger failed, err:%v\n", err)
		return
	}

已经加载全局变量,可以正常使用

mysql调用参数,

func InitMySQL(cfg *setting.MySQLConfig) (err error) {
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
		cfg.User, cfg.Password, cfg.Host, cfg.Port, cfg.DB)

	DB, err = gorm.Open("mysql", dsn)
	if err != nil {
		return
	}
	DB.Debug()
	DB.LogMode(true)
	DB.SetLogger(&GormLogger{})
	return DB.DB().Ping()
}

Zap和lumberjack

Zap 日志记录

lumberjack 日志切割

go get -u go.uber.org/zap
go get -u github.com/natefinch/lumberjack

大家可以参考

golang开发:类库篇(一) Zap高性能日志类库的使用 - 飞翔码农 - 博客园使用zap接收gin框架默认的日志并配置日志归档 - 兰玉磊的个人博客golang开发:类库篇(一) Zap高性能日志类库的使用 - 飞翔码农 - 博客园

在Go语言项目中使用Zap日志库 - 知乎

日志格式

{"level":"INFO","time":"2021-12-28T15:34:50.934+0800","caller":"log/logger.go:65","msg":"/","status":200,"method":"GET","path":"/","query":"","ip":"127.0.0.1","user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36","errors":"","cost":0.0010676}
{"level":"DEBUG","time":"2021-12-28T15:34:51.194+0800","caller":"dao/mysql.go:23","msg":"sql","module":"gorm","type":"sql","src":"D:/桌面/bubble/models/todo.go:24","duration":0.0504676,"sql":"SELECT * FROM `todos`  ","values":null,"rows_returned":3}

我们需要把它集成到Gin框架中。

我这边的配置,也会读取ini的文件(看上面的代码)

[log]
level = debug
filename = ./logs/info.log
maxsize = 1
max_age = 30
max_backups = 5







type LogConfig struct {
	Level      string `ini:"level"`
	Filename   string `ini:"filename"`
	MaxSize    int    `ini:"maxsize"`
	MaxAge     int    `ini:"max_age"`
	MaxBackups int    `ini:"max_backups"`
}
package log

import (
	"bubble/setting"
	"github.com/gin-gonic/gin"
	"github.com/natefinch/lumberjack"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"net"
	"net/http"
	"net/http/httputil"
	"os"
	"runtime/debug"
	"strings"
	"time"
)

var Logger *zap.Logger

// InitLogger 初始化Logger
func InitLogger(cfg *setting.LogConfig) (err error) {
	writeSyncer := getLogWriter(cfg.Filename, cfg.MaxSize, cfg.MaxBackups, cfg.MaxAge)
	//fmt.Println(cfg.Filename)
	encoder := getEncoder()
	var l = new(zapcore.Level)
	err = l.UnmarshalText([]byte(cfg.Level))
	if err != nil {
		return
	}
	core := zapcore.NewCore(encoder, writeSyncer, l)

	Logger = zap.New(core, zap.AddCaller())
	return
}

func getEncoder() zapcore.Encoder {
	encoderConfig := zap.NewProductionEncoderConfig()
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
	encoderConfig.TimeKey = "time"
	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
	encoderConfig.EncodeDuration = zapcore.SecondsDurationEncoder
	encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder

	return zapcore.NewJSONEncoder(encoderConfig)
}

func getLogWriter(filename string, maxSize, maxBackup, maxAge int) zapcore.WriteSyncer {
	lumberJackLogger := &lumberjack.Logger{
		Filename:  filename,
		MaxSize:    maxSize,
		MaxBackups: maxBackup,
		MaxAge:     maxAge,
	}
	return zapcore.AddSync(lumberJackLogger)
}

// GinLogger 接收gin框架默认的日志
func GinLogger(logger *zap.Logger) 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)
		logger.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(logger *zap.Logger, 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 {
					logger.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 {
					logger.Error("[Recovery from panic]",
						zap.Any("error", err),
						zap.String("request", string(httpRequest)),
						zap.String("stack", string(debug.Stack())),
					)
				} else {
					logger.Error("[Recovery from panic]",
						zap.Any("error", err),
						zap.String("request", string(httpRequest)),
					)
				}
				c.AbortWithStatus(http.StatusInternalServerError)
			}
		}()
		c.Next()
	}
}

注册中间件的操作在routes.SetupRouter()中:

func SetupRouter() *gin.Engine {
	if setting.Conf.Release {
		gin.SetMode(gin.ReleaseMode)
	}
    #开始调用,中间件使用
	r := gin.New()
	r.Use(log.GinLogger(log.Logger), log.GinRecovery(log.Logger, true))

}

 main中调用

	log.Logger.Debug("大家好,日志展示")
	defer log.Logger.Sync()
{"level":"DEBUG","time":"2021-12-28T15:34:42.647+0800","caller":"bubble/main.go:39","msg":"大家好,日志展示"}

日志格式可以定义json格式,后期可以直接接入elk分析展示日志

Gorm的sql日志记录到文本

Gorm 建立了对 Logger 的支持,默认模式只会在错误发生的时候打印日志。可以通过gorm SetLogger(log logger)方法 改变gorm 打日志的行为。

gorm 中 logger的接口:

type logger interface {
	Print(ctx context.Context, v ...interface{})
}

v 的值为:

1个参数: level,表示这个是个什么请求,可以是“sql”
2个参数:打印sql的代码行号,如/Users/yejianfeng/Documents/gopath/src/gorm-log/main.go:50, 
3个参数: 执行时间戳
4个参数: sql语句
5参数:如果有预处理,请求参数,第六个参数是这个sql影响的行数。

zaplog集成示例

DB.Debug()
DB.LogMode(true)
DB.SetLogger(&GormLogger{})

// GormLogger struct
type GormLogger struct{}

// Print - Log Formatter
func (*GormLogger) Print(v ...interface{}) {
	switch v[0] {
	case "sql":
		log.Debug(
			"sql",
			zap.String("module", "gorm"),
			zap.String("type", "sql"),
			zap.Any("src", v[1]),
			zap.Any("duration", v[2]),
			zap.Any("sql", v[3]),
			zap.Any("values", v[4]),
			zap.Any("rows_returned", v[5]),
		)
	case "log":
		log.Debug("log", zap.Any("gorm", v[2]))
	}
}

 日志格式,sql语句已经打印到日志中

{"level":"DEBUG","time":"2021-12-28T15:34:51.194+0800","caller":"dao/mysql.go:23","msg":"sql","module":"gorm","type":"sql","src":"D:/桌面/bubble/models/todo.go:24","duration":0.0504676,"sql":"SELECT * FROM `todos`  ","values":null,"rows_returned":3}

参考文档 GORM自定义日志配置 - 苍山落暮 - 博客园

人生得意须尽欢,莫使金樽空对月。 天生我材必有用,千金散尽还复来。
原文地址:https://www.cnblogs.com/heian99/p/15778062.html