Configuration
go-bananas has no central config package, and that is a deliberate choice.
Each package that needs environment configuration exposes its own small Config
struct with env tags,
and your application composes the ones it uses into a single struct that
sethvargo/go-envconfig
processes in one pass. A package owns the env vars it understands; your app
decides which packages to assemble.
Per-package Config structs
Several packages already follow this pattern — for example
secrets.Config and keys.Config — and this release adds
logging.Config:
type Config struct {
// Level is the minimum log level ("debug", "info", "warn"/"warning",
// or "error"). Unrecognized values fall back to info.
Level string `env:"LOG_LEVEL, default=info"`
// Mode selects the output format. "development" produces human-readable
// text; any other value produces JSON.
Mode string `env:"LOG_MODE, default=production"`
}
Once the struct is populated, build the logger from it:
logger := logging.NewLoggerFromConfig(cfg.Logging)
See the
logging GoDoc —
and each package’s GoDoc — for the env vars it owns.
Composing an application config
An application declares one struct that embeds the framework Config structs it
needs and adds its own fields, then processes the whole thing with a single
envconfig.Process call:
type appConfig struct {
Logging logging.Config
Secrets secrets.Config
OIDC oidcConfig
DevMode bool `env:"DEV_MODE, default=false"`
Port string `env:"PORT, default=8080"`
BuildID string `env:"BUILD_ID, default=dev"`
}
go-envconfig walks nested structs, so the env-tagged fields inside
logging.Config and secrets.Config are populated right alongside the
application’s own fields. Process them all at once near the top of main:
func realMain(ctx context.Context) error {
var cfg appConfig
if err := envconfig.Process(ctx, &cfg); err != nil {
return fmt.Errorf("failed to process configuration: %w", err)
}
logger := logging.NewLoggerFromConfig(cfg.Logging)
ctx = logging.WithLogger(ctx, logger)
// … hand cfg to the rest of the app.
}
The application’s own grouped config — here oidcConfig — is just another
struct with env tags, defined wherever it makes sense in your codebase:
type oidcConfig struct {
Issuer string `env:"OIDC_ISSUER"`
ClientID string `env:"OIDC_CLIENT_ID"`
ClientSecret string `env:"OIDC_CLIENT_SECRET"`
RedirectURL string `env:"OIDC_REDIRECT_URL"`
}
The env tag
The tag names the environment variable and, optionally, supplies a default after a comma:
Port string `env:"PORT, default=8080"` // 8080 when PORT is unset
DevMode bool `env:"DEV_MODE, default=false"` // parsed as a bool
Issuer string `env:"OIDC_ISSUER"` // empty when unset (no default)
Defaults apply only when the variable is unset, and go-envconfig parses the
target type for you (bool, numeric types, time.Duration, and more). An empty
OIDC_ISSUER, for instance, lets the example disable its OIDC flow entirely.
For secret-valued configuration, go-envconfig can resolve secret://
references through a secret manager while it processes — see
Secrets & keys.
The runnable examples/ssr-oidc
application composes exactly this appConfig and processes it once in
realMain.