Moonshark/utils/logger/logger.go

353 lines
6.5 KiB
Go

package logger
import (
"fmt"
"io"
"os"
"strings"
"sync"
"sync/atomic"
"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"
colorGray = "\033[90m"
)
// Log types
const (
TypeNone = iota
TypeDebug
TypeInfo
TypeWarning
TypeError
TypeServer
TypeFatal
)
// Type properties
var typeProps = map[int]struct {
tag string
color string
}{
TypeDebug: {"D", colorCyan},
TypeInfo: {"I", colorBlue},
TypeWarning: {"W", colorYellow},
TypeError: {"E", colorRed},
TypeServer: {"S", colorGreen},
TypeFatal: {"F", colorPurple},
}
const timeFormat = "15:04:05"
var (
globalLogger *Logger
globalLoggerOnce sync.Once
)
type Logger struct {
writer io.Writer
useColors bool
timeFormat string
showTimestamp bool
mu sync.Mutex
debugMode atomic.Bool
}
func GetLogger() *Logger {
globalLoggerOnce.Do(func() {
globalLogger = newLogger(true, true)
})
return globalLogger
}
func InitGlobalLogger(useColors bool, showTimestamp bool) {
globalLogger = newLogger(useColors, showTimestamp)
}
func newLogger(useColors bool, showTimestamp bool) *Logger {
return &Logger{
writer: os.Stdout,
useColors: useColors,
timeFormat: timeFormat,
showTimestamp: showTimestamp,
}
}
func New(useColors bool, showTimestamp bool) *Logger {
return newLogger(useColors, showTimestamp)
}
func (l *Logger) SetOutput(w io.Writer) {
l.mu.Lock()
l.writer = w
l.mu.Unlock()
}
func (l *Logger) TimeFormat() string {
return l.timeFormat
}
func (l *Logger) SetTimeFormat(format string) {
l.mu.Lock()
l.timeFormat = format
l.mu.Unlock()
}
func (l *Logger) EnableTimestamp() {
l.showTimestamp = true
}
func (l *Logger) DisableTimestamp() {
l.showTimestamp = false
}
func (l *Logger) EnableColors() {
l.useColors = true
}
func (l *Logger) DisableColors() {
l.useColors = false
}
func (l *Logger) EnableDebug() {
l.debugMode.Store(true)
}
func (l *Logger) DisableDebug() {
l.debugMode.Store(false)
}
func (l *Logger) IsDebugEnabled() bool {
return l.debugMode.Load()
}
func (l *Logger) applyColor(text, color string) string {
if l.useColors {
return color + text + colorReset
}
return text
}
func stripAnsiColors(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
}
func (l *Logger) writeMessage(logType int, message string, rawMode bool) {
if rawMode {
l.mu.Lock()
_, _ = fmt.Fprint(l.writer, message+"\n")
l.mu.Unlock()
return
}
parts := []string{}
if l.showTimestamp {
timestamp := time.Now().Format(l.timeFormat)
if l.useColors {
timestamp = l.applyColor(timestamp, colorGray)
}
parts = append(parts, timestamp)
}
if logType != TypeNone {
props := typeProps[logType]
tag := "[" + props.tag + "]"
if l.useColors {
tag = l.applyColor(tag, props.color)
}
parts = append(parts, tag)
}
parts = append(parts, message)
logLine := strings.Join(parts, " ") + "\n"
l.mu.Lock()
_, _ = fmt.Fprint(l.writer, logLine)
if logType == TypeFatal {
if f, ok := l.writer.(*os.File); ok {
_ = f.Sync()
}
}
l.mu.Unlock()
}
func (l *Logger) log(logType int, format string, args ...any) {
// Only filter debug messages
if logType == TypeDebug && !l.debugMode.Load() {
return
}
var message string
if len(args) > 0 {
message = fmt.Sprintf(format, args...)
} else {
message = format
}
l.writeMessage(logType, message, false)
if logType == TypeFatal {
os.Exit(1)
}
}
func (l *Logger) LogRaw(format string, args ...any) {
var message string
if len(args) > 0 {
message = fmt.Sprintf(format, args...)
} else {
message = format
}
if !l.useColors {
message = stripAnsiColors(message)
}
l.writeMessage(TypeInfo, message, true)
}
func (l *Logger) Debug(format string, args ...any) {
l.log(TypeDebug, format, args...)
}
func (l *Logger) Info(format string, args ...any) {
l.log(TypeInfo, format, args...)
}
func (l *Logger) Warning(format string, args ...any) {
l.log(TypeWarning, format, args...)
}
func (l *Logger) Error(format string, args ...any) {
l.log(TypeError, format, args...)
}
func (l *Logger) Fatal(format string, args ...any) {
l.log(TypeFatal, format, args...)
}
func (l *Logger) Server(format string, args ...any) {
l.log(TypeServer, format, args...)
}
func (l *Logger) LogRequest(statusCode int, method, path string, duration time.Duration) {
var statusColor string
switch {
case statusCode < 300:
statusColor = colorGreen
case statusCode < 400:
statusColor = colorCyan
case statusCode < 500:
statusColor = colorYellow
default:
statusColor = colorRed
}
var durationStr string
micros := duration.Microseconds()
if micros < 1000 {
durationStr = fmt.Sprintf("%.0fµs", float64(micros))
} else if micros < 1000000 {
durationStr = fmt.Sprintf("%.1fms", float64(micros)/1000)
} else {
durationStr = fmt.Sprintf("%.2fs", duration.Seconds())
}
message := fmt.Sprintf("%s %s %s %s",
l.applyColor("["+method+"]", colorGray),
l.applyColor(fmt.Sprintf("%d", statusCode), statusColor),
l.applyColor(path, colorGray),
l.applyColor(durationStr, colorGray),
)
l.writeMessage(TypeNone, message, false)
}
// Global functions
func Debug(format string, args ...any) {
GetLogger().Debug(format, args...)
}
func Info(format string, args ...any) {
GetLogger().Info(format, args...)
}
func Warning(format string, args ...any) {
GetLogger().Warning(format, args...)
}
func Error(format string, args ...any) {
GetLogger().Error(format, args...)
}
func Fatal(format string, args ...any) {
GetLogger().Fatal(format, args...)
}
func Server(format string, args ...any) {
GetLogger().Server(format, args...)
}
func LogRaw(format string, args ...any) {
GetLogger().LogRaw(format, args...)
}
func SetOutput(w io.Writer) {
GetLogger().SetOutput(w)
}
func TimeFormat() string {
return GetLogger().TimeFormat()
}
func EnableDebug() {
GetLogger().EnableDebug()
}
func DisableDebug() {
GetLogger().DisableDebug()
}
func IsDebugEnabled() bool {
return GetLogger().IsDebugEnabled()
}
func EnableTimestamp() {
GetLogger().EnableTimestamp()
}
func DisableTimestamp() {
GetLogger().DisableTimestamp()
}
func LogRequest(statusCode int, method, path string, duration time.Duration) {
GetLogger().LogRequest(statusCode, method, path, duration)
}