mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-09-24 19:24:16 +00:00
164 lines
3.8 KiB
Go
164 lines
3.8 KiB
Go
// Package logging provides centralized structured logging using slog
|
|
// with configurable log levels. Call InitLogger() from main() to configure.
|
|
//
|
|
// Dynamic debug logging:
|
|
// Send SIGUSR1 to toggle debug mode at runtime (auto-reverts after 30 minutes).
|
|
// Example: docker kill -s SIGUSR1 <container>
|
|
package logging
|
|
|
|
import (
|
|
"io"
|
|
"log/slog"
|
|
"os"
|
|
"os/signal"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"syscall"
|
|
"time"
|
|
)
|
|
|
|
const debugTimeout = 30 * time.Minute
|
|
|
|
var (
|
|
levelVar *slog.LevelVar
|
|
originalLevel slog.Level
|
|
debugEnabled atomic.Bool
|
|
revertTimer *time.Timer
|
|
revertMu sync.Mutex
|
|
)
|
|
|
|
// InitLogger initializes the global slog default logger with the specified log level.
|
|
// Valid levels: debug, info, warn, error (case-insensitive)
|
|
// If level is empty or invalid, defaults to INFO.
|
|
// Call this from main() at startup.
|
|
//
|
|
// Also starts a signal handler for SIGUSR1 to toggle debug mode at runtime.
|
|
func InitLogger(level string) {
|
|
var logLevel slog.Level
|
|
|
|
switch strings.ToLower(strings.TrimSpace(level)) {
|
|
case "debug":
|
|
logLevel = slog.LevelDebug
|
|
case "info", "":
|
|
logLevel = slog.LevelInfo
|
|
case "warn", "warning":
|
|
logLevel = slog.LevelWarn
|
|
case "error":
|
|
logLevel = slog.LevelError
|
|
default:
|
|
logLevel = slog.LevelInfo
|
|
}
|
|
|
|
// Store original level for toggle-back and use LevelVar for dynamic changes
|
|
originalLevel = logLevel
|
|
levelVar = new(slog.LevelVar)
|
|
levelVar.Set(logLevel)
|
|
|
|
opts := &slog.HandlerOptions{
|
|
Level: levelVar,
|
|
}
|
|
|
|
handler := slog.NewTextHandler(os.Stdout, opts)
|
|
slog.SetDefault(slog.New(handler))
|
|
|
|
// Start signal handler for dynamic debug toggle
|
|
go handleDebugSignal()
|
|
}
|
|
|
|
func handleDebugSignal() {
|
|
sigChan := make(chan os.Signal, 1)
|
|
signal.Notify(sigChan, syscall.SIGUSR1)
|
|
|
|
for range sigChan {
|
|
ToggleDebug()
|
|
}
|
|
}
|
|
|
|
// ToggleDebug toggles between the original log level and DEBUG.
|
|
// When enabling debug, starts a 30-minute timer that auto-reverts.
|
|
// Typically called via SIGUSR1 signal.
|
|
func ToggleDebug() {
|
|
revertMu.Lock()
|
|
defer revertMu.Unlock()
|
|
|
|
wasDebug := debugEnabled.Swap(!debugEnabled.Load())
|
|
|
|
// Cancel any existing revert timer
|
|
if revertTimer != nil {
|
|
revertTimer.Stop()
|
|
revertTimer = nil
|
|
}
|
|
|
|
if wasDebug {
|
|
// Turning debug OFF
|
|
levelVar.Set(originalLevel)
|
|
slog.Info("Log level changed",
|
|
"from", "DEBUG",
|
|
"to", levelToString(originalLevel),
|
|
"trigger", "SIGUSR1")
|
|
} else {
|
|
// Turning debug ON - start auto-revert timer
|
|
levelVar.Set(slog.LevelDebug)
|
|
revertTimer = time.AfterFunc(debugTimeout, autoRevert)
|
|
slog.Info("Log level changed",
|
|
"from", levelToString(originalLevel),
|
|
"to", "DEBUG",
|
|
"trigger", "SIGUSR1",
|
|
"auto_revert_in", debugTimeout)
|
|
}
|
|
}
|
|
|
|
func autoRevert() {
|
|
revertMu.Lock()
|
|
defer revertMu.Unlock()
|
|
|
|
if !debugEnabled.Load() {
|
|
return // Already reverted manually
|
|
}
|
|
|
|
debugEnabled.Store(false)
|
|
levelVar.Set(originalLevel)
|
|
revertTimer = nil
|
|
|
|
slog.Info("Log level changed",
|
|
"from", "DEBUG",
|
|
"to", levelToString(originalLevel),
|
|
"trigger", "auto-revert")
|
|
}
|
|
|
|
func levelToString(l slog.Level) string {
|
|
switch l {
|
|
case slog.LevelDebug:
|
|
return "DEBUG"
|
|
case slog.LevelInfo:
|
|
return "INFO"
|
|
case slog.LevelWarn:
|
|
return "WARN"
|
|
case slog.LevelError:
|
|
return "ERROR"
|
|
default:
|
|
return l.String()
|
|
}
|
|
}
|
|
|
|
// SetupTestLogger configures logging for tests to reduce noise.
|
|
// Sets log level to WARN and outputs to io.Discard to suppress DEBUG and INFO messages.
|
|
// Returns a cleanup function that should be called when the test completes (use t.Cleanup).
|
|
func SetupTestLogger() func() {
|
|
// Save original logger to restore later
|
|
originalLogger := slog.Default()
|
|
|
|
// Set level to WARN and discard output to silence tests
|
|
opts := &slog.HandlerOptions{
|
|
Level: slog.LevelWarn,
|
|
}
|
|
handler := slog.NewTextHandler(io.Discard, opts)
|
|
slog.SetDefault(slog.New(handler))
|
|
|
|
// Return cleanup function
|
|
return func() {
|
|
slog.SetDefault(originalLogger)
|
|
}
|
|
}
|