printLoadedConf modified to only print explicitly set config values (via env or config file)
This commit is contained in:
1 parent
d6688a6754
commit
803a8929e6
3 files changed
+122
-7
No files matched your search
@@ -24,17 +24,21 @@ func printEnv() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// printLoadedConfig logs every key Viper has loaded, with its value and type,
|
// printLoadedConfig logs every key set by the config file or an environment
|
||||||
// at debug level.
|
// variable (see isExplicitlySet), with its value and type, at debug level.
|
||||||
func printLoadedConfig() {
|
func printLoadedConfig() {
|
||||||
slog.Debug("Loaded config:")
|
slog.Debug("Loaded config:")
|
||||||
keys := viper.AllKeys()
|
keys := viper.AllKeys()
|
||||||
for i := range keys {
|
for i := range keys {
|
||||||
k := keys[i]
|
k := keys[i]
|
||||||
v := viper.Get(k)
|
if isExplicitlySet(k) {
|
||||||
t := reflect.TypeOf(v)
|
v := viper.Get(k)
|
||||||
slog.Debug(
|
t := reflect.TypeOf(v)
|
||||||
"\t" + k + " = " + fmt.Sprintf("%#v", v) + " <" + t.Kind().String() + ">")
|
slog.Debug(
|
||||||
|
"\t" + k + " = " + fmt.Sprintf("%#v", v) +
|
||||||
|
" <" + t.Kind().String() + ">",
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+24
-1
@@ -3,12 +3,35 @@ package config
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// envKeyReplacer turns a dotted key into its environment variable's suffix
|
||||||
|
// (logging.level to LOGGING_LEVEL, once upper-cased).
|
||||||
|
var envKeyReplacer = strings.NewReplacer(".", "_")
|
||||||
|
|
||||||
|
// envVarName returns the environment variable that sets key, e.g.
|
||||||
|
// JOBSEARCH_LOGGING_LEVEL for logging.level.
|
||||||
|
func envVarName(key string) string {
|
||||||
|
return EnvPrefix + "_" + strings.ToUpper(envKeyReplacer.Replace(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// isExplicitlySet reports whether key's value in Viper was set by the config
|
||||||
|
// file or by its environment variable, rather than coming from its default.
|
||||||
|
// As Viper does, it ignores an empty environment variable. Values set with
|
||||||
|
// viper.Set (e.g. by StoreValue) are not counted.
|
||||||
|
func isExplicitlySet(key string) bool {
|
||||||
|
if viper.InConfig(key) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
value, ok := os.LookupEnv(envVarName(key))
|
||||||
|
return ok && value != ""
|
||||||
|
}
|
||||||
|
|
||||||
// loadEnv loads environment variables from a .env file in the working
|
// loadEnv loads environment variables from a .env file in the working
|
||||||
// directory.
|
// directory.
|
||||||
func loadEnv() error {
|
func loadEnv() error {
|
||||||
@@ -37,7 +60,7 @@ func loadConfig(cfgFile string) error {
|
|||||||
LoadDefaultConfig()
|
LoadDefaultConfig()
|
||||||
|
|
||||||
viper.SetEnvPrefix(EnvPrefix)
|
viper.SetEnvPrefix(EnvPrefix)
|
||||||
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
viper.SetEnvKeyReplacer(envKeyReplacer)
|
||||||
viper.AutomaticEnv() // read in environment variables that match
|
viper.AutomaticEnv() // read in environment variables that match
|
||||||
|
|
||||||
err := viper.ReadInConfig()
|
err := viper.ReadInConfig()
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
// initDir makes a temp working directory holding a .env file (if env is not
|
// initDir makes a temp working directory holding a .env file (if env is not
|
||||||
@@ -167,3 +169,89 @@ func TestInitEnvOverride(t *testing.T) {
|
|||||||
t.Errorf("FTSMock.Interval = %v; want 45s from the environment", conf.Sources.FTSMock.Interval)
|
t.Errorf("FTSMock.Interval = %v; want 45s from the environment", conf.Sources.FTSMock.Interval)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEnvVarName(t *testing.T) {
|
||||||
|
tests := map[string]string{
|
||||||
|
"logging.level": "JOBSEARCH_LOGGING_LEVEL",
|
||||||
|
"sources.fts_mock.interval": "JOBSEARCH_SOURCES_FTS_MOCK_INTERVAL",
|
||||||
|
"server.websocket.connection_limit.enabled": "JOBSEARCH_SERVER_WEBSOCKET_CONNECTION_LIMIT_ENABLED",
|
||||||
|
}
|
||||||
|
for key, want := range tests {
|
||||||
|
if got := envVarName(key); got != want {
|
||||||
|
t.Errorf("envVarName(%q) = %q; want %q", key, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// explicitConfigYAML sets a top-level and a nested key in a config file.
|
||||||
|
const explicitConfigYAML = `logging:
|
||||||
|
level: info
|
||||||
|
server:
|
||||||
|
websocket:
|
||||||
|
connection_limit:
|
||||||
|
enabled: true
|
||||||
|
`
|
||||||
|
|
||||||
|
// loadExplicit sets up Viper as Init does, with the defaults, the config
|
||||||
|
// file explicitConfigYAML and the JOBSEARCH_* environment variables.
|
||||||
|
func loadExplicit(t *testing.T) {
|
||||||
|
t.Helper()
|
||||||
|
resetViper(t)
|
||||||
|
LoadDefaultConfig()
|
||||||
|
viper.SetEnvPrefix(EnvPrefix)
|
||||||
|
viper.SetEnvKeyReplacer(envKeyReplacer)
|
||||||
|
viper.AutomaticEnv()
|
||||||
|
viper.SetConfigType("yaml")
|
||||||
|
if err := viper.ReadConfig(strings.NewReader(explicitConfigYAML)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A key is explicitly set by the config file or a non-empty environment
|
||||||
|
// variable, not by its default or viper.Set.
|
||||||
|
func TestIsExplicitlySet(t *testing.T) {
|
||||||
|
t.Setenv("JOBSEARCH_DATABASE_FILE", "/tmp/js.db")
|
||||||
|
t.Setenv("JOBSEARCH_LOGGING_FILE", "")
|
||||||
|
loadExplicit(t)
|
||||||
|
viper.Set("sources.sleeper.enabled", true)
|
||||||
|
|
||||||
|
tests := map[string]bool{
|
||||||
|
"logging.level": true, // config file
|
||||||
|
"server.websocket.connection_limit.enabled": true, // nested, config file
|
||||||
|
"database.file": true, // environment
|
||||||
|
"logging.file": false, // empty environment variable
|
||||||
|
"sources.sleeper.interval": false, // default only
|
||||||
|
"sources.sleeper.enabled": false, // viper.Set
|
||||||
|
"server.websocket.connection_limit.burst": false, // default, beside a set key
|
||||||
|
}
|
||||||
|
for key, want := range tests {
|
||||||
|
if got := isExplicitlySet(key); got != want {
|
||||||
|
t.Errorf("isExplicitlySet(%q) = %v; want %v", key, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// printLoadedConfig logs only the keys set by the config file or the
|
||||||
|
// environment.
|
||||||
|
func TestPrintLoadedConfig(t *testing.T) {
|
||||||
|
t.Setenv("JOBSEARCH_DATABASE_FILE", "/tmp/js.db")
|
||||||
|
loadExplicit(t)
|
||||||
|
logs := captureLogs(t, slog.LevelDebug)
|
||||||
|
printLoadedConfig()
|
||||||
|
|
||||||
|
out := logs.String()
|
||||||
|
for _, want := range []string{
|
||||||
|
`logging.level = \"info\" <string>`,
|
||||||
|
`server.websocket.connection_limit.enabled = true <bool>`,
|
||||||
|
`database.file = \"/tmp/js.db\" <string>`,
|
||||||
|
} {
|
||||||
|
if !strings.Contains(out, want) {
|
||||||
|
t.Errorf("missing %q in:\n%s", want, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, unwanted := range []string{"sources.", "logging.file", "connection_limit.burst"} {
|
||||||
|
if strings.Contains(out, unwanted) {
|
||||||
|
t.Errorf("logged default key %q:\n%s", unwanted, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user