From 87eaddd8e41f267ca8a65f98273f902d1581c6cb Mon Sep 17 00:00:00 2001 From: Sky Johnson Date: Thu, 1 May 2025 12:24:26 -0500 Subject: [PATCH] refactor --- core/http/{Server.go => server.go} | 37 +- core/http/{Utils.go => utils.go} | 0 core/{Moonshark.go => moonshark.go} | 12 +- core/routers/{Errors.go => errors.go} | 0 core/routers/{LuaRouter.go => luaRouter.go} | 15 +- .../{StaticRouter.go => staticRouter.go} | 0 core/runner/{Context.go => context.go} | 0 core/runner/{Embed.go => embed.go} | 12 +- core/runner/{Http.go => http.go} | 0 .../{ModuleLoader.go => moduleLoader.go} | 0 core/runner/{Response.go => response.go} | 0 core/runner/{Runner.go => runner.go} | 16 +- core/runner/{Sandbox.go => sandbox.go} | 10 +- core/runner/sandbox.lua | 10 + core/sessions/{Manager.go => manager.go} | 0 core/sessions/{Session.go => session.go} | 0 core/utils/color/color.go | 46 ++ core/utils/config/{Config.go => config.go} | 0 core/utils/{Debug.go => debug.go} | 2 +- core/utils/{Dirs.go => dirs.go} | 0 core/utils/{ErrorPages.go => errorPages.go} | 0 core/utils/logger/{Logger.go => logger.go} | 437 +++++------------- .../metadata/metadata.go} | 0 core/watchers/{Api.go => api.go} | 0 core/watchers/{Dir.go => dir.go} | 0 core/watchers/{Manager.go => manager.go} | 0 26 files changed, 194 insertions(+), 403 deletions(-) rename core/http/{Server.go => server.go} (85%) rename core/http/{Utils.go => utils.go} (100%) rename core/{Moonshark.go => moonshark.go} (95%) rename core/routers/{Errors.go => errors.go} (100%) rename core/routers/{LuaRouter.go => luaRouter.go} (97%) rename core/routers/{StaticRouter.go => staticRouter.go} (100%) rename core/runner/{Context.go => context.go} (100%) rename core/runner/{Embed.go => embed.go} (70%) rename core/runner/{Http.go => http.go} (100%) rename core/runner/{ModuleLoader.go => moduleLoader.go} (100%) rename core/runner/{Response.go => response.go} (100%) rename core/runner/{Runner.go => runner.go} (94%) rename core/runner/{Sandbox.go => sandbox.go} (95%) rename core/sessions/{Manager.go => manager.go} (100%) rename core/sessions/{Session.go => session.go} (100%) create mode 100644 core/utils/color/color.go rename core/utils/config/{Config.go => config.go} (100%) rename core/utils/{Debug.go => debug.go} (99%) rename core/utils/{Dirs.go => dirs.go} (100%) rename core/utils/{ErrorPages.go => errorPages.go} (100%) rename core/utils/logger/{Logger.go => logger.go} (53%) rename core/{metadata/Metadata.go => utils/metadata/metadata.go} (100%) rename core/watchers/{Api.go => api.go} (100%) rename core/watchers/{Dir.go => dir.go} (100%) rename core/watchers/{Manager.go => manager.go} (100%) diff --git a/core/http/Server.go b/core/http/server.go similarity index 85% rename from core/http/Server.go rename to core/http/server.go index f5a333b..58233e5 100644 --- a/core/http/Server.go +++ b/core/http/server.go @@ -4,13 +4,13 @@ import ( "context" "time" - "Moonshark/core/metadata" "Moonshark/core/routers" "Moonshark/core/runner" "Moonshark/core/sessions" "Moonshark/core/utils" "Moonshark/core/utils/config" "Moonshark/core/utils/logger" + "Moonshark/core/utils/metadata" "github.com/valyala/fasthttp" ) @@ -28,7 +28,7 @@ type Server struct { errorConfig utils.ErrorPageConfig } -// New creates a new HTTP server with optimized connection settings +// New creates a new HTTP server func New(luaRouter *routers.LuaRouter, staticRouter *routers.StaticRouter, runner *runner.Runner, loggingEnabled bool, debugMode bool, overrideDir string, config *config.Config) *Server { @@ -47,7 +47,6 @@ func New(luaRouter *routers.LuaRouter, staticRouter *routers.StaticRouter, }, } - // Configure fasthttp server server.fasthttpServer = &fasthttp.Server{ Handler: server.handleRequest, Name: "Moonshark/" + metadata.Version, @@ -59,7 +58,7 @@ func New(luaRouter *routers.LuaRouter, staticRouter *routers.StaticRouter, TCPKeepalivePeriod: 60 * time.Second, ReduceMemoryUsage: true, GetOnly: false, - DisablePreParseMultipartForm: true, // We'll handle parsing manually + DisablePreParseMultipartForm: true, } return server @@ -67,7 +66,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.ServerCont("Catch the swell at http://localhost%s", addr) + logger.Server("Catch the swell at http://localhost%s", addr) return s.fasthttpServer.ListenAndServe(addr) } @@ -82,11 +81,9 @@ func (s *Server) handleRequest(ctx *fasthttp.RequestCtx) { method := string(ctx.Method()) path := string(ctx.Path()) - // Special case for debug stats when debug mode is enabled if s.debugMode && path == "/debug/stats" { s.handleDebugStats(ctx) - // Log request if s.loggingEnabled { duration := time.Since(start) LogRequest(ctx.Response.StatusCode(), method, path, duration) @@ -94,10 +91,8 @@ func (s *Server) handleRequest(ctx *fasthttp.RequestCtx) { return } - // Process the request s.processRequest(ctx) - // Log the request if s.loggingEnabled { duration := time.Since(start) LogRequest(ctx.Response.StatusCode(), method, path, duration) @@ -111,23 +106,19 @@ func (s *Server) processRequest(ctx *fasthttp.RequestCtx) { logger.Debug("Processing request %s %s", method, path) - // Try Lua routes first params := &routers.Params{} bytecode, scriptPath, found := s.luaRouter.GetBytecode(method, path, params) // Check if we found a route but it has no valid bytecode (compile error) if found && len(bytecode) == 0 { - // Get the actual error from the router errorMsg := "Route exists but failed to compile. Check server logs for details." - // Get the actual node to access its error if node, _ := s.luaRouter.GetNodeWithError(method, path, params); node != nil && node.Error != nil { errorMsg = node.Error.Error() } logger.Error("%s %s - %s", method, path, errorMsg) - // Show error page with the actual error message ctx.SetContentType("text/html; charset=utf-8") ctx.SetStatusCode(fasthttp.StatusInternalServerError) errorHTML := utils.InternalErrorPage(s.errorConfig, path, errorMsg) @@ -139,13 +130,11 @@ func (s *Server) processRequest(ctx *fasthttp.RequestCtx) { return } - // Then try static files if _, found := s.staticRouter.Match(path); found { s.staticRouter.ServeHTTP(ctx) return } - // No route found - 404 Not Found ctx.SetContentType("text/html; charset=utf-8") ctx.SetStatusCode(fasthttp.StatusNotFound) ctx.SetBody([]byte(utils.NotFoundPage(s.errorConfig, path))) @@ -159,19 +148,16 @@ func (s *Server) handleLuaRoute(ctx *fasthttp.RequestCtx, bytecode []byte, scrip host := string(ctx.Host()) - // Set up additional context values luaCtx.Set("method", method) luaCtx.Set("path", path) luaCtx.Set("host", host) - // Add session data session := s.sessionManager.GetSessionFromRequest(ctx) luaCtx.Set("session", map[string]any{ "id": session.ID, "data": session.Data, }) - // URL parameters if params.Count > 0 { paramMap := make(map[string]any, params.Count) for i, key := range params.Keys { @@ -182,7 +168,6 @@ func (s *Server) handleLuaRoute(ctx *fasthttp.RequestCtx, bytecode []byte, scrip luaCtx.Set("params", make(map[string]any)) } - // Parse form data for POST/PUT/PATCH requests if method == "POST" || method == "PUT" || method == "PATCH" { formData, err := ParseForm(ctx) if err == nil && len(formData) > 0 { @@ -197,12 +182,10 @@ func (s *Server) handleLuaRoute(ctx *fasthttp.RequestCtx, bytecode []byte, scrip luaCtx.Set("form", make(map[string]any)) } - // Execute Lua script response, err := s.luaRunner.Run(bytecode, luaCtx, scriptPath) if err != nil { logger.Error("Error executing Lua route: %v", err) - // General error handling ctx.SetContentType("text/html; charset=utf-8") ctx.SetStatusCode(fasthttp.StatusInternalServerError) errorHTML := utils.InternalErrorPage(s.errorConfig, path, err.Error()) @@ -210,25 +193,19 @@ func (s *Server) handleLuaRoute(ctx *fasthttp.RequestCtx, bytecode []byte, scrip return } - // Handle persisting session data for k, v := range response.SessionData { session.Set(k, v) } s.sessionManager.ApplySessionCookie(ctx, session) - // Apply response to HTTP context runner.ApplyResponse(response, ctx) - - // Release the response when done runner.ReleaseResponse(response) } // handleDebugStats displays debug statistics func (s *Server) handleDebugStats(ctx *fasthttp.RequestCtx) { - // Collect system stats stats := utils.CollectSystemStats(s.config) - // Add component stats routeCount, bytecodeBytes := s.luaRouter.GetRouteStats() //stateCount := s.luaRunner.GetStateCount() //activeStates := s.luaRunner.GetActiveStateCount() @@ -240,11 +217,7 @@ func (s *Server) handleDebugStats(ctx *fasthttp.RequestCtx) { //ActiveStates: activeStates, } - // Generate HTML page - html := utils.DebugStatsPage(stats) - - // Send the response ctx.SetContentType("text/html; charset=utf-8") ctx.SetStatusCode(fasthttp.StatusOK) - ctx.SetBody([]byte(html)) + ctx.SetBody([]byte(utils.DebugStatsPage(stats))) } diff --git a/core/http/Utils.go b/core/http/utils.go similarity index 100% rename from core/http/Utils.go rename to core/http/utils.go diff --git a/core/Moonshark.go b/core/moonshark.go similarity index 95% rename from core/Moonshark.go rename to core/moonshark.go index 0f8c004..13dd491 100644 --- a/core/Moonshark.go +++ b/core/moonshark.go @@ -78,7 +78,7 @@ func (s *Moonshark) loadConfig(configPath string) error { s.Config, err = config.Load(configPath) if err != nil { logger.Warning("Wipeout! Couldn't load config file: %v", err) - logger.WarningCont("Rolling with the default setup") + logger.Warning("Rolling with the default setup") s.Config = config.New() } @@ -226,14 +226,6 @@ func (s *Moonshark) setupWatchers() error { func (s *Moonshark) Start() error { logger.Server("Surf's up on port %d!", s.Config.Server.Port) - // Log HTTP logging status - if s.Config.Server.HTTPLogging { - logger.ServerCont("HTTP logging is turned on - watch those waves") - } else { - logger.ServerCont("HTTP logging is turned off - waves are flat bro") - } - - // Start the server in a non-blocking way go func() { if err := s.HTTPServer.ListenAndServe(fmt.Sprintf(":%d", s.Config.Server.Port)); err != nil { if err.Error() != "http: Server closed" { @@ -265,6 +257,6 @@ func (s *Moonshark) Shutdown() error { s.LuaRunner.Close() - logger.ServerCont("Server stopped") + logger.Server("Server stopped") return nil } diff --git a/core/routers/Errors.go b/core/routers/errors.go similarity index 100% rename from core/routers/Errors.go rename to core/routers/errors.go diff --git a/core/routers/LuaRouter.go b/core/routers/luaRouter.go similarity index 97% rename from core/routers/LuaRouter.go rename to core/routers/luaRouter.go index e74f412..72b6872 100644 --- a/core/routers/LuaRouter.go +++ b/core/routers/luaRouter.go @@ -47,7 +47,6 @@ type LuaRouter struct { failedRoutes map[string]*RouteError // Track failed routes mu sync.RWMutex // Lock for concurrent access to routes - // Cache for route matches and bytecode routeCache *fastcache.Cache // Cache for route lookups bytecodeCache *fastcache.Cache // Cache for compiled bytecode } @@ -64,7 +63,6 @@ type node struct { // NewLuaRouter creates a new LuaRouter instance func NewLuaRouter(routesDir string) (*LuaRouter, error) { - // Verify routes directory exists info, err := os.Stat(routesDir) if err != nil { return nil, err @@ -81,7 +79,6 @@ func NewLuaRouter(routesDir string) (*LuaRouter, error) { bytecodeCache: fastcache.New(defaultBytecodeMaxBytes), } - // Initialize method trees methods := []string{"GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"} for _, method := range methods { r.routes[method] = &node{ @@ -89,7 +86,6 @@ func NewLuaRouter(routesDir string) (*LuaRouter, error) { } } - // Build routes err = r.buildRoutes() // If some routes failed to compile, return the router with a warning error @@ -102,7 +98,6 @@ func NewLuaRouter(routesDir string) (*LuaRouter, error) { // buildRoutes scans the routes directory and builds the routing tree func (r *LuaRouter) buildRoutes() error { - // Clear failed routes map r.failedRoutes = make(map[string]*RouteError) return filepath.Walk(r.routesDir, func(path string, info os.FileInfo, err error) error { @@ -110,38 +105,31 @@ func (r *LuaRouter) buildRoutes() error { return err } - // Skip directories if info.IsDir() { return nil } - // Only process .lua files if !strings.HasSuffix(info.Name(), ".lua") { return nil } - // Extract method from filename method := strings.ToUpper(strings.TrimSuffix(info.Name(), ".lua")) - // Check if valid method root, exists := r.routes[method] if !exists { - return nil // Skip invalid methods + return nil } - // Get relative path for URL relDir, err := filepath.Rel(r.routesDir, filepath.Dir(path)) if err != nil { return err } - // Build URL path urlPath := "/" if relDir != "." { urlPath = "/" + strings.ReplaceAll(relDir, "\\", "/") } - // Add route to tree - continue even if there are errors r.addRoute(root, urlPath, path, info.ModTime()) return nil }) @@ -162,7 +150,6 @@ func (r *LuaRouter) addRoute(root *node, urlPath, handlerPath string, modTime ti } current = current.paramChild } else { - // Create or get static child child, exists := current.staticChild[segment] if !exists { child = &node{ diff --git a/core/routers/StaticRouter.go b/core/routers/staticRouter.go similarity index 100% rename from core/routers/StaticRouter.go rename to core/routers/staticRouter.go diff --git a/core/runner/Context.go b/core/runner/context.go similarity index 100% rename from core/runner/Context.go rename to core/runner/context.go diff --git a/core/runner/Embed.go b/core/runner/embed.go similarity index 70% rename from core/runner/Embed.go rename to core/runner/embed.go index 2f75547..6371a08 100644 --- a/core/runner/Embed.go +++ b/core/runner/embed.go @@ -21,18 +21,16 @@ var ( // precompileSandboxCode compiles the sandbox.lua code to bytecode once func precompileSandboxCode() { - // Create temporary state for compilation tempState := luajit.New() if tempState == nil { - logger.ErrorCont("Failed to create temp Lua state for bytecode compilation") - return + logger.Fatal("Failed to create temp Lua state for bytecode compilation") } defer tempState.Close() defer tempState.Cleanup() code, err := tempState.CompileBytecode(sandboxLuaCode, "sandbox.lua") if err != nil { - logger.ErrorCont("Failed to compile sandbox code: %v", err) + logger.Error("Failed to compile sandbox code: %v", err) return } @@ -40,7 +38,7 @@ func precompileSandboxCode() { copy(bytecode, code) sandboxBytecode.Store(&bytecode) - logger.ServerCont("Successfully precompiled sandbox.lua to bytecode (%d bytes)", len(code)) + logger.Debug("Successfully precompiled sandbox.lua to bytecode (%d bytes)", len(code)) } // loadSandboxIntoState loads the sandbox code into a Lua state @@ -50,14 +48,14 @@ func loadSandboxIntoState(state *luajit.State, verbose bool) error { bytecode := sandboxBytecode.Load() if bytecode != nil && len(*bytecode) > 0 { if verbose { - logger.ServerCont("Loading sandbox.lua from precompiled bytecode") // piggyback off Sandbox.go's Setup() + logger.Debug("Loading sandbox.lua from precompiled bytecode") } return state.LoadAndRunBytecode(*bytecode, "sandbox.lua") } // Fallback to direct execution if verbose { - logger.WarningCont("Using non-precompiled sandbox.lua (bytecode compilation failed)") + logger.Warning("Using non-precompiled sandbox.lua (bytecode compilation failed)") } return state.DoString(sandboxLuaCode) } diff --git a/core/runner/Http.go b/core/runner/http.go similarity index 100% rename from core/runner/Http.go rename to core/runner/http.go diff --git a/core/runner/ModuleLoader.go b/core/runner/moduleLoader.go similarity index 100% rename from core/runner/ModuleLoader.go rename to core/runner/moduleLoader.go diff --git a/core/runner/Response.go b/core/runner/response.go similarity index 100% rename from core/runner/Response.go rename to core/runner/response.go diff --git a/core/runner/Runner.go b/core/runner/runner.go similarity index 94% rename from core/runner/Runner.go rename to core/runner/runner.go index 676bccc..96a5eb1 100644 --- a/core/runner/Runner.go +++ b/core/runner/runner.go @@ -121,7 +121,7 @@ func (r *Runner) initializeStates() error { func (r *Runner) createState(index int) (*State, error) { verbose := index == 0 if verbose { - logger.ServerCont("Creating Lua state %d", index) + logger.Debug("Creating Lua state %d", index) } L := luajit.New() @@ -153,7 +153,7 @@ func (r *Runner) createState(index int) (*State, error) { } if verbose { - logger.ServerCont("Lua state %d initialized successfully", index) + logger.Debug("Lua state %d initialized successfully", index) } return &State{ @@ -289,7 +289,7 @@ cleanup: for i, state := range r.states { if state != nil { if state.inUse { - logger.WarningCont("Attempting to refresh state %d that is in use", i) + logger.Warning("Attempting to refresh state %d that is in use", i) } state.L.Cleanup() state.L.Close() @@ -302,7 +302,7 @@ cleanup: return err } - logger.ServerCont("All states refreshed successfully") + logger.Debug("All states refreshed successfully") return nil } @@ -314,13 +314,13 @@ func (r *Runner) NotifyFileChanged(filePath string) bool { // Check if it's a module file module, isModule := r.moduleLoader.GetModuleByPath(filePath) if isModule { - logger.DebugCont("File is a module: %s", module) + logger.Debug("File is a module: %s", module) return r.RefreshModule(module) } // For non-module files, refresh all states if err := r.RefreshStates(); err != nil { - logger.DebugCont("Failed to refresh states: %v", err) + logger.Debug("Failed to refresh states: %v", err) return false } @@ -336,7 +336,7 @@ func (r *Runner) RefreshModule(moduleName string) bool { return false } - logger.DebugCont("Refreshing module: %s", moduleName) + logger.Debug("Refreshing module: %s", moduleName) success := true for _, state := range r.states { @@ -347,7 +347,7 @@ func (r *Runner) RefreshModule(moduleName string) bool { // Invalidate module in Lua if err := state.L.DoString(`package.loaded["` + moduleName + `"] = nil`); err != nil { success = false - logger.DebugCont("Failed to invalidate module %s: %v", moduleName, err) + logger.Debug("Failed to invalidate module %s: %v", moduleName, err) } } diff --git a/core/runner/Sandbox.go b/core/runner/sandbox.go similarity index 95% rename from core/runner/Sandbox.go rename to core/runner/sandbox.go index 8174123..3ad883c 100644 --- a/core/runner/Sandbox.go +++ b/core/runner/sandbox.go @@ -55,21 +55,21 @@ func (s *Sandbox) Setup(state *luajit.State, verbose bool) error { } if err := loadSandboxIntoState(state, verbose); err != nil { - logger.ErrorCont("Failed to load sandbox: %v", err) + logger.Error("Failed to load sandbox: %v", err) return err } if err := s.registerCoreFunctions(state); err != nil { - logger.ErrorCont("Failed to register core functions: %v", err) + logger.Error("Failed to register core functions: %v", err) return err } s.mu.RLock() for name, module := range s.modules { - logger.DebugCont("Registering module: %s", name) + logger.Debug("Registering module: %s", name) if err := state.PushValue(module); err != nil { s.mu.RUnlock() - logger.ErrorCont("Failed to register module %s: %v", name, err) + logger.Error("Failed to register module %s: %v", name, err) return err } state.SetGlobal(name) @@ -77,7 +77,7 @@ func (s *Sandbox) Setup(state *luajit.State, verbose bool) error { s.mu.RUnlock() if verbose { - logger.ServerCont("Sandbox setup complete") + logger.Server("Sandbox setup complete") } return nil } diff --git a/core/runner/sandbox.lua b/core/runner/sandbox.lua index 2ab921d..158901f 100644 --- a/core/runner/sandbox.lua +++ b/core/runner/sandbox.lua @@ -300,6 +300,16 @@ local session = { return env.ctx.session.id end + return nil + end, + + get_all = function() + local env = getfenv(2) + + if env.ctx and env.ctx.session then + return env.ctx.session.data + end + return nil end } diff --git a/core/sessions/Manager.go b/core/sessions/manager.go similarity index 100% rename from core/sessions/Manager.go rename to core/sessions/manager.go diff --git a/core/sessions/Session.go b/core/sessions/session.go similarity index 100% rename from core/sessions/Session.go rename to core/sessions/session.go diff --git a/core/utils/color/color.go b/core/utils/color/color.go new file mode 100644 index 0000000..4443b99 --- /dev/null +++ b/core/utils/color/color.go @@ -0,0 +1,46 @@ +package color + +// ANSI color codes +const ( + Reset = "\033[0m" + Red = "\033[31m" + Green = "\033[32m" + Yellow = "\033[33m" + Blue = "\033[34m" + Purple = "\033[35m" + Cyan = "\033[36m" + White = "\033[37m" + Gray = "\033[90m" +) + +// Apply adds color to text if useColors is true +func Apply(text, color string, useColors bool) string { + if useColors { + return color + text + Reset + } + return text +} + +// Strip removes ANSI color codes from a string +func Strip(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 +} diff --git a/core/utils/config/Config.go b/core/utils/config/config.go similarity index 100% rename from core/utils/config/Config.go rename to core/utils/config/config.go diff --git a/core/utils/Debug.go b/core/utils/debug.go similarity index 99% rename from core/utils/Debug.go rename to core/utils/debug.go index 379b810..e973807 100644 --- a/core/utils/Debug.go +++ b/core/utils/debug.go @@ -7,8 +7,8 @@ import ( "strings" "time" - "Moonshark/core/metadata" "Moonshark/core/utils/config" + "Moonshark/core/utils/metadata" ) // ComponentStats holds stats from various system components diff --git a/core/utils/Dirs.go b/core/utils/dirs.go similarity index 100% rename from core/utils/Dirs.go rename to core/utils/dirs.go diff --git a/core/utils/ErrorPages.go b/core/utils/errorPages.go similarity index 100% rename from core/utils/ErrorPages.go rename to core/utils/errorPages.go diff --git a/core/utils/logger/Logger.go b/core/utils/logger/logger.go similarity index 53% rename from core/utils/logger/Logger.go rename to core/utils/logger/logger.go index 9544d7c..701410f 100644 --- a/core/utils/logger/Logger.go +++ b/core/utils/logger/logger.go @@ -38,23 +38,17 @@ var levelProps = map[int]struct { tag string color string }{ - LevelDebug: {"DEBUG", colorCyan}, - LevelInfo: {" INFO", colorBlue}, - LevelWarning: {" WARN", colorYellow}, - LevelError: {"ERROR", colorRed}, - LevelServer: {" SYS", colorGreen}, - LevelFatal: {"FATAL", colorPurple}, + LevelDebug: {"D", colorCyan}, + LevelInfo: {"I", colorBlue}, + LevelWarning: {"W", colorYellow}, + LevelError: {"E", colorRed}, + LevelServer: {"S", colorGreen}, + LevelFatal: {"F", colorPurple}, } // Time format for log messages const timeFormat = "15:04:05" -// Default rate limiting settings -const ( - defaultMaxLogs = 1000 // Max logs per second before rate limiting - defaultRateLimitTime = 10 * time.Second // How long to pause during rate limiting -) - // Single global logger instance with mutex for safe initialization var ( globalLogger *Logger @@ -70,17 +64,6 @@ type Logger struct { showTimestamp bool // Whether to show timestamp mu sync.Mutex // Mutex for thread-safe writing debugMode atomic.Bool // Force debug logging regardless of level - indentCache string // Cached indent string for continuations - indentSize int // Size of the indent for continuations - lastLevel int // Last log level used, for continuations - - // Simple rate limiting - logCount atomic.Int64 // Number of logs in current window - logCountStart atomic.Int64 // Start time of current counting window - rateLimited atomic.Bool // Whether we're currently rate limited - rateLimitUntil atomic.Int64 // Timestamp when rate limiting ends - maxLogsPerSec int64 // Maximum logs per second before limiting - limitDuration time.Duration // How long to pause logging when rate limited } // GetLogger returns the global logger instance, creating it if needed @@ -105,17 +88,8 @@ func newLogger(minLevel int, useColors bool, showTimestamp bool) *Logger { useColors: useColors, timeFormat: timeFormat, showTimestamp: showTimestamp, - maxLogsPerSec: defaultMaxLogs, - limitDuration: defaultRateLimitTime, - lastLevel: -1, // Initialize to invalid level } - // Initialize counters - logger.resetCounters() - - // Calculate the base indent size - logger.updateIndentCache() - return logger } @@ -124,14 +98,6 @@ func New(minLevel int, useColors bool, showTimestamp bool) *Logger { return newLogger(minLevel, useColors, showTimestamp) } -// resetCounters resets the rate limiting counters -func (l *Logger) resetCounters() { - l.logCount.Store(0) - l.logCountStart.Store(time.Now().Unix()) - l.rateLimited.Store(false) - l.rateLimitUntil.Store(0) -} - // SetOutput changes the output destination func (l *Logger) SetOutput(w io.Writer) { l.mu.Lock() @@ -144,47 +110,21 @@ func (l *Logger) TimeFormat() string { return l.timeFormat } -// updateIndentCache recalculates and updates the indent cache -func (l *Logger) updateIndentCache() { - tagWidth := 7 - - if l.showTimestamp { - // Format: "15:04:05 DEBUG " - timeWidth := len(time.Now().Format(l.timeFormat)) - l.indentSize = timeWidth + 1 + tagWidth + 1 - } else { - // Format: "DEBUG " - l.indentSize = tagWidth + 1 - } - - l.indentCache = strings.Repeat(" ", l.indentSize) -} - -// SetTimeFormat changes the time format string and updates the indent cache +// SetTimeFormat changes the time format string func (l *Logger) SetTimeFormat(format string) { l.mu.Lock() defer l.mu.Unlock() - l.timeFormat = format - l.updateIndentCache() } // EnableTimestamp enables timestamp display func (l *Logger) EnableTimestamp() { - l.mu.Lock() - defer l.mu.Unlock() - l.showTimestamp = true - l.updateIndentCache() } // DisableTimestamp disables timestamp display func (l *Logger) DisableTimestamp() { - l.mu.Lock() - defer l.mu.Unlock() - l.showTimestamp = false - l.updateIndentCache() } // SetLevel changes the minimum log level @@ -217,202 +157,16 @@ func (l *Logger) IsDebugEnabled() bool { return l.debugMode.Load() } -// writeMessage writes a formatted log message directly to the writer -func (l *Logger) writeMessage(level int, message string, rawMode bool, continuation bool) { - var logLine string - - if rawMode { - // Raw mode - message is already formatted, just append newline - logLine = message + "\n" - } else if continuation { - // Continuation format - just indent and message - if l.useColors { - // For colored output, use the color of the last level - props := levelProps[l.lastLevel] - logLine = fmt.Sprintf("%s%s%s\n", - l.indentCache, props.color, message+colorReset) - } else { - logLine = fmt.Sprintf("%s%s\n", l.indentCache, message) - } - } else { - // Standard format with level tag and optional timestamp - props := levelProps[level] - - if l.showTimestamp { - now := time.Now().Format(l.timeFormat) - - if l.useColors { - logLine = fmt.Sprintf("%s%s%s %s[%s]%s %s\n", - colorGray, now, colorReset, props.color, props.tag, colorReset, message) - } else { - logLine = fmt.Sprintf("%s [%s] %s\n", - now, props.tag, message) - } - } else { - // No timestamp, just level tag and message - if l.useColors { - logLine = fmt.Sprintf("%s[%s]%s %s\n", - props.color, props.tag, colorReset, message) - } else { - logLine = fmt.Sprintf("[%s] %s\n", - props.tag, message) - } - } - - // Store the level for continuations - l.lastLevel = level +// applyColor applies color to text if colors are enabled +func (l *Logger) applyColor(text, color string) string { + if l.useColors { + return color + text + colorReset } - - // Synchronously write the log message - l.mu.Lock() - _, _ = fmt.Fprint(l.writer, logLine) - - // For fatal errors, ensure we sync immediately - if level == LevelFatal { - if f, ok := l.writer.(*os.File); ok { - _ = f.Sync() - } - } - l.mu.Unlock() + return text } -// checkRateLimit checks if we should rate limit logging -// Returns true if the message should be logged, false if it should be dropped -func (l *Logger) checkRateLimit(level int) bool { - // High priority messages are never rate limited - if level >= LevelWarning { - return true - } - - // Check if we're currently in a rate-limited period - if l.rateLimited.Load() { - now := time.Now().Unix() - limitUntil := l.rateLimitUntil.Load() - - if now >= limitUntil { - // Rate limiting period is over - l.rateLimited.Store(false) - l.resetCounters() - } else { - // Still in rate limiting period, drop the message - return false - } - } - - // If not rate limited, check if we should start rate limiting - count := l.logCount.Add(1) - - // Check if we need to reset the counter for a new second - now := time.Now().Unix() - start := l.logCountStart.Load() - if now > start { - // New second, reset counter - l.logCount.Store(1) // Count this message - l.logCountStart.Store(now) - return true - } - - // Check if we've exceeded our threshold - if count > l.maxLogsPerSec { - // Start rate limiting - l.rateLimited.Store(true) - l.rateLimitUntil.Store(now + int64(l.limitDuration.Seconds())) - - // Log a warning about rate limiting - l.writeMessage(LevelServer, - fmt.Sprintf("Rate limiting logger temporarily due to high demand (%d logs/sec exceeded)", count), - false, false) - - return false - } - - return true -} - -// 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()) { - return - } - - // Check rate limiting - always log high priority messages - if !l.checkRateLimit(level) { - return - } - - // Format message - var message string - if len(args) > 0 { - message = fmt.Sprintf(format, args...) - } else { - message = format - } - - l.writeMessage(level, message, false, false) - - // Exit on fatal errors - if level == LevelFatal { - os.Exit(1) - } -} - -// continuation handles continuation log messages (messages that continue from a previous log) -func (l *Logger) continuation(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()) { - return - } - - // Check rate limiting - if !l.checkRateLimit(level) { - return - } - - // Format message - var message string - if len(args) > 0 { - message = fmt.Sprintf(format, args...) - } else { - message = format - } - - // Use the continuation format - l.writeMessage(level, message, false, true) -} - -// 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 - } - - // Check rate limiting - if !l.checkRateLimit(LevelInfo) { - 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, false) -} - -// Simple helper to remove ANSI color codes -func removeAnsiColors(s string) string { +// stripAnsiColors removes ANSI color codes from a string +func stripAnsiColors(s string) string { result := "" inEscape := false @@ -435,68 +189,129 @@ func removeAnsiColors(s string) string { 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 + + 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) + } + } + } + + // Synchronously write the log message + l.mu.Lock() + _, _ = fmt.Fprint(l.writer, logLine) + + // For fatal errors, ensure we sync immediately + if level == LevelFatal { + 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) { + // 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()) { + return + } + + // Format message + 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 = stripAnsiColors(message) + } + + l.writeMessage(LevelInfo, message, true) +} + // Debug logs a debug message func (l *Logger) Debug(format string, args ...any) { l.log(LevelDebug, format, args...) } -// DebugCont logs a debug message as a continuation of the previous log -func (l *Logger) DebugCont(format string, args ...any) { - l.continuation(LevelDebug, format, args...) -} - // Info logs an informational message func (l *Logger) Info(format string, args ...any) { l.log(LevelInfo, format, args...) } -// InfoCont logs an informational message as a continuation of the previous log -func (l *Logger) InfoCont(format string, args ...any) { - l.continuation(LevelInfo, format, args...) -} - // Warning logs a warning message func (l *Logger) Warning(format string, args ...any) { l.log(LevelWarning, format, args...) } -// WarningCont logs a warning message as a continuation of the previous log -func (l *Logger) WarningCont(format string, args ...any) { - l.continuation(LevelWarning, format, args...) -} - // Error logs an error message func (l *Logger) Error(format string, args ...any) { l.log(LevelError, format, args...) } -// ErrorCont logs an error message as a continuation of the previous log -func (l *Logger) ErrorCont(format string, args ...any) { - l.continuation(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() } -// FatalCont logs a fatal error message as a continuation of the previous log and exits -func (l *Logger) FatalCont(format string, args ...any) { - l.continuation(LevelFatal, format, args...) - os.Exit(1) -} - // Server logs a server message func (l *Logger) Server(format string, args ...any) { l.log(LevelServer, format, args...) } -// ServerCont logs a server message as a continuation of the previous log -func (l *Logger) ServerCont(format string, args ...any) { - l.continuation(LevelServer, format, args...) -} - // Global helper functions that use the global logger // Debug logs a debug message to the global logger @@ -504,61 +319,31 @@ func Debug(format string, args ...any) { GetLogger().Debug(format, args...) } -// DebugCont logs a debug message as a continuation of the previous log to the global logger -func DebugCont(format string, args ...any) { - GetLogger().DebugCont(format, args...) -} - // Info logs an informational message to the global logger func Info(format string, args ...any) { GetLogger().Info(format, args...) } -// InfoCont logs an informational message as a continuation of the previous log to the global logger -func InfoCont(format string, args ...any) { - GetLogger().InfoCont(format, args...) -} - // Warning logs a warning message to the global logger func Warning(format string, args ...any) { GetLogger().Warning(format, args...) } -// WarningCont logs a warning message as a continuation of the previous log to the global logger -func WarningCont(format string, args ...any) { - GetLogger().WarningCont(format, args...) -} - // Error logs an error message to the global logger func Error(format string, args ...any) { GetLogger().Error(format, args...) } -// ErrorCont logs an error message as a continuation of the previous log to the global logger -func ErrorCont(format string, args ...any) { - GetLogger().ErrorCont(format, args...) -} - // Fatal logs a fatal error message to the global logger and exits func Fatal(format string, args ...any) { GetLogger().Fatal(format, args...) } -// FatalCont logs a fatal error message as a continuation of the previous log to the global logger and exits -func FatalCont(format string, args ...any) { - GetLogger().FatalCont(format, args...) -} - // Server logs a server message to the global logger func Server(format string, args ...any) { GetLogger().Server(format, args...) } -// ServerCont logs a server message as a continuation of the previous log to the global logger -func ServerCont(format string, args ...any) { - GetLogger().ServerCont(format, args...) -} - // LogRaw logs a raw message to the global logger func LogRaw(format string, args ...any) { GetLogger().LogRaw(format, args...) @@ -620,9 +405,9 @@ func (l *Logger) LogSpacer() { restSpacer := strings.Repeat("-", 20) // Fixed width for the rest if l.useColors { - spacer = fmt.Sprintf("%s%s%s %s%s%s %s\n", - colorGray, strings.Repeat("-", timeWidth), colorReset, - colorCyan, tagSpacer, colorReset, restSpacer) + 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) @@ -633,8 +418,8 @@ func (l *Logger) LogSpacer() { restSpacer := strings.Repeat("-", 20) // Fixed width for the rest if l.useColors { - spacer = fmt.Sprintf("%s%s%s %s\n", - colorCyan, tagSpacer, colorReset, restSpacer) + tagStr := l.applyColor(tagSpacer, colorCyan) + spacer = fmt.Sprintf("%s %s\n", tagStr, restSpacer) } else { spacer = fmt.Sprintf("%s %s\n", tagSpacer, restSpacer) } diff --git a/core/metadata/Metadata.go b/core/utils/metadata/metadata.go similarity index 100% rename from core/metadata/Metadata.go rename to core/utils/metadata/metadata.go diff --git a/core/watchers/Api.go b/core/watchers/api.go similarity index 100% rename from core/watchers/Api.go rename to core/watchers/api.go diff --git a/core/watchers/Dir.go b/core/watchers/dir.go similarity index 100% rename from core/watchers/Dir.go rename to core/watchers/dir.go diff --git a/core/watchers/Manager.go b/core/watchers/manager.go similarity index 100% rename from core/watchers/Manager.go rename to core/watchers/manager.go