设置默认值
直接设置某个key的默认值,不需要从配置文件或环境变量获取,Viper 配置键不区分大小写
package main
import (
"fmt"
"github.com/spf13/viper"
)
func main() {
viper.SetDefault("ContentDir", "content")
viper.SetDefault("LayoutDir", "layouts")
viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "categories"})
content := viper.Get("ContentDir")
layouts := viper.Get("LayoutDir")
taxonomies := viper.Get("Taxonomies")
fmt.Println(content)
fmt.Println(layouts)
fmt.Println(taxonomies)
fmt.Println(taxonomies.(map[string]string)["tag"])
}
从目录读取配置文件
Viper 需要最少的配置,因此它知道在哪里查找配置文件。 Viper 支持 JSON、TOML、YAML、HCL、INI、envfile 和 Java 属性文件。 Viper 可以搜索多个路径,但目前单个 Viper 实例仅支持单个配置文件。 Viper 不会默认任何配置搜索路径,将默认决定留给应用程序。
以下是如何使用 Viper 搜索和读取配置文件的示例。 不需要任何特定路径,但应至少提供一个需要配置文件的路径。
package main
import (
"fmt"
"github.com/spf13/viper"
)
func main() {
viper.SetConfigName("config") // 文件名,不包含后缀
viper.SetConfigType("yaml") // 文件类型
viper.AddConfigPath("cfg1") // 配置文件目录
viper.AddConfigPath("cfg2") // 可添加多个配置文件目录,但如果cfg1目录下有config.yaml文件,则不会使用cfg2下的配置文件
err := viper.ReadInConfig() // 读取配置文件
if err != nil { // Handle errors reading the config file
panic(fmt.Errorf("Fatal error config file: %w \n", err))
}
/*
if err := viper.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
// Config file not found; ignore error if desired
} else {
// Config file was found but another error was produced
}
}
*/
aa := viper.Get("aa") // aa, bb只会有一个能获取到
bb := viper.Get("bb")
fmt.Println(aa, bb)
// 另一个实例去读取cfg2中的配置文件
cfg2()
}
func cfg2() {
v := viper.New()
v.SetConfigName("config")
v.SetConfigType("yaml")
v.AddConfigPath("cfg2")
fmt.Println(v.ReadInConfig())
fmt.Println(v.Get("bb"))
}
写入配置文件
- WriteConfig: 将当前 viper 配置写入预定义路径(如果存在)。 如果没有预定义路径,则会出错。 如果存在,将覆盖当前的配置文件。
- SafeWriteConfig: 将当前 viper 配置写入预定义路径。 如果没有预定义路径,则会出错。 不会覆盖当前的配置文件(如果存在)。
- WriteConfigAs: 将当前的 viper 配置写入给定的文件路径。 将覆盖给定的文件(如果存在)。
- SafeWriteConfigAs: 将当前 viper 配置写入给定的文件路径。 不会覆盖给定的文件(如果存在)。
用xxxAs写入时,需要设置文件类型,不需要设置文件名和目录,已在参数中包含
package main
import (
"fmt"
"github.com/spf13/viper"
)
func main() {
viper.SetConfigName("config") // 文件名,不包含后缀
viper.SetConfigType("yaml") // 文件类型
viper.AddConfigPath("cfg1") // 配置文件目录
err := viper.ReadInConfig() // 读取配置文件
if err != nil { // Handle errors reading the config file
panic(fmt.Errorf("Fatal error config file: %w \n", err))
}
viper.SetDefault("ContentDir", "content")
viper.SetDefault("LayoutDir", "layouts")
viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "categories"})
viper.WriteConfig() // writes current config to predefined path set by 'viper.AddConfigPath()' and 'viper.SetConfigName'
//viper.SafeWriteConfig()
//viper.WriteConfigAs("cfg1/config")
//viper.SafeWriteConfigAs("cfg1/config") // will error since it has already been written
//viper.SafeWriteConfigAs("cfg2/config")
}
热加载
不用自己使用goroutine去运行,WatchConfig会使用goroutine去运行,反应时间有点慢......
viper.OnConfigChange(func(e fsnotify.Event) {
fmt.Println("Config file changed:", e.Name)
})
viper.WatchConfig()
获取环境变量
使用 ENV 变量时,重要的是要认识到 Viper 将 ENV 变量视为区分大小写。
Viper 提供了一种机制来尝试确保 ENV 变量是唯一的。通过使用 SetEnvPrefix,您可以告诉 Viper 在读取环境变量时使用前缀。 BindEnv 和 AutomaticEnv 都将使用这个前缀。
BindEnv 接受一个或多个参数。第一个参数是键名,其余的是要绑定到这个键的环境变量的名称。如果提供了多个,它们将按指定的顺序优先。环境变量的名称区分大小写。如果未提供 ENV 变量名称,则 Viper 将自动假定 ENV 变量与以下格式匹配:前缀 + "_" + 全部大写的密钥名称。当您显式提供 ENV 变量名称(第二个参数)时,它不会自动添加前缀。例如,如果第二个参数是“id”,Viper 将查找 ENV 变量“ID”。
使用 ENV 变量时要认识到的一件重要事情是每次访问该值时都会读取该值。调用 BindEnv 时,Viper 不会修复该值。
AutomaticEnv 是一个强大的帮手,尤其是与 SetEnvPrefix 结合使用时。调用时,Viper 将在任何时候发出 viper.Get 请求时检查环境变量。它将应用以下规则。它将检查名称与密钥大写并以 EnvPrefix 为前缀的名称(如果已设置)的环境变量。
SetEnvKeyReplacer 允许您使用 strings.Replacer 对象在一定程度上重写 Env 键。如果您想在 Get() 调用中使用 - 或其他内容,但希望您的环境变量使用 _ 分隔符,这将非常有用。可以在 viper_test.go 中找到使用它的示例。
或者,您可以将 EnvKeyReplacer 与 NewWithOptions 工厂函数一起使用。与 SetEnvKeyReplacer 不同,它接受一个 StringReplacer 接口,允许您编写自定义字符串替换逻辑。
默认情况下,空环境变量被视为未设置,并将回退到下一个配置源。要将空环境变量视为设置,请使用 AllowEmptyEnv 方法。
此处通过SetEnvPrefix设置的前缀为spf,通过BindEnv绑定的值为ID,要获取的环境变量名就为SPF_ID,此处用代码设置了环境变量SPF_ID为13,也可以通过export设置来测试,比如,export SPF_NAME="myName"
os.Setenv("SPF_ID", "13")
viper.SetEnvPrefix("spf")
viper.BindEnv("id")
viper.BindEnv("name")
id := viper.Get("id")
name := viper.Get("name")
fmt.Println(id, name)
AutomaticEnv,SetEnvKeyReplacer
func main() {
// 使用环境变量,也可使用export设置
os.Setenv("VIPER_USER_SECRET_ID", "QLdywI2MrmDVjSSv6e95weNRvmteRjfKAuNV")
os.Setenv("VIPER_USER_SECRET_KEY", "bVix2WBv0VPfrDrvlLWrhEdzjLpPCNYb")
viper.AutomaticEnv() // 读取所有环境变量,如果有设置Prefix,则读取Prefix开头的环境变量
viper.SetEnvPrefix("VIPER") // 设置环境变量前缀:VIPER_,如果是viper,将自动转变为大写。
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_")) // 将viper.Get(key) key字符串中'.'和'-'替换为'_'
//viper.BindEnv("user.secret-key") // SetEnvKeyReplacer已将.和-替换成了_,所以此处的key在viper看来就是user_secret_key,设置了AutomaticEnv则不用此BindEnv,单独使用就用BindEnv
//viper.BindEnv("user.secret-id", "USER_SECRET_ID") // 绑定环境变量名到key
fmt.Println(viper.Get("user.secret-key"),viper.Get("user_secret_key")) // 两个获取到的是一样的
fmt.Println(viper.Get("USER_SECRET_ID"))
}
和pflag配合使用
pflag.Int("flagname", 1234, "help message for flagname")
pflag.Parse()
viper.BindPFlags(pflag.CommandLine)
i := viper.GetInt("flagname") // retrieve values from viper instead of pflag
从io.Reader中读取
出自n9e
package main
import (
"bytes"
"fmt"
"github.com/spf13/viper"
"io/ioutil"
"time"
)
type ConfigT struct {
Stra straSection `yaml:"stra"`
Worker workerSection `yaml:"worker"`
Enable enableSection `yaml:"enable"`
Job jobSection `yaml:"job"`
Report reportSection `yaml:"report"`
Udp UdpSection `yaml:"udp"`
Metrics MetricsSection `yaml:"metrics"`
Abcc string `yaml:"abcc"`
}
type UdpSection struct {
Enable bool `yaml:"enable"`
Listen string `yaml:"listen"`
}
type MetricsSection struct {
MaxProcs int `yaml:"maxProcs"`
ReportIntervalMs int `yaml:"reportIntervalMs"`
ReportTimeoutMs int `yaml:"reportTimeoutMs"`
ReportPacketSize int `yaml:"reportPacketSize"`
SendToInfoFile bool `yaml:"sendToInfoFile"`
Interval time.Duration
}
type enableSection struct {
Mon bool `yaml:"mon"`
Job bool `yaml:"job"`
Report bool `yaml:"report"`
Metrics bool `yaml:"metrics"`
}
type reportSection struct {
Token string `yaml:"token"`
Interval int `yaml:"interval"`
Cate string `yaml:"cate"`
UniqKey string `yaml:"uniqkey"`
SN string `yaml:"sn"`
Fields map[string]string `yaml:"fields"`
}
type straSection struct {
Enable bool `yaml:"enable"`
Interval int `yaml:"interval"`
Api string `yaml:"api"`
Timeout int `yaml:"timeout"`
PortPath string `yaml:"portPath"`
ProcPath string `yaml:"procPath"`
LogPath string `yaml:"logPath"`
}
type workerSection struct {
WorkerNum int `yaml:"workerNum"`
QueueSize int `yaml:"queueSize"`
PushInterval int `yaml:"pushInterval"`
WaitPush int `yaml:"waitPush"`
}
type jobSection struct {
MetaDir string `yaml:"metadir"`
Interval int `yaml:"interval"`
}
var (
Config ConfigT
)
func main() {
//结构体,配置文件均使用n9e源码
//两种方式读取均可
bs, err := ioutil.ReadFile("agent.yml")
//f, err := os.Open("agent.yml")
if err != nil {
fmt.Println(fmt.Errorf("cannot read yml[%s]: %v", "agent.yml", err))
}
//使用viper包解析配置文件,并进行设置
viper.SetConfigType("yaml")
//不设置配置文件目录,[]byte格式
err = viper.ReadConfig(bytes.NewBuffer(bs))
//err = viper.ReadConfig(f)
//结构体中有的字段都可以设置,设置完之后Unmarshal
viper.Set("abcc", "ooookk-------------")
//设置结构体内结构体某字段
viper.Set("enable", map[string]interface{}{
"mon": false,
})
_ = viper.Unmarshal(&Config)
fmt.Println(Config)
}
logger:
dir: logs/agent
level: INFO
keepHours: 24
enable:
mon: true
job: true
report: true
metrics: true
udp:
enable: true
listen: :788
metrics:
maxProcs: 1
reportIntervalMs: 10
reportTimeoutMs: 2000
reportPacketSize: 100
sendToInfoFile: false
job:
metadir: ./meta
interval: 2
report:
# 调用ams的接口上报数据,需要ams的token
token: ams-builtin-token
# 上报周期,单位是秒
interval: 10
# physical:物理机,virtual:虚拟机,container:容器,switch:交换机
cate: physical
# 使用哪个字段作为唯一KEY,即作为where条件更新对应记录,一般使用sn或ip
uniqkey: ip
# 如果是虚拟机,应该是获取uuid
# curl -s http://169.254.169.254/a/meta-data/instance-id
sn: dmidecode -s system-serial-number | tail -n 1
fields:
cpu: cat /proc/cpuinfo | grep processor | wc -l
mem: cat /proc/meminfo | grep MemTotal | awk '{printf "%dGi", $2/1024/1024}'
disk: df -m | grep '/dev/' | grep -v '/var/lib' | grep -v tmpfs | awk '{sum += $2};END{printf "%dGi", sum/1024}'
sys:
# timeout in ms
# interval in second
timeout: 5000
interval: 30
ifacePrefix:
- eth
- em
- ens
# ignore disk mount point
mountIgnore:
prefix:
- /var/lib
- /run
# collect anyway
exclude: []
ignoreMetrics:
- cpu.core.idle
- cpu.core.util
- cpu.core.sys
- cpu.core.user
- cpu.core.nice
- cpu.core.guest
- cpu.core.irq
- cpu.core.softirq
- cpu.core.iowait
- cpu.core.steal
n9e源码
具体内容:从src/modules/agent/agent.go的main函数开始
//入口
func main() {
//读取配置文件
parseConf()
//实现
func parseConf() {
//配置文件
if err := config.Parse(); err != nil {
fmt.Println("cannot parse configuration file:", err)
os.Exit(1)
}
}
//读取配置文件
func Parse() error {
//获取文件名,agent.yml
conf := getYmlFile()
//读取文件
bs, err := file.ReadBytes(conf)
if err != nil {
return fmt.Errorf("cannot read yml[%s]: %v", conf, err)
}
//使用viper包解析配置文件,并进行设置
viper.SetConfigType("yaml")
err = viper.ReadConfig(bytes.NewBuffer(bs))
if err != nil {
return fmt.Errorf("cannot read yml[%s]: %v", conf, err)
}
//设置worker字段为如下内容,其他类似
viper.SetDefault("worker", map[string]interface{}{
"workerNum": 10,
"queueSize": 1024000,
"pushInterval": 5,
"waitPush": 0,
})
viper.SetDefault("stra", map[string]interface{}{
"enable": true,
"timeout": 5000,
"interval": 10, //采集策略更新时间
"portPath": "./etc/port",
"procPath": "./etc/proc",
"logPath": "./etc/log",
"api": "/api/mon/collects/",
})
viper.SetDefault("sys", map[string]interface{}{
"enable": true,
"timeout": 1000, //请求超时时间
"interval": 10, //基础指标上报周期
"pluginRemote": true, //从monapi获取插件采集配置
"plugin": "./plugin",
})
viper.SetDefault("job", map[string]interface{}{
"metadir": "./meta",
"interval": 2,
})
//identify设置,ip/ident
if err = identity.Parse(); err != nil {
return err
}
var c ConfigT
err = viper.Unmarshal(&c)
if err != nil {
return fmt.Errorf("unmarshal config error:%v", err)
}
// 启动的时候就获取一下本机的identity,缓存起来以备后用,优点是性能好,缺点是机器唯一标识发生变化需要重启进程
ident, err := identity.GetIdent()
if err != nil {
return err
}
fmt.Println("identity:", ident)
if ident == "" || ident == "127.0.0.1" {
return fmt.Errorf("identity[%s] invalid", ident)
}
//全局配置,Endpoint为本机IP
Endpoint = ident
//获取meta dir绝对路径
c.Job.MetaDir = strings.TrimSpace(c.Job.MetaDir)
c.Job.MetaDir, err = file.RealPath(c.Job.MetaDir)
if err != nil {
return fmt.Errorf("get absolute filepath of %s fail %v", c.Job.MetaDir, err)
}
//保证目录存在,(mkdir该目录)
if err = file.EnsureDir(c.Job.MetaDir); err != nil {
return fmt.Errorf("mkdir -p %s fail: %v", c.Job.MetaDir, err)
}
Config = c
return nil
}
//返回文件名
func getYmlFile() string {
yml := "etc/agent.local.yml"
if file.IsExist(yml) {
return yml
}
yml = "etc/agent.yml"
if file.IsExist(yml) {
return yml
}
return ""
}