zoukankan      html  css  js  c++  java
  • 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自定义日志配置 - 苍山落暮 - 博客园

    人生得意须尽欢,莫使金樽空对月。 天生我材必有用,千金散尽还复来。
  • 相关阅读:
    c++ 函数中的部分代码执行一次
    如何限制对象只能建立在堆上或者栈上
    FFMPEG Qt视频播放器
    C/C++中带可变参数的函数
    柔性数组
    压缩图片网站
    vscode存盘时格式化
    两个i标签之间有缝隙
    node 中process进程argv,argv0,execArgv,execPath
    chalk插件 使终端输出的字带颜色
  • 原文地址:https://www.cnblogs.com/heian99/p/15778062.html
Copyright © 2011-2022 走看看