Xconf
Dead simple yet complete and powerful configuration manager for Go.
Install / Use
/learn @sandwich-go/XconfREADME
XCONF
Golang配置文件加载解析, goconf v2,扩充了功能支持。
功能简介
- 支持默认值配置、解析
- 支持多种格式,内置JSON, TOML, YAML,FLAG, ENV支持,并可注册解码器扩展格式支持
- 支持多文件、多
io.Reader数据加载,支持文件继承 - 支持由OS ENV变量数据加载配置
- 支持由命令行参数FLAGS加载数据
- 支持由远程URL加载配置数据
- 支持数据覆盖合并,加载多份数据时将按照加载文件的顺序按
FieldPath自动合并 - 支持通过
${READ_TIMEOUT|5s}、${IP_ADDRESS}等方式绑定Env参数 - 支持配置热加载、实时同步,内置内存热加载支持,支持异步更新通知, 支持xconf-providers: ETCD、文件系统.
- 支持
WATCH具体的FieldPath变动 - 支持导出配置到多种配置文件
- 支持配置HASH,便于比对配置一致性
FLAGS、ENV、FieldPath支持复杂类型,支持自定义复杂类型扩展支持- 支持配置访问秘钥
- 支持自定义基于Label的灰度更新
- 支持数值别名,如:
math.MaxInt,runtime.NumCPU - 支持",squash"将子结构体的字段提到父结构中便于做配置扩充
名词解释
FieldTagxconf在将配置由Strut与JSON, TOML, YAML,FLAG, ENV等转换时使用的字段别名,如:示例配置中的HttpAddress的FieldTag为http_address- 如果没有配置
xconf:"http_address",则默认会采用字段名的SnakeCase作为FieldTag,可以通过xconf.WithFieldTagConvertor指定为其他方案,如字段名的小写等,注意FieldTag策略必须与配置源中使用的字符串一致,否则会导致解析数据失败。
FieldPath,由FieldTag组成的Field访问路径,如示例配置中的Config.SubTest.HTTPAddress的FieldPath为config.sub_test.http_address.Leaf,xconf中配置的最小单位,基础类型、slice类型都是最小单位,Struct不是配置的最小单位,会根据配置的属性字段进行赋值、覆盖。- 默认情况下map是
xconf配置的最小单位,但是可以通过指定notleaf标签使map不作为最小单位,而是基于key进行合并.但是这种情况下map的Value依然是xconf中的最小单位,即使value是Struct也将作为配置合并的最小单位 - 通过
xconf.WithMapMerge(true)可以激活MapMerge模式,在这个模式下map及其Value都不再是配置的最小单位,配置的最小单位为基础类型和slice类型。
- 默认情况下map是
快速开始
定义配置结构
- 参考xconf/tests/conf.go使用optiongen定义配置并指定
--xconf=true以生成支持xconf需求的标签. - 自定义结构,指定
xconf需求的标签
type Server struct {
Timeouts map[string]time.Duration `xconf:"timeouts"`
}
type SubTest struct {
HTTPAddress string `xconf:"http_address"`
MapNotLeaf map[string]int `xconf:"map_not_leaf,notleaf"`
Map2 map[string]int `xconf:"map2"`
Map3 map[string]int `xconf:"map3"`
Slice2 []int64 `xconf:"slice2"`
Servers map[string]Server `xconf:"servers,notleaf"`
}
type Config struct {
HttpAddress string `xconf:"http_address"`
Map1 map[string]int `xconf:"map1"`
MapNotLeaf map[string]int `xconf:"map_not_leaf,notleaf"`
TimeDurations []time.Duration `xconf:"time_durations"`
Int64Slice []int64 `xconf:"int64_slice"`
Float64Slice []float64 `xconf:"float64_slice"`
Uin64Slice []uint64 `xconf:"uin64_slice"`
StringSlice []string `xconf:"string_slice"`
ReadTimeout time.Duration `xconf:"read_timeout"`
SubTest SubTest `xconf:"sub_test"`
}
从文件载入配置
以yaml格式为例(tests/)
http_address: :3002
read_timeout: 100s
default_empty_map:
test1: 1
map1:
test1: 1000000
map_not_leaf:
test1: 1000000
int64_slice:
- 1
- 2
sub_test:
map2:
${IP_ADDRESS}: 2222
map_not_leaf:
test2222: 2222
servers:
s1:
timeouts:
read: ${READ_TIMEOUT|5s}
参考:tests/main/main.go,文件间的继承通过
xconf_inherit_files指定,参考tests/main/c2.toml
cc := NewTestConfig(
xconf.WithFiles("c2.toml"), // 由指定的文件加载配置
xconf.WithReaders(bytes.NewBuffer(yamlContents),bytes.NewBuffer(tomlContents),xconf.NewRemoteReader("http://127.0.0.1:9001/test.json", time.Duration(5)*time.Second)), // 由指定的reader加载配置
xconf.WithFlagSet(flag.CommandLine), // 指定解析flag.CommandLine,默认值
xconf.WithEnviron(os.Environ()), // 指定解析os.Environ(),默认值
)
xconf.Parse(cc)
文件级继承
通过 xconf_inherit_files 可以完整继承其他文件的配置内容,并在当前配置文件内覆盖差异化内容。
支持不同文件格式的继承(如 yaml / toml / json 等),被继承文件路径为相对于当前文件的路径,例如:./conf/base.yaml
xconf_inherit_files = ["c1.yaml"]
使用场景
- 环境差异配置:如 dev / test / prod 共享基础配置,仅覆盖少量差异字段
- 配置复用:多个服务复用同一基础配置,避免重复维护
- 模块化配置管理:将通用配置拆分为独立文件,提升可读性与维护性
继承规则
-
加载顺序
- 按
xconf_inherit_files中的顺序依次加载 - 后加载的文件会覆盖前面的同名字段
- 按
-
当前文件优先级最高
- 当前文件中的配置会覆盖所有继承文件中的同名字段
-
字段合并规则 参考上述文当中的
Leaf定义,Leaf为xconf中配置的最小单位,也是最小覆盖单位。
示例
base.yaml
server:
host: 127.0.0.1
port: 8080
db:
user: root
password: 123456
dev.yaml
xconf_inherit_files = ["./base.yaml"]
server:
port: 8081
db:
password: dev_pass
合并结果
server:
host: 127.0.0.1
port: 8081
db:
user: root
password: dev_pass
多文件继承示例
xconf_inherit_files = [
"./base.yaml",
"./feature.yaml"
]
加载顺序:
base.yaml → feature.yaml → 当前文件
注意事项
- 避免循环继承(A 继承 B,B 又继承 A)
- 路径必须正确,否则会导致加载失败
- 不同格式文件需保证结构兼容(字段语义一致)
- 建议将通用配置抽离到
base文件,减少重复定义
配置存入文件
// SaveToFile 将内置解析的数据dump到文件,根据文件后缀选择codec
func SaveToFile(fileName string) error
// SaveToWriter 将内置解析的数据dump到writer,类型为ct
func SaveToWriter(ct ConfigType, writer io.Writer) error
// SaveVarToFile 将外部传入的valPtr,写入到fileName中,根据文件后缀选择codec
func SaveVarToFile(valPtr interface{}, fileName string) error
// SaveVarToWriter 将外部传入的valPtr,写入到writer中,类型为ct
func SaveVarToWriter(valPtr interface{}, ct ConfigType, writer io.Writer) error
// MustSaveToFile 将内置解析的数据dump到文件,根据文件后缀选择codec,如发生错误会panic
func MustSaveToFile(f string)
// MustSaveToWriter 将内置解析的数据dump到writer,需指定ConfigType,如发生错误会panic
func MustSaveToWriter(ct ConfigType, writer io.Writer)
// MustSaveVarToFile 将外部传入的valPtr,写入到fileName中,根据文件后缀选择codec
func MustSaveVarToFile(v interface{}, f string)
// MustSaveVarToWriter 将外部传入的valPtr,写入到writer中,类型为ct
func MustSaveVarToWriter(v interface{}, ct ConfigType, w io.Writer)
// MustSaveToBytes 将内置解析的数据以字节流返回,需指定ConfigType
func MustSaveToBytes(ct ConfigType) []byte { return xx.MustSaveToBytes(ct) }
// SaveVarToWriterAsYAML 将内置解析的数据解析到yaml,带comment
func SaveVarToWriterAsYAML(valPtr interface{}, writer io.Writer) error
可用选项
WithFiles: 指定加载的文件,配置覆盖顺序依赖传入的文件顺序WithReaders: 指定加载的io.Reader,配置覆盖顺序依赖传入的io.Reader顺序。WithFlagSet: 指定解析的FlagSet,默认是全局的flag.CommandLine,如指定为nil则不会将参数自动创建到FlagSet,同样也不会解析FlagSet内数据。WithFlagArgs: 指定FlagSet解析的参数数据,默认为os.Args[1:]WithFlagValueProvider:FlagSet支持的类型有限,xconf/xflag/vars中扩展了部分类型,参考[Flag 与 Env支持]WithEnviron: 指定环境变量值WithErrorHandling:指定错误处理方式,同flag.CommandLine处理方式WithLogDebug: 指定debug日志输出WithLogWarning: 指定warn日志输出WithFieldTagConvertor: 当无法通过TagName获取FieldTag时,通过该方法转换,默认SnakeCase.WithTagName:FieldTag字段来源的Tag名,默认xconfWithTagNameDefaultValue: 默认值使用的Tag名称 ,默认defaultWithParseDefault:是否解析默认值,默认true,推荐使用optiongen生成默认配置数据WithDebug: 调试模式,会输出详细的解析流程日志WithDecoderConfigOption: 调整mapstructure参数,xconf使用mapstructure进行类型转换FieldPathDeprecated: 弃用的配置,解析时不会报错,但会打印warning日志ErrEnvBindNotExistWithoutDefault: EnvBind时如果Env中不存在指定的key而且没有指定默认值时报错FieldFlagSetCreateIgnore: 指定的FieldPath或者类型名在没有Flag Provider的时候,不打印报警日志
Flag 与 Env支持
- 支持Flag中通过
xconf_files指定配置文件 xconf/xflag/vars中扩展了部分类型如下:- float32,float64
- int,int8,int16,int32,int64
- uint,uint8,uint16,uint32,uint64
- []float32,[]float64
- []int,[]int8,[]int16,[]int32,[]int64
- []uint,[]uint8,[]uint16,[]uint32,[]uint64
- []string
- []Duration
- map[stirng]string,map[int]int,map[int64]int64,map[int64]string,map[stirng]int,map[stirng]int64,map[stirng]Duration
- 扩展类型Slice与Map配置
- slcie的定义方式为元素通过
vars.StringValueDelim分割,默认为,,如:--time_durations=5s,10s,100s - map的定位方式为K、V通过
vars.StringValueDelim分割,默认为,,如:--sub_test.map_not_leaf=k1,1,k2,2,k3,3
- slcie的定义方式为元素通过
- 自定义扩展
- 扩展需要实现
flag.Getter接口,可以通过实现Usage() string实现自定义的Usage信息。const JsnoPrefix = "json@" type serverProvider struct { s string set bool data *map[string]Server } func (sp *serverProvider) String() string { return sp.s } func (sp *serverProvider) Set(s string) error { sp.s = s if sp.set == false { *sp.data = make(map[string]Server) } if !strings.HasPrefix(s, JsnoPrefix) { return errors.New("server map need json data with prefix:" + JsnoPrefix) } s = strings.TrimPrefix(s, JsnoPrefix) return json.Unmarshal([]byte(s), sp.data) } func (sp *serverProvider) Get() interface{} { ret := make(map[string]interface{}) for k, v := range *sp.data { ret[k] = v } return ret } func (sp *serverProvider) Usage() string { return fmt.Sprintf("server map, json format") } func newServerProvider(v interface{}) flag.Getter { return &serverProvider{data: v.(*map[string]Server)} } - 注册扩展
vars.SetProviderByFieldPath通过FieldPath设定扩展vars.SetProviderByFieldType通过字段类型名称设定扩展
cc := &Config{} jsonServer := `json@{"s1":{"timeouts":{"read":5000000000},"timeouts_not_leaf":{"write":5000000000}}}` x := xconf.New( xconf.WithFlagSet(flag.NewFlagSet("xconf-test", flag.ContinueOnError)), - 扩展需要实现
