Introduction to the
Viper was mentioned in the last article about Cobra, and today we’re going to look at this library. Viper is a configuration solution with rich features:
- Support JSON/TOML/YAML/HCL/envfile/Java properties that a variety of formats such as configuration file;
- You can configure to listen for modifications to the configuration file, and automatically load new configurations when the configuration file is modified.
- From environment variables, command-line options, and
io.Reader
Read configuration; - Read and listen for changes from a remote configuration system, such as etcd/Consul;
- The set key value is displayed in the code logic.
Quick to use
Installation:
$ go get github.com/spf13/viper
Copy the code
Use:
package main
import (
"fmt"
"log"
"github.com/spf13/viper"
)
func main(a) {
viper.SetConfigName("config")
viper.SetConfigType("toml")
viper.AddConfigPath(".")
viper.SetDefault("redis.port".6381)
err := viper.ReadInConfig()
iferr ! =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"))}Copy the code
We use the same configuration used in the previous Go Daily Library go-INI article, but in TOML format. Toml grammar is simple. For a quick introduction, 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
Copy the code
Viper is very simple to use and requires very few Settings. Set the file name (SetConfigName), configuration type (SetConfigType), and search path (AddConfigPath), and then call ReadInConfig. Viper automatically reads the configuration by type. Call the viper.Get method to Get the key value.
Compile and run a program:
Awesome Web DEBUG mysql IP: 127.0.0.1 mysql port: 3306 mysql user: DJ mysql password: 123456 Awesome Redis IP: 127.0.0.1 Redis port: 7381Copy the code
A few points to note:
- Do not add a suffix to the file name;
- Multiple search paths can be set, and Viper will search according to the set order;
- Viper used when obtaining a value
section.key
, passing in nested key names; - Default values can be called
viper.SetDefault
Settings.
Read the key
Viper provides multiple forms of reading methods. In the example above, we saw the use of the Get method. The Get method returns a value for interface{}, which is inconvenient to use.
The GetType series of methods can return a value of a specified type. Among them, the Type to Bool Float64 / Int/String/Time/Duration/IntSlice/StringSlice. Note, however, that if the specified key does not exist or is of the wrong type, the GetType method returns a zero value for the corresponding type.
To determine whether a key exists, use the IsSet method. In addition, GetStringMap and GetStringMapString directly return all key-value pairs below a certain key as a map. The former returns map[string]interface{} and the latter returns map[string]string. AllSettings returns AllSettings with map[string]interface{}.
// Omit the package name and import section
func main(a) {
viper.SetConfigName("config")
viper.SetConfigType("toml")
viper.AddConfigPath(".")
err := viper.ReadInConfig()
iferr ! =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())
}
Copy the code
We add the Protocols and ports configuration to the configuration file config.toml:
[server]
protocols = ["http"."https"."port"]
ports = [10000.10001.10002]
timeout = 3s
Copy the code
Compile and run the program, output:
Protocols: [HTTP HTTPS port] ports: [10000 10001 10002] timeout: 3s mysql IP: 127.0.0.1 mysql port: 3306 redis.port issetMysql Settings: map[database:awesome IP :127.0.0.1 Password :123456 Port :3306 User: DJ] The 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]]Copy the code
If you comment out redis. Port in the configuration, redis. Port is not set.
The example above also demonstrates how to use the time.duration type in any format accepted by time.parseduration, such as 3s, 2min, 1min30s, and so on.
Set the key value
Viper supports setting in multiple places, using the following sequence of readings:
- call
Set
Displaying Settings; - Command line options;
- Environmental variables;
- Configuration file;
- The default value.
viper.Set
If a key has a value Set via viper.set, then that value has the highest priority.
viper.Set("redis.port".5381)
Copy the code
If you put the above line of code into a program and run the program, the output redis.port will be 5381.
Command-line options
If a key does not display a Set value via viper.set, then the fetch will attempt to read from the command line option. If so, use preferentially. Viper uses the PFlag library to resolve options. We first define the option in the init method and call viper.bindpFlags to bind the option to the configuration:
func init(a) {
pflag.Int("redis.port".8381."Redis port to connect")
// Bind the command line
viper.BindPFlags(pflag.CommandLine)
}
Copy the code
The pflag.parse option is then called at the beginning of the main method.
Compile and run a program:
$ ./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
Copy the code
How not to pass 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: 7381Copy the code
Note that the default value of the option redis.port is not used.
However, if the key value cannot be obtained by any of the following methods, the option default value (if any) is returned. Try commenting out redis.port in the configuration file to see how it works.
The environment variable
If the key value is not obtained before, an attempt is made to read it from the environment variable. We can either bind individually or automatically bind all of them.
Call AutomaticEnv in init method to bind all environment variables:
func init(a) {
// Bind environment variables
viper.AutomaticEnv()
}
Copy the code
To verify the binding, print out the environment variable GOPATH in the main method:
func main(a) {
// Omit part of the code
fmt.Println("GOPATH: ", viper.Get("GOPATH"))}Copy the code
Go to System > Advanced Settings > New to create an environment variable named redis.port with the value 10381. Run the program and output the redis.port value 10381 with GOPATH information in the output.
You can also bind environment variables separately:
func init(a) {
// Bind environment variables
viper.BindEnv("redis.port")
viper.BindEnv("go.path"."GOPATH")}func main(a) {
// Omit part of the code
fmt.Println("go path: ", viper.Get("go.path"))}Copy the code
Call the BindEnv method with only one parameter, which represents both the key name and the environment variable name. If you pass two arguments, the first argument represents the key name and the second the environment variable name.
The viper.setenvprefix method can also be used to set the prefix of the environment variable, so that when using Get, viper will automatically prefix the environment variable bound by the BindEnv of AutomaticEnv and a parameter and then look it up from the environment variable.
If the corresponding environment variable does not exist, Viper will automatically uppercase the key name and look again. Therefore, the value of the environment variable gopath can also be read using the key gopath.
The configuration file
If the key is not found in the previous approach, Viper will then try to find it in the configuration file. To avoid the impact of environment variables, you need to delete the environment variable redis.port.
See an example in quick use.
The default value
In the Quick Use section above, we saw how to set the default values, so we won’t go into that here.
Reading configuration
fromio.Reader
Reads the
Viper supports reading configuration from IO.Reader. This form is flexible and can come from a file, a string generated in a program, or even a byte stream read from a network connection.
package main
import (
"bytes"
"fmt"
"log"
"github.com/spf13/viper"
)
func main(a) {
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))
iferr ! =nil {
log.Fatal("read config failed: %v", err)
}
fmt.Println("redis port: ", viper.GetInt("redis.port"))}Copy the code
Unmarshal
Viper supports configuring Unmarshal into a structure and assigning 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(a) {
viper.SetConfigName("config")
viper.SetConfigType("toml")
viper.AddConfigPath(".")
err := viper.ReadInConfig()
iferr ! =nil {
log.Fatal("read config failed: %v", err)
}
var c Config
viper.Unmarshal(&c)
fmt.Println(c.MySQL)
}
Copy the code
Compile, run a program, output:
{127.0.0.1 3306 dj 123456 awesome}
Copy the code
Save the configuration
Sometimes, we want to save the configuration generated in the program, or the changes made. Viper provides interfaces!
WriteConfig
: writes the current Viper configuration to a predefined path. If no predefined path exists, an error is returned. The current configuration will be overwritten.SafeWriteConfig
: The function is the same as the above, but if the configuration file exists, the configuration file is not overwritten.WriteConfigAs
: Saves the configuration file to the specified path. If the file exists, the file is overwritten.SafeWriteConfigAs
: The function is the same as the above, but the configuration file is not overwritten.
Here we programmatically generate a config.toml configuration:
package main
import (
"log"
"github.com/spf13/viper"
)
func main(a) {
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()
iferr ! =nil {
log.Fatal("write config failed: ", err)
}
}
Copy the code
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 = 6381Copy the code
Listen for file changes
Viper can listen for file changes 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(a) {
viper.SetConfigName("config")
viper.SetConfigType("toml")
viper.AddConfigPath(".")
err := viper.ReadInConfig()
iferr ! =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"))}Copy the code
Just call viper.watchConfig and Viper will listen for configuration changes. If there are changes, reload the configuration.
In the above program, we print the value of redis.port and then Sleep 10s. During this time, modify the value of redis.port in the configuration, and print again after Sleep. Found the modified value printed:
redis port before sleep: 7381
redis port after sleep: 73810
Copy the code
In addition, you can add a callback for configuration changes:
viper.OnConfigChange(func(e fsnotify.Event) {
fmt.Printf("Config file:%s Op:%s\n", e.Name, e.Op)
})
Copy the code
This callback is executed when the file is modified.
Viper uses the fsnotify library to listen for file changes.
See GitHub for the full sample code.
reference
- 9 making warehouse
I
My blog
Welcome to follow my wechat public account [GoUpUp], learn together, make progress together ~
This article is published by OpenWrite!