{"id":"crecentez/logger","name":"logger","scope":"crecentez","platform":"roblox","description":"A small, strict-typed logging utility for Roblox projects. Provides categorized logging, conditional output, assertions, and basic performance timing.","version":"1.1.2","latest":"1.1.2","versions":["1.1.1","1.1.2"],"license":"MIT","licenseRating":"safe","licenseCaveats":[],"licenseVerified":true,"dependencies":{},"integrity":"9f4e72f4eaae81745f24a336c463b4a63bcbaa18a46b7383c7dcd9c296d2b2a5","likes":0,"downloads":0,"install":"forest install crecentez/logger","url":"https://forest.dev/p/roblox/crecentez/logger","files":"https://api.forest.dev/ai/package/roblox/crecentez/logger/files","readme":"# Logger.luau\n\nA strict-typed logging utility for Roblox projects.  \nProvides categorized logging, per-logger filtering, structured output, timers, buffering, grouping, and an event system.\n\n---\n\n## Features\n\n- Categorized log output with automatic prefixes\n- Global logging toggle via a game attribute\n- Per-logger enable/disable overrides via `Logger.Settings`\n- Warnings and errors with automatic callsite detection\n- Assertions with formatted error messages\n- Named timers for performance measurement\n- Structured logging — tables are pretty-printed automatically\n- Optional context injection (timestamp + callsite on every line)\n- Buffered logging — queue output and flush on demand\n- Grouped logging — collect related logs and print as a formatted block\n- Lazy evaluation — pass a function; it only runs if logging is active\n- Event system — subscribe to every dispatched log via `Logger.OnLog`\n- Written in Luau with `--!strict`\n\n---\n\n## Installation\n\n1. Place `Logger.luau` in a shared location (e.g. `ReplicatedStorage`)\n2. Require it from your scripts:\n\n```lua\nlocal Logger = require(game.ReplicatedStorage.Logger)\n```\n\n---\n\n## Basic Usage\n\n```lua\nlocal Logger = require(game.ReplicatedStorage.Logger)\nlocal log = Logger.new(\"MyScript\")\n\nlog:Log(\"This always prints\")\nlog:Print(\"This prints only when logging is enabled\")\nlog:Warn(\"Something might be wrong\")\n```\n\n---\n\n## Logging Control\n\n### Global toggle\nControlled by a game attribute set automatically on the first `Logger.new` call:\n```lua\ngame:SetAttribute(\"Logging\", true)   -- enable all conditional logging\ngame:SetAttribute(\"Logging\", false)  -- silence Print, Warn, and Debug\n```\n`Log()` and `Error()` always output regardless of this setting.\n\n### Per-logger override\n`Logger.Settings` lets you silence or force-enable individual loggers, overriding the global attribute:\n```lua\nLogger.Settings[\"Combat\"] = false  -- silence only the Combat logger\nLogger.Settings[\"Net\"]    = true   -- force Net logger on even if global is off\nLogger.Settings[\"Combat\"] = nil    -- remove override; follows global again\n```\n\n---\n\n## Module Config\n\n```lua\nLogger.Config.Buffered       = false  -- default: print immediately\nLogger.Config.ContextEnabled = false  -- default: no timestamp/callsite prefix\n```\n\n---\n\n## API\n\n### `Logger.new(logType: string) -> LoggerType`\nCreates a new logger instance.\n```lua\nlocal log = Logger.new(\"Combat\")\n```\n\n---\n\n### `Logger:Log(...any)`\nUnconditional output. Ignores all filters.\n\n---\n\n### `Logger:Print(...any)`\nConditional output. Respects `Logger.Settings` and the global attribute.\n\n---\n\n### `Logger:Warn(...any)`\nConditional warning. Respects `Logger.Settings` and the global attribute.\n\n---\n\n### `Logger:Debug(...any)`\nConditional output with a `[DEBUG]` prefix. Supports lazy evaluation:\n```lua\nlog:Debug(function() return \"Expensive value: \" .. computeSomething() end)\n-- The function only runs if this logger is currently enabled.\n```\n\n---\n\n### `Logger:Error(...any)`\nAlways throws. Includes an automatic callsite in the error message.\n\n---\n\n### `Logger:Assert(condition: boolean?, ...any)`\nThrows if `condition` is falsy. Includes an automatic callsite.\n```lua\nlog:Assert(player ~= nil, \"Player expected\")\n```\n\n---\n\n### `Logger:StartTimer(name: string)`\nStarts a named timer.\n```lua\nlog:StartTimer(\"LoadAssets\")\n```\n\n### `Logger:GetTimer(name: string) -> string`\nStops the named timer and returns elapsed time as `mm:ss.mmmm`.\nReturns `\"00:00.0000\"` if the timer was never started.\n```lua\nlog:Log(\"Loaded in\", log:GetTimer(\"LoadAssets\"))\n```\n\n---\n\n### `Logger:BeginGroup(name: string)`\nStarts a named log group. All subsequent logs from this instance are buffered until `EndGroup` is called. Groups can be nested.\n\n### `Logger:EndGroup()`\nCloses the current group and prints all buffered entries as a formatted block.\n```lua\nlog:BeginGroup(\"Startup\")\nlog:Print(\"Loading config...\")\nlog:Print(\"Config loaded\")\nlog:EndGroup()\n-- Prints the entire block at once when EndGroup is called.\n```\n\n---\n\n### `Logger.Flush()`\nOutputs all buffered logs (when `Logger.Config.Buffered = true`) and clears the buffer.\n```lua\nLogger.Config.Buffered = true\nlog:Print(\"queued message\")\nLogger.Flush()  -- prints now\n```\n\n---\n\n### `Logger.OnLog`\nFires after every dispatched log, including logs captured into groups.\n```lua\nlocal disconnect = Logger.OnLog:Connect(function(entry)\n    -- entry.logType, entry.message, entry.timestamp, entry.context\n    print(\"LOG EVENT:\", entry.logType, entry.message)\nend)\n\ndisconnect()  -- unsubscribe\n```\n\n---\n\n## Structured Logging\n\nIf any argument is a table, it is automatically pretty-printed:\n```lua\nlog:Print({ userId = 123, score = 4200, active = true })\n-- [MyScript] ::  {\n--   [\"userId\"] = 123\n--   [\"score\"] = 4200\n--   [\"active\"] = true\n-- }\n```\n\n---\n\n## Context Injection\n\nEnable to prepend a timestamp and callsite to every log line:\n```lua\nLogger.Config.ContextEnabled = true\nlog:Print(\"Hello\")\n-- [14:32:05 | Script 'ServerScript.Main', Line 12] [MyScript] ::  Hello\n```\n\n---\n\n## Example\n\n```lua\nlocal Logger = require(game.ReplicatedStorage.Logger)\n\n-- Per-logger filtering\nLogger.Settings[\"Net\"] = false  -- silence Net logger\n\nlocal log = Logger.new(script.Name)\n\n-- Structured log\nlog:Print({ event = \"PlayerJoined\", userId = 1234 })\n\n-- Lazy evaluation (compute() not called if logging is off)\nlog:Debug(function() return \"State: \" .. computeExpensiveState() end)\n\n-- Grouped output\nlog:BeginGroup(\"Init\")\nlog:Print(\"Loading modules...\")\nlog:Print(\"Connecting remotes...\")\nlog:EndGroup()\n\n-- Timers\nlog:StartTimer(\"HeavyTask\")\nfor i = 1, 1_000_000 do local _ = i * i end\nlog:Log(\"HeavyTask took:\", log:GetTimer(\"HeavyTask\"))\n\n-- Event subscription\nlocal disconnect = Logger.OnLog:Connect(function(entry)\n    if entry.logType == \"Combat\" then\n        -- forward to external analytics, etc.\n    end\nend)\n```\n\n---\n\n## License\nMIT License  \n© 2025 Crecentez / Lunoxi Studios\n\n## Notes\n- Intended for server or shared modules\n- Safe to leave in production when logging is disabled via `Logger.Settings` or the global attribute\n- `Logger.Config.Buffered` is useful for batching logs during startup then flushing after","readmeTruncated":false}