likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 46

回顾

在第一篇开篇文章中,咱们已经选用了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]
}

日志监控

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()),
      ))
   }
}
  • 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,
   }
}
  • 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...)
}

引入配置文件: 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       # 最大并发链接数

解析 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()
}

热加载 Air 配置

Air 使用的包: Air

  • 安装 Air
go get -u github.com/cosmtrek/air
  • 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

结束语

本节实现了:

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

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

转载自:https://juejin.cn/post/7282232046519812155
评论
请登录