n9e源码阅读-agent-1-配置文件解析&viper使用

设置默认值

直接设置某个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 ""
}

更多

viper package - github.com/spf13/viper - pkg.go.dev

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值