Logging: structured slog messages throughout, and WithLogger options so callers choose the logger

Log messages are lower-case event names with their details as key/value
attributes ("error" for errors), in place of formatted strings.

Loggable package take a WithLogger option as part of their "New*" call,
defaulting to slog.Default(). Each tags its lines with its `component`.

Some debug loggers print directly rather than using slog when in
terminal logging mode.
This commit is contained in:
bwilliams committed 2026-10-15 16:10:52 +01:00
1 parent 0b5fde9563
commit 043e307663
49 files changed
+598 -248

No files matched your search

+62 -21
View File
@@ -1,6 +1,7 @@
package config
import (
"context"
"fmt"
"log/slog"
"os"
@@ -9,41 +10,81 @@ import (
"strings"
"github.com/spf13/viper"
"git.avsdev.uk/Go/job-search/internal/logging"
)
// printEnv logs the JOBSEARCH* environment variables at debug level.
// printEnv logs each JOBSEARCH* environment variable at debug level. On a
// terminal it logs one line and prints the variables under it as
// "name=value" lines; elsewhere each variable is a log entry of its own.
func printEnv() {
slog.Debug("Environment:")
env := os.Environ()
slices.Sort(env)
for _, e := range env {
pair := strings.SplitN(e, "=", 2)
if strings.HasPrefix(pair[0], EnvPrefix) {
slog.Debug("\t" + pair[0] + "=" + pair[1])
logger := slog.Default()
terminal := logging.WritesToTerminal(logger.Handler())
if terminal && !logger.Enabled(context.Background(), slog.LevelDebug) {
return
}
var variables []string
for _, variable := range os.Environ() {
if strings.HasPrefix(variable, EnvPrefix) {
variables = append(variables, variable)
}
}
slices.Sort(variables)
if terminal {
logger.Debug("environment variables", "count", len(variables))
}
for _, variable := range variables {
if terminal {
logging.PrintToTerminal("\t" + variable)
continue
}
name, value, _ := strings.Cut(variable, "=")
logger.Debug("environment variable", "name", name, "value", value)
}
}
// printLoadedConfig logs every key set by the config file or an environment
// variable (see isExplicitlySet), with its value and type, at debug level.
// variable (see isExplicitlySet), with its value and type, at debug level. On
// a terminal it logs one line and prints the values under it as
// "key = value <type>" lines; elsewhere each value is a log entry of its own.
func printLoadedConfig() {
slog.Debug("Loaded config:")
keys := viper.AllKeys()
for i := range keys {
k := keys[i]
if isExplicitlySet(k) {
v := viper.Get(k)
t := reflect.TypeOf(v)
slog.Debug(
"\t" + k + " = " + fmt.Sprintf("%#v", v) +
" <" + t.Kind().String() + ">",
)
logger := slog.Default()
terminal := logging.WritesToTerminal(logger.Handler())
if terminal && !logger.Enabled(context.Background(), slog.LevelDebug) {
return
}
var keys []string
for _, key := range viper.AllKeys() {
if isExplicitlySet(key) {
keys = append(keys, key)
}
}
if terminal {
logger.Debug("loaded config values", "count", len(keys))
}
for _, key := range keys {
value := viper.Get(key)
kind := reflect.TypeOf(value).Kind().String()
if terminal {
logging.PrintToTerminal(
fmt.Sprintf("\t%s = %#v <%s>", key, value, kind),
)
continue
}
logger.Debug(
"loaded config value",
"key", key,
"value", value,
"type", kind,
)
}
}
// printConfig logs conf at debug level.
func printConfig(conf JobSearchConfig) {
slog.Debug("Config:")
conf.PrintLog(slog.LevelDebug)
}