From bf5a84171636e223902263d439900930924a1437 Mon Sep 17 00:00:00 2001 From: Sky Johnson Date: Sat, 24 May 2025 11:06:22 -0500 Subject: [PATCH] big ol logger refactor --- http/server.go | 7 +- http/utils.go | 50 ------- main.go | 137 ++++++++++--------- runner/fs.go | 9 +- runner/runner.go | 6 +- runner/sqlite.go | 3 +- tests/basic_test.go | 2 +- utils/color/color.go | 32 ++++- utils/config/config.go | 5 - utils/logger/logger.go | 290 +++++++++++++++-------------------------- watchers/api.go | 5 +- 11 files changed, 224 insertions(+), 322 deletions(-) diff --git a/http/server.go b/http/server.go index 8b5cd93..e8be780 100644 --- a/http/server.go +++ b/http/server.go @@ -9,6 +9,7 @@ import ( "Moonshark/runner" "Moonshark/sessions" "Moonshark/utils" + "Moonshark/utils/color" "Moonshark/utils/config" "Moonshark/utils/logger" "Moonshark/utils/metadata" @@ -73,7 +74,7 @@ func New(luaRouter *routers.LuaRouter, staticRouter *routers.StaticRouter, // ListenAndServe starts the server on the given address func (s *Server) ListenAndServe(addr string) error { - logger.Server("Catch the swell at http://localhost%s", addr) + logger.Info("Catch the swell at %s", color.Apply("http://localhost"+addr, color.Cyan)) return s.fasthttpServer.ListenAndServe(addr) } @@ -95,7 +96,7 @@ func (s *Server) handleRequest(ctx *fasthttp.RequestCtx) { if s.debugMode && path == "/debug/stats" { s.handleDebugStats(ctx) if s.loggingEnabled { - LogRequest(ctx.Response.StatusCode(), method, path, time.Since(start)) + logger.LogRequest(ctx.Response.StatusCode(), method, path, time.Since(start)) } return } @@ -103,7 +104,7 @@ func (s *Server) handleRequest(ctx *fasthttp.RequestCtx) { s.processRequest(ctx, method, path) if s.loggingEnabled { - LogRequest(ctx.Response.StatusCode(), method, path, time.Since(start)) + logger.LogRequest(ctx.Response.StatusCode(), method, path, time.Since(start)) } } diff --git a/http/utils.go b/http/utils.go index 5a734be..3128e78 100644 --- a/http/utils.go +++ b/http/utils.go @@ -3,13 +3,9 @@ package http import ( "crypto/rand" "encoding/base64" - "fmt" "mime/multipart" "strings" "sync" - "time" - - "Moonshark/utils/logger" "github.com/valyala/fasthttp" ) @@ -29,52 +25,6 @@ var ( } ) -// LogRequest logs an HTTP request with its status code and duration -func LogRequest(statusCode int, method, path string, duration time.Duration) { - var statusColor, methodColor string - - // Simplified color assignment - switch { - case statusCode < 300: - statusColor = "\u001b[32m" // Green for 2xx - case statusCode < 400: - statusColor = "\u001b[36m" // Cyan for 3xx - case statusCode < 500: - statusColor = "\u001b[33m" // Yellow for 4xx - default: - statusColor = "\u001b[31m" // Red for 5xx+ - } - - switch method { - case "GET": - methodColor = "\u001b[32m" - case "POST": - methodColor = "\u001b[34m" - case "PUT": - methodColor = "\u001b[33m" - case "DELETE": - methodColor = "\u001b[31m" - default: - methodColor = "\u001b[35m" - } - - // Optimized duration formatting - 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()) - } - - logger.Server("%s%d\u001b[0m %s%s\u001b[0m %s %s", - statusColor, statusCode, - methodColor, method, - path, durationStr) -} - // QueryToLua converts HTTP query args to a Lua-friendly map func QueryToLua(ctx *fasthttp.RequestCtx) map[string]any { args := ctx.QueryArgs() diff --git a/main.go b/main.go index 0751d16..3a611ba 100644 --- a/main.go +++ b/main.go @@ -8,6 +8,7 @@ import ( "os" "os/signal" "path/filepath" + "strconv" "syscall" "time" @@ -15,8 +16,10 @@ import ( "Moonshark/routers" "Moonshark/runner" "Moonshark/sessions" + "Moonshark/utils/color" "Moonshark/utils/config" "Moonshark/utils/logger" + "Moonshark/utils/metadata" "Moonshark/watchers" ) @@ -33,23 +36,42 @@ type Moonshark struct { func main() { configPath := flag.String("config", "config.lua", "Path to configuration file") - scriptPath := flag.String("script", "", "Path to Lua script to execute once") debugFlag := flag.Bool("debug", false, "Enable debug mode") + scriptPath := flag.String("script", "", "Path to Lua script to execute once") + scriptMode := *scriptPath != "" flag.Parse() - // Initialize logger with basic settings first - logger.InitGlobalLogger(logger.LevelInfo, true, false) + banner() + mode := "" + if scriptMode { + mode = "[Script Mode]" + } else { + mode = "[Server Mode]" + } + fmt.Printf("%s %s\n\n", color.Apply(mode, color.Gray), color.Apply("v"+metadata.Version, color.Blue)) + + // Initialize logger + logger.InitGlobalLogger(true, false) + + // Load config + cfg, err := config.Load(*configPath) + if err != nil { + logger.Warning("Config load failed: %v, using defaults", color.Apply(err.Error(), color.Red)) + cfg = config.New() + } + + // Setup logging with debug mode + if *debugFlag || cfg.Server.Debug { + logger.EnableDebug() + logger.Debug("Debug logging enabled") + } - scriptMode := *scriptPath != "" var moonshark *Moonshark - var err error if scriptMode { - logger.Server("Moonshark script execution mode 🦈") - moonshark, err = initScriptMode(*configPath, *debugFlag) + moonshark, err = initScriptMode(cfg) } else { - logger.Server("Moonshark server mode 🦈") - moonshark, err = initServerMode(*configPath, *debugFlag) + moonshark, err = initServerMode(cfg, *debugFlag) } if err != nil { @@ -82,23 +104,15 @@ func main() { <-stop fmt.Print("\n") - logger.Server("Shutdown signal received") + logger.Info("Shutdown signal received") } // initScriptMode initializes minimal components needed for script execution -func initScriptMode(configPath string, debug bool) (*Moonshark, error) { - moonshark := &Moonshark{scriptMode: true} - - // Load config (minimal validation for script mode) - cfg, err := config.Load(configPath) - if err != nil { - logger.Warning("Config load failed: %v, using defaults", err) - cfg = config.New() +func initScriptMode(cfg *config.Config) (*Moonshark, error) { + moonshark := &Moonshark{ + Config: cfg, + scriptMode: true, } - moonshark.Config = cfg - - // Setup logging - setupLogging(cfg, debug) // Only initialize the Lua runner with required paths runnerOpts := []runner.RunnerOption{ @@ -108,6 +122,7 @@ func initScriptMode(configPath string, debug bool) (*Moonshark, error) { runner.WithDataDir(cfg.Dirs.Data), } + var err error moonshark.LuaRunner, err = runner.NewRunner(runnerOpts...) if err != nil { return nil, fmt.Errorf("failed to initialize Lua runner: %v", err) @@ -118,22 +133,16 @@ func initScriptMode(configPath string, debug bool) (*Moonshark, error) { } // initServerMode initializes all components needed for server operation -func initServerMode(configPath string, debug bool) (*Moonshark, error) { - moonshark := &Moonshark{scriptMode: false} - - cfg, err := config.Load(configPath) - if err != nil { - logger.Warning("Config load failed: %v, using defaults", err) - cfg = config.New() +func initServerMode(cfg *config.Config, debug bool) (*Moonshark, error) { + moonshark := &Moonshark{ + Config: cfg, + scriptMode: false, } - moonshark.Config = cfg if debug { cfg.Server.Debug = true } - setupLogging(cfg, debug) - if err := initRouters(moonshark); err != nil { return nil, err } @@ -170,7 +179,7 @@ func (s *Moonshark) RunScript(scriptPath string) error { return fmt.Errorf("script file not found: %s", scriptPath) } - logger.Server("Executing: %s", scriptPath) + logger.Info("Executing: %s", scriptPath) resp, err := s.LuaRunner.RunScriptFile(scriptPath) if err != nil { @@ -178,9 +187,9 @@ func (s *Moonshark) RunScript(scriptPath string) error { } if resp != nil && resp.Body != nil { - logger.Server("Script result: %v", resp.Body) + logger.Info("Script result: %v", resp.Body) } else { - logger.Server("Script executed successfully (no return value)") + logger.Info("Script executed successfully (no return value)") } return nil @@ -192,7 +201,7 @@ func (s *Moonshark) Start() error { return errors.New("cannot start server in script mode") } - logger.Server("Surf's up on port %d!", s.Config.Server.Port) + logger.Info("Surf's up on port %s!", color.Apply(strconv.Itoa(s.Config.Server.Port), color.Cyan)) go func() { if err := s.HTTPServer.ListenAndServe(fmt.Sprintf(":%d", s.Config.Server.Port)); err != nil { @@ -207,7 +216,7 @@ func (s *Moonshark) Start() error { // Shutdown gracefully shuts down Moonshark func (s *Moonshark) Shutdown() error { - logger.Server("Shutting down...") + logger.Info("Shutting down...") if !s.scriptMode && s.HTTPServer != nil { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) @@ -228,30 +237,10 @@ func (s *Moonshark) Shutdown() error { s.LuaRunner.Close() } - logger.Server("Shutdown complete") + logger.Info("Shutdown complete") return nil } -func setupLogging(cfg *config.Config, debug bool) { - switch cfg.Server.LogLevel { - case "warn": - logger.SetLevel(logger.LevelWarning) - case "error": - logger.SetLevel(logger.LevelError) - case "server": - logger.SetLevel(logger.LevelServer) - case "fatal": - logger.SetLevel(logger.LevelFatal) - default: - logger.SetLevel(logger.LevelInfo) - } - - if debug || cfg.Server.Debug { - logger.EnableDebug() - logger.Debug("Debug logging enabled") - } -} - func dirExists(path string) bool { info, err := os.Stat(path) if err != nil { @@ -281,16 +270,16 @@ func initRouters(s *Moonshark) error { return fmt.Errorf("lua router init failed: %v", err) } } - logger.Info("Lua router initialized: %s", s.Config.Dirs.Routes) + logger.Info("LuaRouter is g2g! %s", color.Set(s.Config.Dirs.Routes, color.Yellow)) if dirExists(s.Config.Dirs.Static) { s.StaticRouter, err = routers.NewStaticRouter(s.Config.Dirs.Static) if err != nil { return fmt.Errorf("static router init failed: %v", err) } - logger.Info("Static router initialized: %s", s.Config.Dirs.Static) + logger.Info("StaticRouter is g2g! %s", color.Apply(s.Config.Dirs.Static, color.Yellow)) } else { - logger.Warning("Static directory not found: %s", s.Config.Dirs.Static) + logger.Warning("Static directory not found... %s", color.Apply(s.Config.Dirs.Static, color.Yellow)) } return nil @@ -298,13 +287,13 @@ func initRouters(s *Moonshark) error { func initRunner(s *Moonshark) error { if !dirExists(s.Config.Dirs.Override) { - logger.Warning("Override directory not found: %s", s.Config.Dirs.Override) + logger.Warning("Override directory not found... %s", color.Apply(s.Config.Dirs.Override, color.Yellow)) s.Config.Dirs.Override = "" } for _, dir := range s.Config.Dirs.Libs { if !dirExists(dir) { - logger.Warning("Lib directory not found: %s", dir) + logger.Warning("Lib directory not found... %s", color.Apply(dir, color.Yellow)) } } @@ -336,7 +325,7 @@ func initRunner(s *Moonshark) error { return fmt.Errorf("lua runner init failed: %v", err) } - logger.Server("Lua runner initialized with pool size: %d", poolSize) + logger.Info("LuaRunner is g2g with %s states!", color.Apply(strconv.Itoa(poolSize), color.Yellow)) return nil } @@ -365,8 +354,26 @@ func setupWatchers(s *Moonshark) error { return manager.UnwatchDirectory(dirPath) }) } - logger.Info("Watching %d module directories", len(moduleWatchers)) + plural := "" + if len(moduleWatchers) == 1 { + plural = "directory" + } else { + plural = "directories" + } + logger.Info("Watching %s module %s.", color.Apply(strconv.Itoa(len(moduleWatchers)), color.Yellow), plural) } return nil } + +func banner() { + banner := ` + _____ _________.__ __ + / \ ____ ____ ____ / _____/| |__ _____ _______| | __ + / \ / \ / _ \ / _ \ / \ \_____ \ | | \\__ \\_ __ \ |/ / +/ Y ( <_> | <_> ) | \/ \| Y \/ __ \| | \/ < +\____|__ /\____/ \____/|___| /_______ /|___| (____ /__| |__|_ \ + \/ \/ \/ \/ \/ \/ + ` + fmt.Println(color.Apply(banner, color.Blue)) +} diff --git a/runner/fs.go b/runner/fs.go index 220fa0d..43dc4d4 100644 --- a/runner/fs.go +++ b/runner/fs.go @@ -8,6 +8,7 @@ import ( "strings" "time" + "Moonshark/utils/color" "Moonshark/utils/logger" lru "git.sharkk.net/Go/LRU" @@ -51,7 +52,7 @@ func InitFS(basePath string) error { // Initialize file cache with 2000 entries (reasonable for most use cases) fileCache = lru.NewLRUCache(2000) - logger.Server("Virtual filesystem initialized at: %s", fsBasePath) + logger.Info("Filesystem is g2g! %s", color.Apply(fsBasePath, color.Yellow)) return nil } @@ -59,7 +60,11 @@ func InitFS(basePath string) error { func CleanupFS() { if fileCache != nil { fileCache.Clear() - logger.Server("File cache cleared - Stats: hits=%d, misses=%d", stats.hits, stats.misses) + logger.Info( + "File cache cleared - %s hits, %s misses", + color.Apply(fmt.Sprintf("%d", stats.hits), color.Yellow), + color.Apply(fmt.Sprintf("%d", stats.misses), color.Red), + ) } } diff --git a/runner/runner.go b/runner/runner.go index 358c228..3011338 100644 --- a/runner/runner.go +++ b/runner/runner.go @@ -1,6 +1,7 @@ package runner import ( + "Moonshark/utils/color" "Moonshark/utils/logger" "context" "errors" @@ -8,6 +9,7 @@ import ( "os" "path/filepath" "runtime" + "strconv" "sync" "sync/atomic" "time" @@ -130,7 +132,7 @@ func NewRunner(options ...RunnerOption) (*Runner, error) { // initializeStates creates and initializes all states in the pool func (r *Runner) initializeStates() error { - logger.Server("Initializing %d states...", r.poolSize) + logger.Info("[LuaRunner] Creating %s states...", color.Apply(strconv.Itoa(r.poolSize), color.Yellow)) for i := range r.poolSize { state, err := r.createState(i) @@ -302,7 +304,7 @@ func (r *Runner) RefreshStates() error { return ErrRunnerClosed } - logger.Server("Runner is refreshing all states...") + logger.Info("Runner is refreshing all states...") // Drain all states from the pool for { diff --git a/runner/sqlite.go b/runner/sqlite.go index e798db2..44e3bb5 100644 --- a/runner/sqlite.go +++ b/runner/sqlite.go @@ -13,6 +13,7 @@ import ( sqlite "zombiezen.com/go/sqlite" "zombiezen.com/go/sqlite/sqlitex" + "Moonshark/utils/color" "Moonshark/utils/logger" luajit "git.sharkk.net/Sky/LuaJIT-to-Go" @@ -48,7 +49,7 @@ func generateConnToken() string { // InitSQLite initializes the SQLite subsystem func InitSQLite(dir string) { dataDir = dir - logger.Server("SQLite initialized with data directory: %s", dir) + logger.Info("SQLite is g2g! %s", color.Apply(dir, color.Yellow)) // Start connection cleanup goroutine go cleanupIdleConnections() diff --git a/tests/basic_test.go b/tests/basic_test.go index 8effca6..e77ae5d 100644 --- a/tests/basic_test.go +++ b/tests/basic_test.go @@ -16,7 +16,7 @@ import ( // setupTestEnv initializes test components and returns cleanup function func setupTestEnv(b *testing.B) (*routers.LuaRouter, *runner.Runner, func()) { // Completely silence logging during benchmarks - logger.InitGlobalLogger(logger.LevelFatal, false, false) + logger.InitGlobalLogger(false, false) // Redirect standard logger output to discard log.SetOutput(io.Discard) diff --git a/utils/color/color.go b/utils/color/color.go index 4443b99..1fe1b41 100644 --- a/utils/color/color.go +++ b/utils/color/color.go @@ -13,14 +13,39 @@ const ( Gray = "\033[90m" ) -// Apply adds color to text if useColors is true -func Apply(text, color string, useColors bool) string { +var useColors = true + +// SetColors enables or disables colors globally +func SetColors(enabled bool) { + useColors = enabled +} + +// ColorsEnabled returns current global color setting +func ColorsEnabled() bool { + return useColors +} + +// Apply adds color to text using global color setting +func Apply(text, color string) string { if useColors { return color + text + Reset } return text } +// ApplyIf adds color to text if useColors is true (for backward compatibility) +func ApplyIf(text, color string, enabled bool) string { + if enabled { + return color + text + Reset + } + return text +} + +// Set adds color to text (always applies color, ignores global setting) +func Set(text, color string) string { + return color + text + Reset +} + // Strip removes ANSI color codes from a string func Strip(s string) string { result := "" @@ -33,14 +58,11 @@ func Strip(s string) string { } continue } - if c == '\033' { inEscape = true continue } - result += string(c) } - return result } diff --git a/utils/config/config.go b/utils/config/config.go index 389c1bf..8a2882d 100644 --- a/utils/config/config.go +++ b/utils/config/config.go @@ -16,7 +16,6 @@ type Config struct { Server struct { Port int Debug bool - LogLevel string HTTPLogging bool } @@ -49,7 +48,6 @@ func New() *Config { // Server defaults config.Server.Port = 3117 config.Server.Debug = false - config.Server.LogLevel = "info" config.Server.HTTPLogging = false // Runner defaults @@ -116,9 +114,6 @@ func applyConfig(config *Config, values map[string]any) { if v, ok := serverTable["debug"].(bool); ok { config.Server.Debug = v } - if v, ok := serverTable["log_level"].(string); ok { - config.Server.LogLevel = v - } if v, ok := serverTable["http_logging"].(bool); ok { config.Server.HTTPLogging = v } diff --git a/utils/logger/logger.go b/utils/logger/logger.go index 701410f..81115f2 100644 --- a/utils/logger/logger.go +++ b/utils/logger/logger.go @@ -19,145 +19,117 @@ const ( colorBlue = "\033[34m" colorPurple = "\033[35m" colorCyan = "\033[36m" - colorWhite = "\033[37m" colorGray = "\033[90m" ) -// Log levels +// Log types const ( - LevelDebug = iota - LevelInfo - LevelWarning - LevelError - LevelServer - LevelFatal + TypeNone = iota + TypeDebug + TypeInfo + TypeWarning + TypeError + TypeServer + TypeFatal ) -// Level names and colors -var levelProps = map[int]struct { +// Type properties +var typeProps = map[int]struct { tag string color string }{ - LevelDebug: {"D", colorCyan}, - LevelInfo: {"I", colorBlue}, - LevelWarning: {"W", colorYellow}, - LevelError: {"E", colorRed}, - LevelServer: {"S", colorGreen}, - LevelFatal: {"F", colorPurple}, + TypeDebug: {"D", colorCyan}, + TypeInfo: {"I", colorBlue}, + TypeWarning: {"W", colorYellow}, + TypeError: {"E", colorRed}, + TypeServer: {"S", colorGreen}, + TypeFatal: {"F", colorPurple}, } -// Time format for log messages const timeFormat = "15:04:05" -// Single global logger instance with mutex for safe initialization var ( globalLogger *Logger globalLoggerOnce sync.Once ) -// Logger handles logging operations type Logger struct { writer io.Writer - level int useColors bool timeFormat string - showTimestamp bool // Whether to show timestamp - mu sync.Mutex // Mutex for thread-safe writing - debugMode atomic.Bool // Force debug logging regardless of level + showTimestamp bool + mu sync.Mutex + debugMode atomic.Bool } -// GetLogger returns the global logger instance, creating it if needed func GetLogger() *Logger { globalLoggerOnce.Do(func() { - globalLogger = newLogger(LevelInfo, true, true) + globalLogger = newLogger(true, true) }) return globalLogger } -// InitGlobalLogger initializes the global logger with custom settings -func InitGlobalLogger(minLevel int, useColors bool, showTimestamp bool) { - // Reset the global logger instance - globalLogger = newLogger(minLevel, useColors, showTimestamp) +func InitGlobalLogger(useColors bool, showTimestamp bool) { + globalLogger = newLogger(useColors, showTimestamp) } -// newLogger creates a new logger instance (internal use) -func newLogger(minLevel int, useColors bool, showTimestamp bool) *Logger { - logger := &Logger{ +func newLogger(useColors bool, showTimestamp bool) *Logger { + return &Logger{ writer: os.Stdout, - level: minLevel, useColors: useColors, timeFormat: timeFormat, showTimestamp: showTimestamp, } - - return logger } -// New creates a new logger (deprecated - use GetLogger() instead) -func New(minLevel int, useColors bool, showTimestamp bool) *Logger { - return newLogger(minLevel, useColors, showTimestamp) +func New(useColors bool, showTimestamp bool) *Logger { + return newLogger(useColors, showTimestamp) } -// SetOutput changes the output destination func (l *Logger) SetOutput(w io.Writer) { l.mu.Lock() - defer l.mu.Unlock() l.writer = w + l.mu.Unlock() } -// 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.mu.Lock() - defer l.mu.Unlock() l.timeFormat = format + l.mu.Unlock() } -// EnableTimestamp enables timestamp display func (l *Logger) EnableTimestamp() { l.showTimestamp = true } -// DisableTimestamp disables timestamp display func (l *Logger) DisableTimestamp() { l.showTimestamp = false } -// 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 } -// EnableDebug forces debug logs to be shown regardless of level func (l *Logger) EnableDebug() { l.debugMode.Store(true) } -// DisableDebug stops forcing debug logs func (l *Logger) DisableDebug() { l.debugMode.Store(false) } -// IsDebugEnabled returns true if debug mode is enabled func (l *Logger) IsDebugEnabled() bool { return l.debugMode.Load() } -// applyColor applies color to text if colors are enabled func (l *Logger) applyColor(text, color string) string { if l.useColors { return color + text + colorReset @@ -165,7 +137,6 @@ func (l *Logger) applyColor(text, color string) string { return text } -// stripAnsiColors removes ANSI color codes from a string func stripAnsiColors(s string) string { result := "" inEscape := false @@ -177,56 +148,48 @@ func stripAnsiColors(s string) string { } continue } - if c == '\033' { inEscape = true continue } - result += string(c) } - return result } -// writeMessage writes a formatted log message directly to the writer -func (l *Logger) writeMessage(level int, message string, rawMode bool) { - var logLine string - +func (l *Logger) writeMessage(logType int, message string, rawMode bool) { if rawMode { - // Raw mode - message is already formatted, just append newline - logLine = message + "\n" - } else { - // Standard format with level tag and optional timestamp - props := levelProps[level] - - if l.showTimestamp { - now := time.Now().Format(l.timeFormat) - - if l.useColors { - timestamp := l.applyColor(now, colorGray) - tag := l.applyColor("["+props.tag+"]", props.color) - logLine = fmt.Sprintf("%s %s %s\n", timestamp, tag, message) - } else { - logLine = fmt.Sprintf("%s [%s] %s\n", now, props.tag, message) - } - } else { - // No timestamp, just level tag and message - if l.useColors { - tag := l.applyColor("["+props.tag+"]", props.color) - logLine = fmt.Sprintf("%s %s\n", tag, message) - } else { - logLine = fmt.Sprintf("[%s] %s\n", props.tag, message) - } - } + l.mu.Lock() + _, _ = fmt.Fprint(l.writer, message+"\n") + l.mu.Unlock() + return } - // Synchronously write the log message + 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) - - // For fatal errors, ensure we sync immediately - if level == LevelFatal { + if logType == TypeFatal { if f, ok := l.writer.(*os.File); ok { _ = f.Sync() } @@ -234,15 +197,12 @@ func (l *Logger) writeMessage(level int, message string, rawMode bool) { l.mu.Unlock() } -// log handles the core logging logic with level filtering -func (l *Logger) log(level int, format string, args ...any) { - // Check if we should log this message - // Either level is high enough OR (it's a debug message AND debug mode is enabled) - if level < l.level && !(level == LevelDebug && l.debugMode.Load()) { +func (l *Logger) log(logType int, format string, args ...any) { + // Only filter debug messages + if logType == TypeDebug && !l.debugMode.Load() { return } - // Format message var message string if len(args) > 0 { message = fmt.Sprintf(format, args...) @@ -250,21 +210,14 @@ func (l *Logger) log(level int, format string, args ...any) { message = format } - l.writeMessage(level, message, false) + l.writeMessage(logType, message, false) - // Exit on fatal errors - if level == LevelFatal { + if logType == TypeFatal { 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...) @@ -272,163 +225,128 @@ func (l *Logger) LogRaw(format string, args ...any) { message = format } - // Don't apply colors if disabled if !l.useColors { - // Strip ANSI color codes if colors are disabled message = stripAnsiColors(message) } - l.writeMessage(LevelInfo, message, true) + l.writeMessage(TypeInfo, message, true) } -// Debug logs a debug message func (l *Logger) Debug(format string, args ...any) { - l.log(LevelDebug, format, args...) + l.log(TypeDebug, format, args...) } -// Info logs an informational message func (l *Logger) Info(format string, args ...any) { - l.log(LevelInfo, format, args...) + l.log(TypeInfo, format, args...) } -// Warning logs a warning message func (l *Logger) Warning(format string, args ...any) { - l.log(LevelWarning, format, args...) + l.log(TypeWarning, format, args...) } -// Error logs an error message func (l *Logger) Error(format string, args ...any) { - l.log(LevelError, format, args...) + l.log(TypeError, 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() + l.log(TypeFatal, format, args...) } -// Server logs a server message func (l *Logger) Server(format string, args ...any) { - l.log(LevelServer, format, args...) + l.log(TypeServer, format, args...) } -// Global helper functions that use the global logger +func (l *Logger) LogRequest(statusCode int, method, path string, duration time.Duration) { + var statusColor string -// Debug logs a debug message to the global logger + 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...) } -// Info logs an informational message to the global logger func Info(format string, args ...any) { GetLogger().Info(format, args...) } -// Warning logs a warning message to the global logger func Warning(format string, args ...any) { GetLogger().Warning(format, args...) } -// Error logs an error message to the global logger func Error(format string, args ...any) { GetLogger().Error(format, args...) } -// Fatal logs a fatal error message to the global logger and exits func Fatal(format string, args ...any) { GetLogger().Fatal(format, args...) } -// Server logs a server message to the global logger func Server(format string, args ...any) { GetLogger().Server(format, args...) } -// LogRaw logs a raw message to the global logger func LogRaw(format string, args ...any) { GetLogger().LogRaw(format, args...) } -// SetLevel changes the minimum log level of the global logger -func SetLevel(level int) { - GetLogger().SetLevel(level) -} - -// SetOutput changes the output destination of the global logger func SetOutput(w io.Writer) { GetLogger().SetOutput(w) } -// TimeFormat returns the current time format of the global logger func TimeFormat() string { return GetLogger().TimeFormat() } -// EnableDebug enables debug messages regardless of log level func EnableDebug() { GetLogger().EnableDebug() } -// DisableDebug disables forced debug messages func DisableDebug() { GetLogger().DisableDebug() } -// IsDebugEnabled returns true if debug mode is enabled func IsDebugEnabled() bool { return GetLogger().IsDebugEnabled() } -// EnableTimestamp enables timestamp display func EnableTimestamp() { GetLogger().EnableTimestamp() } -// DisableTimestamp disables timestamp display func DisableTimestamp() { GetLogger().DisableTimestamp() } -// LogSpacer adds a horizontal line separator to the log output -func (l *Logger) LogSpacer() { - l.mu.Lock() - defer l.mu.Unlock() - - // Calculate spacer width - tagWidth := 7 // Standard width of tag area "[DEBUG]" - - var spacer string - if l.showTimestamp { - // Format: "15:04:05 [DEBUG] ----" - timeWidth := len(time.Now().Format(l.timeFormat)) - tagSpacer := strings.Repeat("-", tagWidth) - restSpacer := strings.Repeat("-", 20) // Fixed width for the rest - - if l.useColors { - timeStr := l.applyColor(strings.Repeat("-", timeWidth), colorGray) - tagStr := l.applyColor(tagSpacer, colorCyan) - spacer = fmt.Sprintf("%s %s %s\n", timeStr, tagStr, restSpacer) - } else { - spacer = fmt.Sprintf("%s %s %s\n", - strings.Repeat("-", timeWidth), tagSpacer, restSpacer) - } - } else { - // No timestamp: "[DEBUG] ----" - tagSpacer := strings.Repeat("-", tagWidth) - restSpacer := strings.Repeat("-", 20) // Fixed width for the rest - - if l.useColors { - tagStr := l.applyColor(tagSpacer, colorCyan) - spacer = fmt.Sprintf("%s %s\n", tagStr, restSpacer) - } else { - spacer = fmt.Sprintf("%s %s\n", tagSpacer, restSpacer) - } - } - - _, _ = fmt.Fprint(l.writer, spacer) -} - -// LogSpacer adds a horizontal line separator to the global logger -func LogSpacer() { - GetLogger().LogSpacer() +func LogRequest(statusCode int, method, path string, duration time.Duration) { + GetLogger().LogRequest(statusCode, method, path, duration) } diff --git a/watchers/api.go b/watchers/api.go index 26252f0..20bfb31 100644 --- a/watchers/api.go +++ b/watchers/api.go @@ -7,6 +7,7 @@ import ( "Moonshark/routers" "Moonshark/runner" + "Moonshark/utils/color" "Moonshark/utils/logger" ) @@ -47,7 +48,7 @@ func WatchLuaRouter(router *routers.LuaRouter, runner *runner.Runner, routesDir return nil, fmt.Errorf("failed to watch directory: %w", err) } - logger.Info("Started watching Lua routes directory: %s", routesDir) + logger.Info("Started watching Lua routes! %s", color.Apply(routesDir, color.Yellow)) return watcher, nil } @@ -76,7 +77,7 @@ func WatchLuaModules(luaRunner *runner.Runner, libDirs []string) ([]*DirectoryWa } watchers = append(watchers, watcher) - logger.Info("Started watching Lua modules directory: %s", dir) + logger.Info("Started watching Lua modules! %s", color.Apply(dir, color.Yellow)) } return watchers, nil