• 「容器管理系统」 3. 初始化配置和日志监控


    回顾

    在第一篇开篇文章中,咱们已经选用了Gin框架和Docker的版本,这一节咱们需要初始化一些基础配置:

    • DB 配置
    • 日志监控
    • 配置文件 toml
    • 热重载 Air

    DB 配置

    链接 DB 库咱们使用的是 xorm 的官方包最新版本 v1.3.3

    var (
       groups map[string]*xorm.Engine
       once   sync.Once
    )
    
    type (
       cfg struct {
          Host        string
          Port        int
          User        string
          Pass        string
          Name        string
          MaxIdleConn int
          MaxOpenConn int
       }
    )
    
    func Init() {
       once.Do(func() {
          groups = make(map[string]*xorm.Engine)
          var cfgs map[string]cfg
          if err := viper.UnmarshalKey("db", &cfgs); err != nil {
             panic(err)
          }
          // 记录获取配置日志
          logz.Warn(fmt.Sprintln(cfgs))
          for instanceRwType, instanceCfg := range cfgs {
             dsn := fmt.Sprintf(
                "%s:%s@tcp(%s:%d)/%s?charset=%s",
                instanceCfg.User,
                instanceCfg.Pass,
                instanceCfg.Host,
                instanceCfg.Port,
                instanceCfg.Name,
                "utf8mb4")
             // 创建 xorm 实例
             engine, err := xorm.NewEngine(instanceRwType, dsn)
             if err != nil {
                panic(err)
             }
             // 设置最大空闲链接数
             if instanceCfg.MaxIdleConn == 0 {
                instanceCfg.MaxIdleConn = 5
             }
             // 设置最大并发链接数
             engine.SetMaxIdleConns(instanceCfg.MaxIdleConn)
             if instanceCfg.MaxOpenConn == 0 {
                instanceCfg.MaxOpenConn = 10
             }
             engine.SetMaxOpenConns(instanceCfg.MaxOpenConn)
             // 是否打印SQL
             engine.ShowSQL(viper.GetBool("app.debug"))
             engine.SetMapper(names.GonicMapper{})
        
             groups[instanceRwType] = engine
          }
       })
    }
    
    // Grp 返回指定实例组实例
    func Grp(name string) *xorm.Engine {
       return groups[name]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    日志监控

    log 包使用的是 go-tools

    这里使用到了 Gin 的全局中间件,创建一个 Logger 的一个中间件,使用 GinUse 引入全局中间件.

    实现功能:

    • 获取 trace id
    • 记录时间
    • 记录请求类型
    • 记录请求参数
    • 记录响应码
    // 最大内存大小
    const maxMemory = 32 << 20
    
    func Logger() gin.HandlerFunc {
       return func(c *gin.Context) {
          // 获取唯一的 trace id
          traceId.SetTraceId(c, fmt.Sprintf("%v", id.Make.Make()))
          start := time.Now()
          params := c.Request.URL.RawQuery
          // 解析请求类型,获取请求参数结构体
          if c.Request.Method == "POST" {
             contentType := strings.Split(c.Request.Header.Get("Content-Type"), ";")[0]
             switch contentType {
             case "application/x-www-form-urlencoded":
                if err := c.Request.ParseForm(); err == nil {
                   values := c.Request.PostForm
                   jsonByte, _ := json.Marshal(values)
                   params = string(jsonByte)
                }
             case "application/form-data":
                if err := c.Request.ParseMultipartForm(maxMemory); err == nil {
                   values := c.Request.PostForm
                   jsonByte, _ := json.Marshal(values)
                   params = string(jsonByte)
                }
             case "multipart/form-data":
                if err := c.Request.ParseMultipartForm(maxMemory); err == nil {
                   values := c.Request.PostForm
                   jsonByte, _ := json.Marshal(values)
                   params = string(jsonByte)
                }
             default:
                if requestBody, err := io.ReadAll(c.Request.Body); err == nil {
                   c.Request.Body = io.NopCloser(bytes.NewBuffer(requestBody))
                   params = string(requestBody)
                }
             }
          }
    
          // Stop timer
          end := time.Now()
          latency := end.Sub(start).Seconds() * 1e3
    
          // 写入日志
          xlog.Info(traceId.GetLogContext(c, c.Errors.ByType(gin.ErrorTypePrivate).String(),
             logz.F("params", params),
             logz.F("statusCode", c.Writer.Status()),
             logz.F("latency", latency),
             logz.F("bodySize", c.Writer.Size()),
          ))
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • GetLogContext
    
    const ginContextTraceId = "__gin_context_trace_id__"
    
    var hostName = ""
    
    func init() {
       var err error
       hostName, err = os.Hostname()
       if err != nil {
          hostName = ""
       }
    }
    
    func SetTraceId(c *gin.Context, id string) {
       c.Set(ginContextTraceId, id)
    }
    
    func getFields(c *gin.Context, fields []zapcore.Field) []zapcore.Field {
       logId := c.GetString(ginContextTraceId)
       var method, path, raw, ip string
       if c.Request != nil {
          method = c.Request.Method
          path = c.Request.URL.Path
          raw = c.Request.URL.RawQuery
          ip = c.ClientIP()
       }
       if raw != "" {
          path = path + "?" + raw
       }
       fields = append(fields, logz.F("logId", logId),
          logz.F("hostName", hostName), logz.F("path", path),
          logz.F("method", method), logz.F("clientIp", ip),
          logz.F("dateTime", time.Now().Format(time.DateTime)),
       )
       return fields
    }
    
    type LogContext struct {
       Ctx    *gin.Context
       Msg    string
       Fields []zapcore.Field
    }
    
    func (l *LogContext) Formatter() (string, []zapcore.Field) {
       return l.Msg, getFields(l.Ctx, l.Fields)
    }
    
    func GetLogContext(c *gin.Context, msg string, fields ...zapcore.Field) *LogContext {
       return &LogContext{
          Ctx:    c,
          Msg:    msg,
          Fields: fields,
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • xlog 文件
    const (
       DebugLevel = "debug"
       InfoLevel  = "info"
       WarnLevel  = "warn"
       ErrorLevel = "error"
       PanicLevel = "panic"
       FatalLevel = "fatal"
    )
    
    var levelMap = map[string]zapcore.Level{
       DebugLevel: zapcore.DebugLevel,
       InfoLevel:  zapcore.InfoLevel,
       WarnLevel:  zapcore.WarnLevel,
       ErrorLevel: zapcore.ErrorLevel,
       PanicLevel: zapcore.PanicLevel,
       FatalLevel: zapcore.FatalLevel,
    }
    
    type LogContext interface {
       Formatter() (string, []zapcore.Field)
    }
    
    type log struct {
       errLogger     *logz.Logger
       defaultLogger *logz.Logger
    }
    
    var logger *log
    
    func InitLog(dir string, level string, serviceName string) {
       logger = &log{}
       if dir == "" {
          logger.defaultLogger = logz.DefaultLogger()
          logger.errLogger = logz.DefaultLogger()
          return
       }
       l, ok := levelMap[strings.ToLower(level)]
       if !ok {
          l = zapcore.InfoLevel
       }
       logFile := dir + "/" + serviceName
       defaultLogger := logz.New(logz.Writer(logz.NewFileWriter(logFile+".log")), logz.Level(l))
       logz.SetDefaultLogger(defaultLogger)
       errLogger := logz.New(logz.Writer(logz.NewFileWriter(logFile+".wf")), logz.Level(levelMap[WarnLevel]))
       logger.defaultLogger = defaultLogger
       logger.errLogger = errLogger
       logz.Info("init logger succ")
    }
    
    func Debug(ctx LogContext) {
       msg, fields := ctx.Formatter()
       logger.defaultLogger.Debug(msg, fields...)
    }
    
    func Info(ctx LogContext) {
       msg, fields := ctx.Formatter()
       logger.defaultLogger.Info(msg, fields...)
    }
    
    func Warn(ctx LogContext) {
       msg, fields := ctx.Formatter()
       logger.errLogger.Warn(msg, fields...)
    }
    
    func Error(ctx LogContext) {
       msg, fields := ctx.Formatter()
       logger.errLogger.Error(msg, fields...)
    }
    
    func Panic(ctx LogContext) {
       msg, fields := ctx.Formatter()
       logger.errLogger.Panic(msg, fields...)
    }
    
    func Fatal(ctx LogContext) {
       msg, fields := ctx.Formatter()
       logger.errLogger.Fatal(msg, fields...)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78

    引入配置文件: toml

    local.toml

    在这里咱们采用的是 toml ,并没有用常用的 yaml,纯属个人喜好…

    [app]
        port=":8000"   # 项目启动端口
        env = "dev"    # 当前环境
        debug = "true" # 是否开启Debug
    
    [log]
        name = "go-cloud-native"  # 日志文件名
        level = "debug"           # 日志等级
        dir = "logs/applogs"      # 日志目录
    
    [db]  # 数据库相关配置
        [db.mysql]    # mysql 组(这里可以扩展其他组)
            host = "localhost"     # mysql server 地址
            port = 13306           # mysql 链接端口
            user = "cloud_native"  # mysql 登录用户名
            pass = "GGzHBpwrLiiMZNaY" # mysql 登录密码
            name = "cloud_native"  # mysql 当前使用库名
            maxIdleConn = 5        # 最大空闲链接数
            maxOpenConn = 10       # 最大并发链接数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    解析 toml

    解析 toml 使用的 go 官方 flag 包,详细用法自行查阅…

    func parseConfig() {
       config := flag.String("c", "conf/local.toml", "conf")
       flag.Parse()
       viper.SetConfigFile(*config)
       if err := viper.ReadInConfig(); err != nil {
          panic(fmt.Sprintf("parse config file fail: %s", err))
       }
       viper.WatchConfig()
       viper.OnConfigChange(func(in fsnotify.Event) {
          logz.Info("Config: conf/local.toml Changed...")
       })
    
       // 初始化日志文件
       xlog.InitLog(viper.GetString("log.dir"), viper.GetString("log.level"), viper.GetString("log.name"))
    
       // 初始化数据库
       store.Init()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    热加载 Air 配置

    Air 使用的包: Air

    • 安装 Air
    go get -u github.com/cosmtrek/air
    
    • 1
    • Air 的使用

    安装 Air 之后 需要先在你的项目根目录执行:air init,之后会出现 .air.toml 文件,配置没人如下:

    # 工作路径
    # 或绝对路径,请注意以下目录必须位于根目录下
    root = "."
    tmp_dir = "tmp"
    
    [build]
      args_bin = []
      # 执行的二进制文件
      bin = "./tmp/main"
      # shell 命令
      cmd = "go build -o ./tmp/main ./cmd/main.go"
      delay = 0
      exclude_dir = ["assets", "tmp", "vendor", "testdata"]
      exclude_file = []
      exclude_regex = ["_test.go"]
      exclude_unchanged = false
      follow_symlink = false
      full_bin = ""
      # 监听目录
      include_dir = []
      # 监听文件后缀
      include_ext = ["go", "tpl", "tmpl", "html"]
      # 监听文件
      include_file = []
      kill_delay = "0s"
      log = "build-errors.log"
      poll = false
      poll_interval = 0
      rerun = false
      rerun_delay = 500
      send_interrupt = false
      stop_on_error = false
    
    # 自定义每个输出的颜色
    [color]
      app = ""
      build = "yellow"
      main = "magenta"
      runner = "green"
      watcher = "cyan"
    
    [log]
      main_only = false
      time = true
    
    [misc]
      clean_on_exit = true  # 关闭是否清理 tmp
    
    [screen]
      clear_on_rebuild = false
      keep_scroll = true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    结束语

    本节实现了:

    • 配置文件 toml
    • DB 配置
    • 日志监控
    • 热重载 Air

    项目框架初始配置,为了后面的开发做铺垫,下一节,就要开始正式的项目功能开发了,尽情期待…

  • 相关阅读:
    前端面试题目(三十)
    Bigemap在地质矿产行业的应用
    hadoop3.3.2版本分布式集群搭建1主1从(含虚拟机安装教程)
    python基础
    数仓建模—数据仓库即服务
    SSM前后端分离项目(后端:SpringBoot 前端:vue+elementui)
    C++:写ini文件(附完整源码)
    WeTab--颜值与实力并存的浏览器插件
    sonic-ios-bridge(sib)性能监控
    Chrome之解决DevTools: Failed to load data:No resource with given identifier found
  • 原文地址:https://blog.csdn.net/LW1314QS/article/details/133908296