From a9f5565da6e8bdeb3ebb01f36aad8dca9ae88035 Mon Sep 17 00:00:00 2001 From: AkitsukiNagi Date: Wed, 24 Dec 2025 19:09:07 +0800 Subject: [PATCH] upload logging.go --- logging.go | 286 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 286 insertions(+) create mode 100755 logging.go diff --git a/logging.go b/logging.go new file mode 100755 index 0000000..cd5fd7b --- /dev/null +++ b/logging.go @@ -0,0 +1,286 @@ +package logging + +import ( + "fmt" + "io" + "log" + "os" + "path/filepath" + "sync" + "time" + + "golang.org/x/term" +) + +const ( + COLOR_RESET = "\033[0m" + COLOR_RED = "\033[91m" + COLOR_YELLOW = "\033[93m" + COLOR_GREEN = "\033[92m" + COLOR_BLUE = "\033[94m" + COLOR_CYAN = "\033[96m" + COLOR_WHITE = "\033[97m" + COLOR_GRAY = "\033[90m" + COLOR_GREY = COLOR_GRAY +) + +type LogLevel int + +const ( + Debug LogLevel = iota + Info + Warn + Error + Critical +) + +func (l LogLevel) String() string { + switch l { + case Debug: + return "DEBUG" + case Info: + return "INFO" + case Warn: + return "WARN" + case Error: + return "ERROR" + case Critical: + return "CRITICAL" + default: + return "UNKNOWN" + } +} + +var LevelColorMap = map[LogLevel]string{ + Debug: COLOR_GRAY, + Info: COLOR_WHITE, + Warn: COLOR_YELLOW, + Error: COLOR_RED, + Critical: COLOR_RED, +} + +type Formatter interface { + Format(level LogLevel, timestamp time.Time, message string) string +} + +type ConsoleFormatter struct { + TimestampFormatter string + UseColor bool + LevelColors map[LogLevel]string + mu sync.Mutex +} + +func NewConsoleFormatter(timestampFormat string, useColor bool, levelColors map[LogLevel]string) *ConsoleFormatter { + if timestampFormat == "" { + timestampFormat = "[02/01/06 15:04:05]" + } + if levelColors == nil { + levelColors = LevelColorMap + } + + return &ConsoleFormatter{ + TimestampFormatter: timestampFormat, + UseColor: useColor, + LevelColors: levelColors, + } +} + +func (f *ConsoleFormatter) Format(level LogLevel, timestamp time.Time, message string) string { + f.mu.Lock() + defer f.mu.Unlock() + + timestampStr := timestamp.Format(f.TimestampFormatter) + levelStr := level.String() + color, ok := f.LevelColors[level] + if !ok { + color = COLOR_WHITE + } + + if f.UseColor && isTTY() { + return fmt.Sprintf("%s %s[%s]%s %s", timestampStr, color, levelStr, COLOR_RESET, message) + } + return fmt.Sprintf("%s [%s] %s", timestampStr, levelStr, message) +} + +type FileFormatter struct { + TimestampFormat string + LoggerName string +} + +func NewFileFormatter(timestampFormat, loggerName string) *FileFormatter { + if timestampFormat == "" { + timestampFormat = "2006-01-02 15:04:05" + } + return &FileFormatter{ + TimestampFormat: timestampFormat, + LoggerName: loggerName, + } +} + +func (f *FileFormatter) Format(level LogLevel, timestamp time.Time, message string) string { + return fmt.Sprintf("[ %s | %s | %s ] %s", timestamp.Format(f.TimestampFormat), f.LoggerName, level.String(), message) +} + +type Logger struct { + name string + level LogLevel + consoleColor bool + logDir string + logFilenamePattern string + formatter Formatter + consoleWriter io.Writer + fileWriter io.WriteCloser + fileLogger *log.Logger + fileFormatter *FileFormatter + mu sync.Mutex + initialized bool +} + +var ( + globalLogger *Logger + once sync.Once +) + +func NewLogger( + loggerName string, + logLevel LogLevel, + consoleColor bool, + logDir string, + logFilenamePattern string, +) *Logger { + if loggerName == "" { + loggerName = "main" + } + if logDir == "" { + logDir = "logs" + } + if logFilenamePattern == "" { + logFilenamePattern = "2006-01-02" + } + + return &Logger{ + name: loggerName, + level: logLevel, + consoleColor: consoleColor, + logDir: logDir, + logFilenamePattern: logFilenamePattern, + consoleWriter: os.Stdout, + } +} + +func (l *Logger) init() { + l.mu.Lock() + defer l.mu.Unlock() + + if l.initialized { + return + } + + if err := os.MkdirAll(l.logDir, 0755); err != nil { + fmt.Fprintf(os.Stderr, "Error creating log directory %s: %v\n", l.logDir, err) + } else { + logFilePath := l.genUniqueLogFilename() + file, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) + if err != nil { + fmt.Fprintf(os.Stderr, "Error opening log file %s: %v\n", logFilePath, err) + } else { + l.fileWriter = file + if l.fileWriter != nil { + l.fileFormatter = NewFileFormatter("", l.name) + } + l.fileLogger = log.New(l.fileWriter, "", 0) + fmt.Printf("Log file will be saved to %s\n", logFilePath) + } + } + + l.formatter = NewConsoleFormatter("", l.consoleColor, nil) + + l.initialized = true +} + +func (l *Logger) genUniqueLogFilename() string { + now := time.Now() + baseFilename := now.Format(l.logFilenamePattern) + logFilePath := filepath.Join(l.logDir, fmt.Sprintf("%s.log", baseFilename)) + + if _, err := os.Stat(logFilePath); os.IsNotExist(err) { + return logFilePath + } + + counter := 1 + for { + suffixedFilePath := filepath.Join(l.logDir, fmt.Sprintf("%s_%d.log", baseFilename, counter)) + if _, err := os.Stat(suffixedFilePath); os.IsNotExist(err) { + return suffixedFilePath + } + counter += 1 + } +} + +func (l *Logger) Log(level LogLevel, message string) { + if !l.initialized { + l.init() + } + + if level < l.level { + return + } + + now := time.Now() + + if l.formatter != nil { + formattedMsg := l.formatter.Format(level, now, message) + fmt.Fprintln(l.consoleWriter, formattedMsg) + } else { + fmt.Fprintf(l.consoleWriter, "%s [%s] %s\n", now.Format("[15:04:05]"), level.String(), message) + } + + if l.fileLogger != nil && l.fileFormatter != nil { + formattedFileMsg := l.fileFormatter.Format(level, now, message) + l.fileLogger.Println(formattedFileMsg) + } +} + +func (l *Logger) Debug(message string, v ...any) { + l.Log(Debug, fmt.Sprintf(message, v...)) +} + +func (l *Logger) Warn(message string, v ...any) { + l.Log(Warn, fmt.Sprintf(message, v...)) +} + +func (l *Logger) Info(message string, v ...any) { + l.Log(Info, fmt.Sprintf(message, v...)) +} + +func (l *Logger) Error(message string, v ...any) { + l.Log(Error, fmt.Sprintf(message, v...)) +} + +func (l *Logger) Critical(message string, v ...any) { + l.Log(Critical, fmt.Sprintf(message, v...)) + os.Exit(1) +} + +func (l *Logger) Close() error { + l.mu.Lock() + defer l.mu.Unlock() + if l.fileWriter != nil { + err := l.fileWriter.Close() + l.fileWriter = nil + l.fileLogger = nil + return err + } + return nil +} + +func GetLogger() *Logger { + once.Do(func() { + globalLogger = NewLogger("shizuku", Debug, true, "logs", "2006-01-02") + }) + return globalLogger +} + +func isTTY() bool { + return os.Getenv("TERM") != "" && term.IsTerminal(int(os.Stdout.Fd())) +}