Files
MasterHttpRelayVPN/internal/logger/logger.go
T
2026-04-20 13:53:51 +03:30

322 lines
6.6 KiB
Go

// ==============================================================================
// 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, "</")
if closing {
name = tag[2 : len(tag)-1]
} else {
name = tag[1 : len(tag)-1]
}
_, ok = colorTagCodes[name]
return name, closing, ok
}
func restoreColorTag(stack *[]string, name string) bool {
if stack == nil || len(*stack) == 0 {
return false
}
items := *stack
for idx := len(items) - 1; idx >= 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()
}