Skip to content

logging Library

code/logging is the shared logging package for the Go workspace. It wraps Go’s log/slog with a process-global handler that can be reconfigured at runtime, exposes named group loggers, and defaults timestamps to UTC.

Consumers: homelabctl2, proxmox-ipam. Both modules require it as gitlab.com/joelMuehlena/homelab/code/logging; locally the workspace (go.work) resolves it to the in-tree path, in CI a pseudo-version is fetched from the GitLab module proxy.

import "gitlab.com/joelMuehlena/homelab/code/logging"
// At process startup (typically in main):
logging.Init(logging.LOG_FORMAT_TEXT, logging.INFO)
// Adjust at runtime (e.g. after parsing --log-* flags):
logging.SetLevel(logging.DEBUG)
logging.SetFormat(logging.LOG_FORMAT_JSON)
logging.SetTimezone(time.UTC) // default; takes any *time.Location
// Use it:
log := logging.GetWithGroup("password-vault-init")
log.Info("writing secret", "path", "secrets/data/foo", "keys", []string{"username"})
FunctionPurpose
Init(format, level) *slog.LoggerOne-shot init. Second+ call warns and returns the existing logger.
SetLevel(slog.Level)Rebuild handler with new level (keeps format + tz).
SetFormat(LogFormat)Rebuild handler with new format (keeps level + tz).
SetTimezone(*time.Location)Rebuild handler so timestamps render in this zone. nil → UTC.
Get() *slog.LoggerThe root logger (no group).
GetWithGroup(name) *slog.LoggerRoot logger wrapped in WithGroup(name).
LevelFromString(s) slog.LevelParse debug / info / warning / error (falls back to info).
FormatFromString(s) LogFormatParse text / json (falls back to text).

Get() and GetWithGroup(name) return a snapshot of the configured logger — call them inside the function that runs after SetLevel / SetFormat / SetTimezone so the returned logger reflects the latest config.

AspectDefault
LevelWARN (until Init) / caller-chosen after Init
Formatcaller-chosen (typically LOG_FORMAT_TEXT)
Timezonetime.UTC
SourceAdded automatically when level is DEBUG
Outputos.Stdout

Source attribution (file:line) is enabled when level < 0 (i.e. DEBUG) so debug logs always include where they came from.

homelabctl2’s main.go calls Init with text/info defaults so the terminal-error path is usable even if cobra fails before PersistentPreRunE. The root’s PersistentPreRunE then applies the parsed --log-format / --log-level flags via SetFormat / SetLevel, plus slog.SetDefault(logging.Get()) so any stray slog.* call also flows through the configured handler. Timestamps stay UTC; no flag exposes it.

cmd/homelabctl2/main.go
logging.Init(logging.LOG_FORMAT_TEXT, logging.INFO)
if err := rootCmd.Execute(); err != nil {
logging.Get().Error("command failed", "err", err)
os.Exit(1)
}
// cmd/homelabctl2/root.go (excerpt)
PersistentPreRunE: func(_ *cobra.Command, _ []string) error {
logging.SetFormat(logging.FormatFromString(logFormat))
logging.SetLevel(logging.LevelFromString(logLevel))
slog.SetDefault(logging.Get())
return nil
}

Subcommands obtain a named logger via logging.GetWithGroup("name") at their entry function and plumb it to helpers that log:

func runPasswordVaultInit(cmd *cobra.Command, _ []string) error {
log := logging.GetWithGroup("password-vault-init")
// ... log.Info / log.Debug / log.Error ...
}