package logging import ( "errors" "log/slog" "os" "path/filepath" "github.com/lmittmann/tint" ) // ---------- Standard log handling (terminal or file based) ---------- // Logger state shared by the terminal and file loggers. var ( // logLevel is the minimum level logged, read by whichever handler is // installed, so changing it takes effect immediately. logLevel slog.LevelVar // fileLogger is the log file opened by LogToFile, or nil when logging // to the terminal. fileLogger *os.File ) // SetLevel sets the logging level without logging the change. func SetLevel(level slog.Level) { logLevel.Set(level) } // EnableDebug lowers the logging level to debug, if it isn't already, and // logs that it did. func EnableDebug() { if logLevel.Level() > slog.LevelDebug { logLevel.Set(slog.LevelDebug) slog.Debug("Debug logging enabled") } } // LogToFile sends all logging to logfile as JSON, replacing the default slog // logger. The file is created if needed and appended to; any log file opened // by an earlier call is closed. func LogToFile(logfile string) error { closeErr := Shutdown() // Open the log file var err error fileLogger, err = os.OpenFile( filepath.Clean(logfile), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o600, ) if err != nil { return errors.Join(err, closeErr) } opts := &slog.HandlerOptions{ Level: &logLevel, } handler := slog.NewJSONHandler(fileLogger, opts) logger := slog.New(handler) slog.SetDefault(logger) return closeErr } // Shutdown closes the log file opened by LogToFile, if any. func Shutdown() error { var err error = nil if fileLogger != nil { err = (*fileLogger).Close() fileLogger = nil } return err } // LogToTerminal sends all logging to stdout as coloured text, replacing the // default slog logger. func LogToTerminal() { opts := &tint.Options{ Level: &logLevel, TimeFormat: "*", } handler := tint.NewTextHandler(os.Stdout, opts) logger := slog.New(handler) slog.SetDefault(logger) } // IsJSONHandler reports whether h writes JSON, as the file logger does. func IsJSONHandler(h slog.Handler) bool { _, ok := h.(*slog.JSONHandler) return ok } // IsFileLogger reports whether logging currently goes to a file. func IsFileLogger() bool { return fileLogger != nil } // IsDebugEnabled reports whether debug messages are logged. func IsDebugEnabled() bool { return logLevel.Level() <= slog.LevelDebug }