You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

225 lines
6.1 KiB

2 months ago
package dtviper
import (
"encoding/json"
"fmt"
"net/url"
"path/filepath"
"strings"
"time"
"github.com/cloudwego/kitex/pkg/klog"
"github.com/fsnotify/fsnotify"
"github.com/spf13/pflag"
"github.com/spf13/viper"
)
// Config
type Config struct {
Viper *viper.Viper
}
var (
configVar string
isRemoteConfig bool
GlobalSource = pflag.String("global.source", "default(flag)", "identify the source of configuration")
// var globalUnset = pflag.String("global.unset", "default(flag)", "this parameter do not appear in config file")
GlobalUnset = pflag.String("global.unset", "", "this parameter do not appear in config file")
)
/*
configVar 采用了另一种方式来初始化
主要是为了强调这个命令行参数的特殊性这个参数是需要在代码中直接引用的
其他参数是跟viper绑定的不会直接使用而是通过viper.GetType()来获取
另外一个原因是plfag.String()返回的是*string用起来没那么直观
*/
// Viper 配置存取初始化
func init() {
pflag.StringVar(&configVar, "config", "", "Config file path")
pflag.BoolVar(&isRemoteConfig, "isRemoteConfig", false, "Whether to choose remote config")
}
// SetRemoteConfig sets the config from remote.
func (v *Config) SetRemoteConfig(u *url.URL) {
/*
这里接受etcd consul 的url
etcd:
url格式为 etcd+http://127.0.0.1:2380/path/to/key.yaml
其中provider=etcd, endpoint=http://127.0.0.1:2380, path=/path/to/key.yaml
consul:
url格式为consul://127.0.0.1:8500/key.json
其中provider=consul, endpoint=127.0.0.1:8500, path=key.json
TODO: consul key name 可以包含 .
*/
var provider string
var endpoint string
var path string
schemes := strings.SplitN(u.Scheme, "+", 2)
if len(schemes) < 1 {
klog.Fatalf("invalid config scheme '%s'", u.Scheme)
}
provider = schemes[0]
switch provider {
case "etcd":
if len(schemes) < 2 {
klog.Fatalf("invalid config scheme '%s'", u.Scheme)
}
protocol := schemes[1]
endpoint = fmt.Sprintf("%s://%s", protocol, u.Host)
path = u.Path // u.Path = /path/to/key.yaml
case "consul":
endpoint = u.Host
path = u.Path[1:] // u.Path = /key.json
default:
klog.Fatalf("unsupported provider '%s'", provider)
}
// 配置文件的后缀
ext := filepath.Ext(path)
if ext == "" {
klog.Fatalf("using remote config, without specifiing file extension")
}
// .yaml ==> yaml
configType := ext[1:]
klog.Infof("Using Remote Config Provider: '%s', Endpoint: '%s', Path: '%s', ConfigType: '%s'", provider, endpoint, path, configType)
if err := v.Viper.AddRemoteProvider(provider, endpoint, path); err != nil {
klog.Fatalf("error adding remote provider %s", err)
}
v.Viper.SetConfigType(configType)
}
// SetConfigType
func (v *Config) SetDefaultValue() {
v.Viper.SetDefault("global.unset", "default(viper)")
/* add more */
}
// WatchRemoteConf watch the configuration of the remote provider and
func (v *Config) WatchRemoteConf() {
for {
time.Sleep(time.Second * 5) // delay after each request
// currently, only tested with etcd support
err := v.Viper.WatchRemoteConfig()
if err != nil {
klog.Errorf("unable to read remote config: %v", err)
continue
}
// unmarshal new config into our runtime config struct. you can also use channel
// to implement a signal to notify the system of the changes
// runtime_viper.Unmarshal(&runtime_conf)
klog.Info("Watching Remote Config")
klog.Infof("Global.Source: '%s'", v.Viper.GetString("Global.Source"))
klog.Infof("Global.ChangeMe: '%s'", v.Viper.GetString("Global.ChangeMe"))
}
}
// ZapLogConfig 读取Log的配置文件,并返回
func (v *Config) ZapLogConfig() []byte {
log := v.Viper.Sub("Log")
logConfig, err := json.Marshal(log.AllSettings())
if err != nil {
klog.Fatalf("error marshalling log config %s", err)
}
return logConfig
}
// ConfigInit initializes the configuration
func ConfigInit(envPrefix string, cfgName string) *Config {
pflag.Parse()
v := viper.New()
config := Config{Viper: v}
viper := config.Viper
/*
viper.BindPFlags 自动绑定了所有命令行参数如果只需要其中一部分可以用viper.BingPflag选择性绑定
viper.BindPFlag("global.source", pflag.Lookup("global.source"))
*/
viper.BindPFlags(pflag.CommandLine)
config.SetDefaultValue()
// read from env
viper.AutomaticEnv()
// so that client.foo maps to MYAPP_CLIENT_FOO
viper.SetEnvPrefix(envPrefix)
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
if configVar != "" {
/*
如果设置了--config参数尝试从这里解析
它可能是一个Remote Config来自etcd或consul
也可能是一个本地文件
*/
u, err := url.Parse(configVar)
if err != nil {
klog.Fatalf("error parsing: '%s'", configVar)
}
if u.Scheme != "" {
// 看起来是个remote config
config.SetRemoteConfig(u)
isRemoteConfig = true
} else {
viper.SetConfigFile(configVar)
}
} else {
/*
尝试搜索若干默认路径先后顺序如下:
- /etc/tiktok/config/userConfig.<ext>
- ~/.tiktok/userConfig.<ext>
- ./userConfig.<ext>
其中<ext> viper所支持的文件类型如ymljson等
*/
viper.SetConfigName(cfgName) // name of config file (without extension)
viper.AddConfigPath("/etc/tiktok/config")
viper.AddConfigPath("$HOME/.tiktok/")
viper.AddConfigPath("./config")
viper.AddConfigPath("../../config")
viper.AddConfigPath("../../../config")
viper.AddConfigPath("../../../../config")
}
if isRemoteConfig {
if err := viper.ReadRemoteConfig(); err != nil {
klog.Fatalf("error reading config: %s", err)
}
klog.Infof("Using Remote Config: '%s'", configVar)
viper.WatchRemoteConfig()
// 另启动一个协程来监测远程配置文件
go config.WatchRemoteConf()
} else {
if err := viper.ReadInConfig(); err != nil {
klog.Fatalf("error reading config: %s", err)
}
klog.Infof("Using configuration file '%s'", viper.ConfigFileUsed())
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
klog.Info("Config file changed:", e.Name)
klog.Infof("Global.Source: '%s'", viper.GetString("Global.Source"))
klog.Infof("Global.ChangeMe: '%s'", viper.GetString("Global.ChangeMe"))
})
}
return &config
}