/* Copyright © 2026 Craig Williams This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . */ package logging import ( "log/slog" "os" "reflect" "unsafe" "github.com/lmittmann/tint" ) // ---------- Standard log handling (terminal or file based) ---------- var logLevel slog.LevelVar var fileLogger *os.File // Set the logging to a particular level, quietly func SetLevel(level slog.Level) { logLevel.Set(level) } // Enable debug level logging func EnableDebug() { if logLevel.Level() > slog.LevelDebug { logLevel.Set(slog.LevelDebug) slog.Debug("Debug logging enabled") } } func LogToFile(logfile string) error { if fileLogger != nil { (*fileLogger).Close() } // Open the log file fileLogger, err := os.OpenFile(logfile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) if err != nil { return err } opts := &slog.HandlerOptions{ Level: &logLevel, } handler := slog.NewJSONHandler(fileLogger, opts) logger := slog.New(handler) slog.SetDefault(logger) return nil } func Shutdown() error { var err error = nil if fileLogger != nil { err = (*fileLogger).Close() fileLogger = nil } return err } func LogToTerminal() { opts := &tint.Options{ Level: &logLevel, TimeFormat: "*", } handler := tint.NewTextHandler(os.Stdout, opts) logger := slog.New(handler) slog.SetDefault(logger) } func IsJSONHandler(h slog.Handler) bool { val := reflect.ValueOf(h) // Ensure we are working with a pointer to a struct (*JSONHandler or *TextHandler or derived) if val.Kind() != reflect.Pointer || val.Elem().Kind() != reflect.Struct { return false } // Both JSONHandler and TextHandler wrap an unexported field named 'commonHandler' commonHandlerField := val.Elem().FieldByName("commonHandler") if !commonHandlerField.IsValid() { return false } // Get the underlying *commonHandler struct pointer // commonHandlerField is unexported, so we must use unsafe to bypass Go's safety checks ptr := reflect.NewAt( commonHandlerField.Type().Elem(), unsafe.Pointer(commonHandlerField.Pointer()), ) // Find the unexported 'json' boolean field inside commonHandler jsonField := ptr.Elem().FieldByName("json") if !jsonField.IsValid() { return false } // Read the boolean value using unsafe return *(*bool)(unsafe.Pointer(jsonField.UnsafeAddr())) } func IsFileLogger() bool { return fileLogger != nil } func IsDebugEnabled() bool { return logLevel.Level() <= slog.LevelDebug }