Moonshark/core/logger/logger.go

272 lines
5.7 KiB
Go

package logger
import (
"fmt"
"io"
"os"
"sync"
"time"
)
// ANSI color codes
const (
colorReset = "\033[0m"
colorRed = "\033[31m"
colorGreen = "\033[32m"
colorYellow = "\033[33m"
colorBlue = "\033[34m"
colorPurple = "\033[35m"
colorCyan = "\033[36m"
colorWhite = "\033[37m"
)
// Log levels
const (
LevelDebug = iota
LevelInfo
LevelWarning
LevelError
LevelFatal
)
// Level names and colors
var levelProps = map[int]struct {
tag string
color string
}{
LevelDebug: {"DBG", colorCyan},
LevelInfo: {"INF", colorBlue},
LevelWarning: {"WRN", colorYellow},
LevelError: {"ERR", colorRed},
LevelFatal: {"FTL", colorPurple},
}
// Time format for log messages
const timeFormat = "15:04:05"
// Logger handles logging operations
type Logger struct {
writer io.Writer
level int
useColors bool
timeFormat string
mu sync.Mutex // Mutex for thread-safe writing
}
// New creates a new logger
func New(minLevel int, useColors bool) *Logger {
return &Logger{
writer: os.Stdout,
level: minLevel,
useColors: useColors,
timeFormat: timeFormat,
}
}
// SetOutput changes the output destination
func (l *Logger) SetOutput(w io.Writer) {
l.mu.Lock()
defer l.mu.Unlock()
l.writer = w
}
// TimeFormat returns the current time format
func (l *Logger) TimeFormat() string {
return l.timeFormat
}
// SetTimeFormat changes the time format string
func (l *Logger) SetTimeFormat(format string) {
l.timeFormat = format
}
// SetLevel changes the minimum log level
func (l *Logger) SetLevel(level int) {
l.level = level
}
// EnableColors enables ANSI color codes in the output
func (l *Logger) EnableColors() {
l.useColors = true
}
// DisableColors disables ANSI color codes in the output
func (l *Logger) DisableColors() {
l.useColors = false
}
// writeMessage writes a formatted log message directly to the writer
func (l *Logger) writeMessage(level int, message string, rawMode bool) {
var logLine string
if rawMode {
// Raw mode - message is already formatted, just append newline
logLine = message + "\n"
} else {
// Standard format with timestamp, level tag, and message
now := time.Now().Format(l.timeFormat)
props := levelProps[level]
if l.useColors {
logLine = fmt.Sprintf("%s %s[%s]%s %s\n",
now, props.color, props.tag, colorReset, message)
} else {
logLine = fmt.Sprintf("%s [%s] %s\n",
now, props.tag, message)
}
}
// Asynchronously write the log message
go func(w io.Writer, data string) {
l.mu.Lock()
_, _ = fmt.Fprint(w, data)
l.mu.Unlock()
}(l.writer, logLine)
// For fatal errors, ensure we sync immediately in the current goroutine
if level == LevelFatal {
l.mu.Lock()
if f, ok := l.writer.(*os.File); ok {
_ = f.Sync()
}
l.mu.Unlock()
}
}
// log handles the core logging logic with level filtering
func (l *Logger) log(level int, format string, args ...any) {
if level < l.level {
return
}
var message string
if len(args) > 0 {
message = fmt.Sprintf(format, args...)
} else {
message = format
}
l.writeMessage(level, message, false)
// Exit on fatal errors
if level == LevelFatal {
os.Exit(1)
}
}
// LogRaw logs a message with raw formatting, bypassing the standard format
func (l *Logger) LogRaw(format string, args ...any) {
// Use info level for filtering
if LevelInfo < l.level {
return
}
var message string
if len(args) > 0 {
message = fmt.Sprintf(format, args...)
} else {
message = format
}
// Don't apply colors if disabled
if !l.useColors {
// Strip ANSI color codes if colors are disabled
message = removeAnsiColors(message)
}
l.writeMessage(LevelInfo, message, true)
}
// Simple helper to remove ANSI color codes
func removeAnsiColors(s string) string {
result := ""
inEscape := false
for _, c := range s {
if inEscape {
if c == 'm' {
inEscape = false
}
continue
}
if c == '\033' {
inEscape = true
continue
}
result += string(c)
}
return result
}
// Debug logs a debug message
func (l *Logger) Debug(format string, args ...any) {
l.log(LevelDebug, format, args...)
}
// Info logs an informational message
func (l *Logger) Info(format string, args ...any) {
l.log(LevelInfo, format, args...)
}
// Warning logs a warning message
func (l *Logger) Warning(format string, args ...any) {
l.log(LevelWarning, format, args...)
}
// Error logs an error message
func (l *Logger) Error(format string, args ...any) {
l.log(LevelError, format, args...)
}
// Fatal logs a fatal error message and exits
func (l *Logger) Fatal(format string, args ...any) {
l.log(LevelFatal, format, args...)
// No need for os.Exit here as it's handled in log()
}
// Default global logger
var defaultLogger = New(LevelInfo, true)
// Debug logs a debug message to the default logger
func Debug(format string, args ...any) {
defaultLogger.Debug(format, args...)
}
// Info logs an informational message to the default logger
func Info(format string, args ...any) {
defaultLogger.Info(format, args...)
}
// Warning logs a warning message to the default logger
func Warning(format string, args ...any) {
defaultLogger.Warning(format, args...)
}
// Error logs an error message to the default logger
func Error(format string, args ...any) {
defaultLogger.Error(format, args...)
}
// Fatal logs a fatal error message to the default logger and exits
func Fatal(format string, args ...any) {
defaultLogger.Fatal(format, args...)
}
// LogRaw logs a raw message to the default logger
func LogRaw(format string, args ...any) {
defaultLogger.LogRaw(format, args...)
}
// SetLevel changes the minimum log level of the default logger
func SetLevel(level int) {
defaultLogger.SetLevel(level)
}
// SetOutput changes the output destination of the default logger
func SetOutput(w io.Writer) {
defaultLogger.SetOutput(w)
}