package log import ( "context" "fmt" "log/slog" "os" "path/filepath" "runtime" "strings" "golang.org/x/term" ) // LogLevel represents the logging level type LogLevel int const ( LevelFatal LogLevel = iota LevelError LevelWarn LevelNotice LevelInfo LevelDebug ) // Logger configuration type Config struct { Verbose bool Debug bool Cron bool } var logger *slog.Logger // Initialize sets up the global logger based on the provided configuration func Initialize(cfg Config) { // Determine log level based on configuration var level slog.Level if cfg.Cron { // In cron mode, only show fatal errors (which we'll handle specially) level = slog.LevelError } else if cfg.Debug || strings.Contains(os.Getenv("GODEBUG"), "vaultik") { level = slog.LevelDebug } else if cfg.Verbose { level = slog.LevelInfo } else { level = slog.LevelWarn } // Create handler with appropriate level opts := &slog.HandlerOptions{ Level: level, } // Check if stdout is a TTY if term.IsTerminal(int(os.Stdout.Fd())) { // Use colorized TTY handler logger = slog.New(NewTTYHandler(os.Stdout, opts)) } else { // Use JSON format for non-TTY output logger = slog.New(slog.NewJSONHandler(os.Stdout, opts)) } // Set as default logger slog.SetDefault(logger) } // getCaller returns the caller information as a string func getCaller(skip int) string { _, file, line, ok := runtime.Caller(skip) if !ok { return "unknown" } return fmt.Sprintf("%s:%d", filepath.Base(file), line) } // Fatal logs a fatal error and exits func Fatal(msg string, args ...any) { if logger != nil { // Add caller info to args args = append(args, "caller", getCaller(2)) logger.Error(msg, args...) } os.Exit(1) } // Fatalf logs a formatted fatal error and exits func Fatalf(format string, args ...any) { Fatal(fmt.Sprintf(format, args...)) } // Error logs an error func Error(msg string, args ...any) { if logger != nil { args = append(args, "caller", getCaller(2)) logger.Error(msg, args...) } } // Errorf logs a formatted error func Errorf(format string, args ...any) { Error(fmt.Sprintf(format, args...)) } // Warn logs a warning func Warn(msg string, args ...any) { if logger != nil { args = append(args, "caller", getCaller(2)) logger.Warn(msg, args...) } } // Warnf logs a formatted warning func Warnf(format string, args ...any) { Warn(fmt.Sprintf(format, args...)) } // Notice logs a notice (mapped to Info level) func Notice(msg string, args ...any) { if logger != nil { args = append(args, "caller", getCaller(2)) logger.Info(msg, args...) } } // Noticef logs a formatted notice func Noticef(format string, args ...any) { Notice(fmt.Sprintf(format, args...)) } // Info logs an info message func Info(msg string, args ...any) { if logger != nil { args = append(args, "caller", getCaller(2)) logger.Info(msg, args...) } } // Infof logs a formatted info message func Infof(format string, args ...any) { Info(fmt.Sprintf(format, args...)) } // Debug logs a debug message func Debug(msg string, args ...any) { if logger != nil { args = append(args, "caller", getCaller(2)) logger.Debug(msg, args...) } } // Debugf logs a formatted debug message func Debugf(format string, args ...any) { Debug(fmt.Sprintf(format, args...)) } // With returns a logger with additional context func With(args ...any) *slog.Logger { if logger != nil { return logger.With(args...) } return slog.Default() } // WithContext returns a logger with context func WithContext(ctx context.Context) *slog.Logger { return logger } // Logger returns the underlying slog.Logger func Logger() *slog.Logger { return logger }