272 lines
5.7 KiB
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)
|
|
}
|