「容器管理系统」 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]
}
日志监控
log
包使用的是 go-tools
这里使用到了 Gin
的全局中间件,创建一个 Logger
的一个中间件,使用 Gin
的 Use
引入全局中间件.
实现功能:
- 获取
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