viper of Go Daily Library

brief introduction

Introduction to the previous article cobra Mentioned when viper Today, let's talk about this library. viper is a configuration solution with rich features:

  • Supports configuration files in various formats, such as JSON/TOML/YAML/HCL/envfile/Java properties;
  • You can set up to listen for changes to configuration files and automatically load new configurations when they are modified.
  • Read configuration from environment variables, command line options, and io.Reader;
  • Read and monitor modifications, such as etcd/Consul, from a remote configuration system;
  • Set key values are displayed in code logic.

Quick use

Installation:

$ go get github.com/spf13/viper

Use:

package main

import (
  "fmt"
  "log"

  "github.com/spf13/viper"
)

func main() {
  viper.SetConfigName("config")
  viper.SetConfigType("toml")
  viper.AddConfigPath(".")
  viper.SetDefault("redis.port", 6381)
  err := viper.ReadInConfig()
  if err != nil {
    log.Fatal("read config failed: %v", err)
  }

  fmt.Println(viper.Get("app_name"))
  fmt.Println(viper.Get("log_level"))

  fmt.Println("mysql ip: ", viper.Get("mysql.ip"))
  fmt.Println("mysql port: ", viper.Get("mysql.port"))
  fmt.Println("mysql user: ", viper.Get("mysql.user"))
  fmt.Println("mysql password: ", viper.Get("mysql.password"))
  fmt.Println("mysql database: ", viper.Get("mysql.database"))

  fmt.Println("redis ip: ", viper.Get("redis.ip"))
  fmt.Println("redis port: ", viper.Get("redis.port"))
}

Before we use it go-ini Configuration used in this article, but in toml format instead. The syntax of toml is simple. For a quick start, see learn X in Y minutes.

app_name = "awesome web"

# possible values: DEBUG, INFO, WARNING, ERROR, FATAL
log_level = "DEBUG"

[mysql]
ip = "127.0.0.1"
port = 3306
user = "dj"
password = 123456
database = "awesome"

[redis]
ip = "127.0.0.1"
port = 7381

viper is very simple to use and requires very few settings.Set the file name (SetConfigName), configuration type (SetConfigType), and search path (AddConfigPath), then call ReadInConfig. Viper automatically reads the configuration based on type.When used, call the viper.Get method to get the key value.

Compile and run programs:

awesome web
DEBUG
mysql ip:  127.0.0.1
mysql port:  3306
mysql user:  dj
mysql password:  123456
mysql database:  awesome
redis ip:  127.0.0.1
redis port:  7381

There are a few points to note:

  • Do not suffix the file name;
  • Search paths can be set up more than one, and viper will find them in the order in which they are set.
  • viper gets the value in the form of section.key, which passes in a nested key name;
  • The default value can call the viper.SetDefault setting.

Read Key

viper provides several forms of reading.In the example above, we see the use of the Get method.The Get method returns a value for interface {}, which is inconvenient to use.

The GetType family method returns a value of the specified type. Where Type can be Bool/Float64/Int/String/Time/Duration/IntSlice/StringSlice. Note, however, that the GetType method returns a zero value for the corresponding type if the specified key does not exist or is of the wrong type.

If you want to determine whether a key exists, use the IsSet method. In addition, GetStringMap and GetStringMapString return all key-value pairs directly under a key with map, the former returns map[string]interface{}, and the latter returns map[string]string. AllSettings returns all settings as map[string]interface{}.

// Omit package name and import section

func main() {
  viper.SetConfigName("config")
  viper.SetConfigType("toml")
  viper.AddConfigPath(".")
  err := viper.ReadInConfig()
  if err != nil {
    log.Fatal("read config failed: %v", err)
  }

  fmt.Println("protocols: ", viper.GetStringSlice("server.protocols"))
  fmt.Println("ports: ", viper.GetIntSlice("server.ports"))
  fmt.Println("timeout: ", viper.GetDuration("server.timeout"))

  fmt.Println("mysql ip: ", viper.GetString("mysql.ip"))
  fmt.Println("mysql port: ", viper.GetInt("mysql.port"))

  if viper.IsSet("redis.port") {
    fmt.Println("redis.port is set")
  } else {
    fmt.Println("redis.port is not set")
  }

  fmt.Println("mysql settings: ", viper.GetStringMap("mysql"))
  fmt.Println("redis settings: ", viper.GetStringMap("redis"))
  fmt.Println("all settings: ", viper.AllSettings())
}

We add protocols and ports configurations in the configuration file config.toml:

[server]
protocols = ["http", "https", "port"]
ports = [10000, 10001, 10002]
timeout = 3s

Compile, run programs, output:

protocols:  [http https port]
ports:  [10000 10001 10002]
timeout:  3s
mysql ip:  127.0.0.1
mysql port:  3306
redis.port is set
mysql settings:  map[database:awesome ip:127.0.0.1 password:123456 port:3306 user:dj]
redis settings:  map[ip:127.0.0.1 port:7381]
all settings:  map[app_name:awesome web log_level:DEBUG mysql:map[database:awesome ip:127.0.0.1 password:123456 port:3306 user:dj] redis:map[ip:127.0.0.1 port:7381] server:map[ports:[10000 10001 10002] protocols:[http https port]]]

If redis.port in the configuration is commented out, redis.port is not set.

The example above also demonstrates how to use the time.Duration type, as long as it is in a format accepted by time.ParseDuration, such as 3s, 2min, 1min30s, and so on.

set key value

viper supports multiple settings, read in the following order:

  • Call Set to display settings;
  • Command line options;
  • Environment variables;
  • Configuration files;
  • Default value.

viper.Set

If a key has a value set through viper.Set, that value has the highest priority.

viper.Set("redis.port", 5381)

If you put the above line of code into the program and run the program, the output redis.port will be 5381.

Command Line Options

If a key does not display a set value through viper.Set, it will attempt to read from the command line options when it is acquired. If so, use first.viper uses the pflag library to parse options. We first define the options in the init method and call the viper.BindPFlags binding option into the configuration:

func init() {
  pflag.Int("redis.port", 8381, "Redis port to connect")

  // Bind Command Line
  viper.BindPFlags(pflag.CommandLine)
}

The pflag.Parse resolution option is then called at the beginning of the main method.

Compile and run programs:

$ ./main.exe --redis.port 9381
awesome web
DEBUG
mysql ip:  127.0.0.1
mysql port:  3306
mysql user:  dj
mysql password:  123456
mysql database:  awesome
redis ip:  127.0.0.1
redis port:  9381

How not to pass in options:

$ ./main.exe
awesome web
DEBUG
mysql ip:  127.0.0.1
mysql port:  3306
mysql user:  dj
mysql password:  123456
mysql database:  awesome
redis ip:  127.0.0.1
redis port:  7381

Note that the default value of the option redis.port is not used here.

However, if the key value cannot be obtained by either of the following methods, return the option default value (if any).Try commenting out redis.port in the configuration file to see what happens.

environment variable

If no key value has been previously obtained, an attempt will be made to read from the environment variable.We can bind one or all of them automatically.

Call the AutomaticEnv method in the init method to bind all environment variables:

func init() {
  // Binding environment variables
  viper.AutomaticEnv()
}

To verify that the binding was successful, we printed out the environment variable GOPATH in the main method:

func main() {
  // Omit some code

  fmt.Println("GOPATH: ", viper.Get("GOPATH"))
}

Create a new environment variable named redis.port with a value of 10381 from System->Advanced Settings->New. Run the program with the redis.port value of 10381 and GOPATH information in the output.

You can also bind environment variables individually:

func init() {
  // Binding environment variables
  viper.BindEnv("redis.port")
  viper.BindEnv("go.path", "GOPATH")
}

func main() {
  // Omit some code
  fmt.Println("go path: ", viper.Get("go.path"))
}

The BindEnv method is called, and if only one parameter is passed in, it represents both the key name and the environment variable name. If two parameters are passed in, the first parameter represents the key name and the second parameter represents the environment variable name.

You can also set the environment variable prefix through the viper.SetEnvPrefix method, which binds the environment variable through AutomaticEnv and a BindEnv parameter. When Get is used, viper automatically prefixes it and looks for it from the environment variable.

If the corresponding environment variable does not exist, viper automatically uppercase all the key names and look them up again.Therefore, using the key name gopath can also read the value of the environment variable GOPATH.

configuration file

If the key cannot be found through the previous path, viper will then try to find it from the configuration file. To avoid the impact of environment variables, you need to delete redis.port.

see Quick use Example in.

Default value

On top Quick use In this section, we've seen how to set default values, so let's not go into that.

Read Configuration

Read from io.Reader

viper supports reading configurations from io.Reader.This is a flexible form that can come from files, strings generated in programs, or even byte streams read from network connections.

package main

import (
  "bytes"
  "fmt"
  "log"

  "github.com/spf13/viper"
)

func main() {
  viper.SetConfigType("toml")
  tomlConfig := []byte(`
app_name = "awesome web"

# possible values: DEBUG, INFO, WARNING, ERROR, FATAL
log_level = "DEBUG"

[mysql]
ip = "127.0.0.1"
port = 3306
user = "dj"
password = 123456
database = "awesome"

[redis]
ip = "127.0.0.1"
port = 7381
`)
  err := viper.ReadConfig(bytes.NewBuffer(tomlConfig))
  if err != nil {
    log.Fatal("read config failed: %v", err)
  }

  fmt.Println("redis port: ", viper.GetInt("redis.port"))
}

Unmarshal

viper supports configuring Unmarshal into a structure to assign values to the corresponding fields in the structure.

package main

import (
  "fmt"
  "log"

  "github.com/spf13/viper"
)

type Config struct {
  AppName  string
  LogLevel string

  MySQL    MySQLConfig
  Redis    RedisConfig
}

type MySQLConfig struct {
  IP       string
  Port     int
  User     string
  Password string
  Database string
}

type RedisConfig struct {
  IP   string
  Port int
}

func main() {
  viper.SetConfigName("config")
  viper.SetConfigType("toml")
  viper.AddConfigPath(".")
  err := viper.ReadInConfig()
  if err != nil {
    log.Fatal("read config failed: %v", err)
  }

  var c Config
  viper.Unmarshal(&c)

  fmt.Println(c.MySQL)
}

Compile, run the program, output:

{127.0.0.1 3306 dj 123456 awesome}

Save Configuration

Sometimes, we want to save the configuration generated in the program, or the changes made.viper provides interface!

  • WriteConfig: Writes the current viper configuration to a predefined path and returns an error if there is no predefined path.The current configuration will be overwritten;
  • SafeWriteConfig: Same functionality as above, but it will not be overwritten if a configuration file exists;
  • WriteConfigAs: Save the configuration to the specified path and overwrite it if the file exists;
  • SafeWriteConfig: Same functionality as above, but if the shareholding profile exists, it will not be overwritten.

Here we programmatically generate a config.toml configuration:

package main

import (
  "log"

  "github.com/spf13/viper"
)

func main() {
  viper.SetConfigName("config")
  viper.SetConfigType("toml")
  viper.AddConfigPath(".")

  viper.Set("app_name", "awesome web")
  viper.Set("log_level", "DEBUG")
  viper.Set("mysql.ip", "127.0.0.1")
  viper.Set("mysql.port", 3306)
  viper.Set("mysql.user", "root")
  viper.Set("mysql.password", "123456")
  viper.Set("mysql.database", "awesome")

  viper.Set("redis.ip", "127.0.0.1")
  viper.Set("redis.port", 6381)

  err := viper.SafeWriteConfig()
  if err != nil {
    log.Fatal("write config failed: ", err)
  }
}

Compile and run the program, resulting in the following files:

app_name = "awesome web"
log_level = "DEBUG"

[mysql]
  database = "awesome"
  ip = "127.0.0.1"
  password = "123456"
  port = 3306
  user = "root"

[redis]
  ip = "127.0.0.1"
  port = 6381

Listen for file changes

viper can listen for file modifications and hot-load configurations.Therefore, you do not need to restart the server for the configuration to take effect.

package main

import (
  "fmt"
  "log"
  "time"

  "github.com/spf13/viper"
)

func main() {
  viper.SetConfigName("config")
  viper.SetConfigType("toml")
  viper.AddConfigPath(".")
  err := viper.ReadInConfig()
  if err != nil {
    log.Fatal("read config failed: %v", err)
  }

  viper.WatchConfig()

  fmt.Println("redis port before sleep: ", viper.Get("redis.port"))
  time.Sleep(time.Second * 10)
  fmt.Println("redis port after sleep: ", viper.Get("redis.port"))
}

Simply call viper.WatchConfig, and viper automatically listens for configuration changes.If there are changes, reload the configuration.

In the above program, we first print the value of redis.port, then Sleep 10s.Modify the redis.port value in the configuration during this time and print again when Sleep is finished. Found printed modified values:

redis port before sleep:  7381
redis port after sleep:  73810

In addition, you can add a callback for configuration modifications:

viper.OnConfigChange(func(e fsnotify.Event) {
  fmt.Printf("Config file:%s Op:%s\n", e.Name, e.Op)
})

This callback will be executed when the file is modified.

viper uses fsnotify This library implements the ability to listen for file modifications.

See the full sample code GitHub.

Reference resources

  1. viper GitHub warehouse

I

My Blog

Welcome to my WeChat Public Number GoUpUp to learn and progress together.

>This article is a multi-article blog platform OpenWrite Release!

Tags: Programming Redis MySQL Database github

Posted on Sun, 19 Jan 2020 20:30:37 -0500 by tukon