🐛 fix: emit all config-loading logs in correct JSON format from the start (#16)
## Summary Closes #15 When `logging.json: true` (or `DLC_LOGGING_JSON=true`), the logger was unconditionally initialised to console/text format at the top of `LoadConfig()`, so early log lines — most visibly **"Config file loaded"** — were always written as human-readable text regardless of configuration. ## Root cause Classic chicken-and-egg: the format flag lives inside the config that is being loaded. The format-switch block only ran *after* `v.Unmarshal()`, too late for the config-file log. ## Changes ### `pkg/config/config.go` - Add `peekJSONLogging()`: resolves the JSON flag **before** any log is emitted by (1) checking `DLC_LOGGING_JSON` directly via `os.Getenv`, then (2) doing a minimal throwaway Viper pre-read of the config file for the `logging.json` key. This mirrors Viper's own priority order without parsing the full config twice. - Apply the resolved format immediately and emit **"Logging configured"** as the very first log line. - Remove the now-redundant format-switch block that ran after `Unmarshal()`. ### `scripts/start-server.sh`, `test-graceful-shutdown.sh`, `test-opentelemetry.sh` - Replace hardcoded `PROJECT_DIR` path with a dynamic `SCRIPTS_DIR=$(dirname $(realpath ${BASH_SOURCE[0]}))` derivation so scripts work from any worktree or clone location. ## Test plan - [x] `go test ./pkg/...` — all pass - [x] `scripts/test-graceful-shutdown.sh` — all JSON valid, all startup logs present - [x] Manual smoke test: first line is `{"level":"info",...,"message":"Logging configured"}`, every line is valid JSON Reviewed-on: #16 Co-authored-by: Gabriel Radureau <arcodange@gmail.com> Co-committed-by: Gabriel Radureau <arcodange@gmail.com>
This commit was merged in pull request #16.
This commit is contained in:
@@ -118,6 +118,34 @@ type SamplerConfig struct {
|
||||
Ratio float64 `mapstructure:"ratio"`
|
||||
}
|
||||
|
||||
// peekJSONLogging determines whether JSON logging should be used before the full
|
||||
// config is loaded, solving the chicken-and-egg problem where the logger format
|
||||
// must be known before any log is emitted, yet the format is stored in the config.
|
||||
//
|
||||
// Resolution order (mirrors Viper's own priority):
|
||||
// 1. DLC_LOGGING_JSON env var — checked directly via os.Getenv (zero overhead)
|
||||
// 2. logging.json key in the config file — read with a minimal throwaway Viper
|
||||
// instance so we don't parse the whole config twice unnecessarily
|
||||
func peekJSONLogging() bool {
|
||||
// 1. Env var takes highest priority — check it first
|
||||
if env := os.Getenv("DLC_LOGGING_JSON"); env != "" {
|
||||
return strings.EqualFold(env, "true") || env == "1"
|
||||
}
|
||||
|
||||
// 2. Try to read logging.json from the config file
|
||||
preV := viper.New()
|
||||
preV.SetDefault("logging.json", false)
|
||||
if configFile := os.Getenv("DLC_CONFIG_FILE"); configFile != "" {
|
||||
preV.SetConfigFile(configFile)
|
||||
} else {
|
||||
preV.SetConfigName("config")
|
||||
preV.SetConfigType("yaml")
|
||||
preV.AddConfigPath(".")
|
||||
}
|
||||
_ = preV.ReadInConfig() // ignore errors — defaults apply on failure
|
||||
return preV.GetBool("logging.json")
|
||||
}
|
||||
|
||||
// LoadConfig loads configuration from file, environment variables, and defaults
|
||||
// Configuration priority: file > environment variables > defaults
|
||||
// To specify a custom config file path, set DLC_CONFIG_FILE environment variable
|
||||
@@ -129,9 +157,17 @@ func LoadConfig() (*Config, error) {
|
||||
|
||||
v := viper.New()
|
||||
|
||||
// Set up initial console logging for config loading messages
|
||||
consoleWriter := zerolog.ConsoleWriter{Out: os.Stderr}
|
||||
log.Logger = log.Output(consoleWriter)
|
||||
// Configure the logger format before emitting any log output.
|
||||
// peekJSONLogging reads the JSON setting early (env var + config file pre-read)
|
||||
// so that every log line — including those produced during config loading — is
|
||||
// already in the correct format.
|
||||
jsonLogging := peekJSONLogging()
|
||||
if jsonLogging {
|
||||
log.Logger = log.Output(os.Stderr)
|
||||
} else {
|
||||
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
|
||||
}
|
||||
log.Info().Bool("json", jsonLogging).Msg("Logging configured")
|
||||
|
||||
// Set default values
|
||||
v.SetDefault("server.host", "0.0.0.0")
|
||||
@@ -227,15 +263,9 @@ func LoadConfig() (*Config, error) {
|
||||
return nil, fmt.Errorf("config unmarshal error: %w", err)
|
||||
}
|
||||
|
||||
// Configure log output format (JSON or console) first
|
||||
if config.Logging.JSON {
|
||||
log.Logger = log.Output(os.Stderr)
|
||||
} else {
|
||||
consoleWriter := zerolog.ConsoleWriter{Out: os.Stderr}
|
||||
log.Logger = log.Output(consoleWriter)
|
||||
}
|
||||
|
||||
// Setup logging based on configuration
|
||||
// Setup logging based on configuration (level, output file, time format).
|
||||
// The JSON/console format was already applied at the top of LoadConfig via
|
||||
// peekJSONLogging, so SetupLogging only needs to handle the remaining knobs.
|
||||
config.SetupLogging()
|
||||
|
||||
log.Info().
|
||||
|
||||
Reference in New Issue
Block a user