// ============================================================================== // MasterHttpRelayVPN // Author: MasterkinG32 // Github: https://github.com/masterking32 // Year: 2026 // ============================================================================== package logger import ( "fmt" "io" "os" "strings" "sync" "time" ) type Logger struct { name string level int mu sync.Mutex consoleWriter io.Writer fileWriter *os.File color bool appNameText string appNameColored string } const ( levelDebug = iota levelInfo levelWarn levelError ) const ( LevelDebug = levelDebug LevelInfo = levelInfo LevelWarn = levelWarn LevelError = levelError ) var colorTagCodes = map[string]string{ "black": "\x1b[30m", "red": "\x1b[31m", "green": "\x1b[32m", "yellow": "\x1b[33m", "blue": "\x1b[34m", "magenta": "\x1b[35m", "cyan": "\x1b[36m", "white": "\x1b[37m", "gray": "\x1b[90m", "grey": "\x1b[90m", "bold": "\x1b[1m", "reset": "\x1b[0m", } var plainLevelTexts = [...]string{ levelDebug: "[DEBUG]", levelInfo: "[INFO]", levelWarn: "[WARN]", levelError: "[ERROR]", } var coloredLevelTexts = [...]string{ levelDebug: "\x1b[35m[DEBUG]\x1b[0m", levelInfo: "\x1b[32m[INFO]\x1b[0m", levelWarn: "\x1b[33m[WARN]\x1b[0m", levelError: "\x1b[31m[ERROR]\x1b[0m", } func New(name, rawLevel string) *Logger { return NewWithFile(name, rawLevel, "") } func NewWithFile(name, rawLevel, filePath string) *Logger { appName := "[" + name + "]" var consoleWriter io.Writer = os.Stdout var fileWriter *os.File if filePath != "" { f, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err == nil { fileWriter = f } } return &Logger{ name: name, level: parseLevel(rawLevel), consoleWriter: consoleWriter, fileWriter: fileWriter, color: shouldUseColor(), appNameText: appName, appNameColored: "\x1b[36m" + appName + "\x1b[0m", } } func parseLevel(raw string) int { switch strings.ToUpper(strings.TrimSpace(raw)) { case "DEBUG": return levelDebug case "WARNING", "WARN": return levelWarn case "ERROR", "CRITICAL": return levelError default: return levelInfo } } func (l *Logger) logf(level int, format string, args ...any) { if l == nil || level < l.level { return } msg := format if len(args) != 0 { msg = fmt.Sprintf(format, args...) } plainMsg := msg if strings.IndexByte(msg, '<') >= 0 { plainMsg = stripColorTags(msg) } l.mu.Lock() defer l.mu.Unlock() ts := time.Now().Format("2006/01/02 15:04:05") if l.fileWriter != nil { fileLine := ts + " " + plainLevelTexts[level] + " " + plainMsg + "\n" if _, err := io.WriteString(l.fileWriter, fileLine); err != nil { _ = l.fileWriter.Close() l.fileWriter = nil } } if l.consoleWriter != nil { appName := l.appNameText levelText := plainLevelTexts[level] finalMsg := plainMsg if l.color { if strings.IndexByte(msg, '<') >= 0 { finalMsg = renderColorTags(msg) } else { finalMsg = msg } appName = l.appNameColored levelText = coloredLevelTexts[level] } consoleLine := ts + " " + appName + " " + levelText + " " + finalMsg + "\n" _, _ = io.WriteString(l.consoleWriter, consoleLine) } } func (l *Logger) Debugf(format string, args ...any) { l.logf(levelDebug, format, args...) } func (l *Logger) Infof(format string, args ...any) { l.logf(levelInfo, format, args...) } func (l *Logger) Warnf(format string, args ...any) { l.logf(levelWarn, format, args...) } func (l *Logger) Errorf(format string, args ...any) { l.logf(levelError, format, args...) } func (l *Logger) Fatalf(format string, args ...any) { l.logf(levelError, format, args...) os.Exit(1) } func (l *Logger) Enabled(level int) bool { return l != nil && level >= l.level } func stripColorTags(text string) string { start := strings.IndexByte(text, '<') if start == -1 { return text } var b strings.Builder b.Grow(len(text)) for i := 0; i < len(text); { if text[i] != '<' { next := strings.IndexByte(text[i:], '<') if next == -1 { b.WriteString(text[i:]) break } b.WriteString(text[i : i+next]) i += next continue } end := strings.IndexByte(text[i:], '>') if end == -1 { b.WriteString(text[i:]) break } rawTag := text[i : i+end+1] tag := strings.ToLower(rawTag) if _, _, ok := parseColorTag(tag); ok { i += end + 1 continue } b.WriteString(rawTag) i += end + 1 } return b.String() } func shouldUseColor() bool { if strings.TrimSpace(os.Getenv("NO_COLOR")) != "" { return false } if strings.TrimSpace(os.Getenv("FORCE_COLOR")) != "" { return true } return detectColorSupport(os.Stdout) } func renderColorTags(text string) string { start := strings.IndexByte(text, '<') if start == -1 { return text } var b strings.Builder b.Grow(len(text) + 16) b.WriteString(text[:start]) stack := make([]string, 0, 4) for i := start; i < len(text); { if text[i] != '<' { next := strings.IndexByte(text[i:], '<') if next == -1 { b.WriteString(text[i:]) break } b.WriteString(text[i : i+next]) i += next continue } end := strings.IndexByte(text[i:], '>') if end == -1 { b.WriteString(text[i:]) break } rawTag := text[i : i+end+1] tag := strings.ToLower(rawTag) if name, closing, ok := parseColorTag(tag); ok { if closing { if name == "reset" { stack = stack[:0] b.WriteString("\x1b[0m") } else if restoreColorTag(&stack, name) { b.WriteString("\x1b[0m") for _, active := range stack { b.WriteString(colorTagCodes[active]) } } else { b.WriteString(rawTag) } } else { stack = append(stack, name) b.WriteString(colorTagCodes[name]) } } else { b.WriteString(rawTag) } i += end + 1 } if len(stack) != 0 { b.WriteString("\x1b[0m") } return b.String() } func parseColorTag(tag string) (name string, closing bool, ok bool) { if len(tag) < 3 || tag[0] != '<' || tag[len(tag)-1] != '>' { return "", false, false } closing = strings.HasPrefix(tag, "= 0; idx-- { if items[idx] != name { continue } copy(items[idx:], items[idx+1:]) lastIdx := len(items) - 1 items[lastIdx] = "" *stack = items[:lastIdx] return true } return false } func NowUnixNano() int64 { return time.Now().UnixNano() }