Start typing to search packages!
logger
By @crecentez
Roblox
MirroredLogger.luau
A strict-typed logging utility for Roblox projects.
Provides categorized logging, per-logger filtering, structured output, timers, buffering, grouping, and an event system.
Features
- Categorized log output with automatic prefixes
- Global logging toggle via a game attribute
- Per-logger enable/disable overrides via
Logger.Settings - Warnings and errors with automatic callsite detection
- Assertions with formatted error messages
- Named timers for performance measurement
- Structured logging — tables are pretty-printed automatically
- Optional context injection (timestamp + callsite on every line)
- Buffered logging — queue output and flush on demand
- Grouped logging — collect related logs and print as a formatted block
- Lazy evaluation — pass a function; it only runs if logging is active
- Event system — subscribe to every dispatched log via
Logger.OnLog - Written in Luau with
--!strict
Installation
- Place
Logger.luauin a shared location (e.g.ReplicatedStorage) - Require it from your scripts:
local Logger = require(game.ReplicatedStorage.Logger)
Basic Usage
local Logger = require(game.ReplicatedStorage.Logger)
local log = Logger.new("MyScript")
log:Log("This always prints")
log:Print("This prints only when logging is enabled")
log:Warn("Something might be wrong")
Logging Control
Global toggle
Controlled by a game attribute set automatically on the first Logger.new call:
game:SetAttribute("Logging", true) -- enable all conditional logging
game:SetAttribute("Logging", false) -- silence Print, Warn, and Debug
Log() and Error() always output regardless of this setting.
Per-logger override
Logger.Settings lets you silence or force-enable individual loggers, overriding the global attribute:
Logger.Settings["Combat"] = false -- silence only the Combat logger
Logger.Settings["Net"] = true -- force Net logger on even if global is off
Logger.Settings["Combat"] = nil -- remove override; follows global again
Module Config
Logger.Config.Buffered = false -- default: print immediately
Logger.Config.ContextEnabled = false -- default: no timestamp/callsite prefix
API
Logger.new(logType: string) -> LoggerType
Creates a new logger instance.
local log = Logger.new("Combat")
Logger:Log(...any)
Unconditional output. Ignores all filters.
Logger:Print(...any)
Conditional output. Respects Logger.Settings and the global attribute.
Logger:Warn(...any)
Conditional warning. Respects Logger.Settings and the global attribute.
Logger:Debug(...any)
Conditional output with a [DEBUG] prefix. Supports lazy evaluation:
log:Debug(function() return "Expensive value: " .. computeSomething() end)
-- The function only runs if this logger is currently enabled.
Logger:Error(...any)
Always throws. Includes an automatic callsite in the error message.
Logger:Assert(condition: boolean?, ...any)
Throws if condition is falsy. Includes an automatic callsite.
log:Assert(player ~= nil, "Player expected")
Logger:StartTimer(name: string)
Starts a named timer.
log:StartTimer("LoadAssets")
Logger:GetTimer(name: string) -> string
Stops the named timer and returns elapsed time as mm:ss.mmmm.
Returns "00:00.0000" if the timer was never started.
log:Log("Loaded in", log:GetTimer("LoadAssets"))
Logger:BeginGroup(name: string)
Starts a named log group. All subsequent logs from this instance are buffered until EndGroup is called. Groups can be nested.
Logger:EndGroup()
Closes the current group and prints all buffered entries as a formatted block.
log:BeginGroup("Startup")
log:Print("Loading config...")
log:Print("Config loaded")
log:EndGroup()
-- Prints the entire block at once when EndGroup is called.
Logger.Flush()
Outputs all buffered logs (when Logger.Config.Buffered = true) and clears the buffer.
Logger.Config.Buffered = true
log:Print("queued message")
Logger.Flush() -- prints now
Logger.OnLog
Fires after every dispatched log, including logs captured into groups.
local disconnect = Logger.OnLog:Connect(function(entry)
-- entry.logType, entry.message, entry.timestamp, entry.context
print("LOG EVENT:", entry.logType, entry.message)
end)
disconnect() -- unsubscribe
Structured Logging
If any argument is a table, it is automatically pretty-printed:
log:Print({ userId = 123, score = 4200, active = true })
-- [MyScript] :: {
-- ["userId"] = 123
-- ["score"] = 4200
-- ["active"] = true
-- }
Context Injection
Enable to prepend a timestamp and callsite to every log line:
Logger.Config.ContextEnabled = true
log:Print("Hello")
-- [14:32:05 | Script 'ServerScript.Main', Line 12] [MyScript] :: Hello
Example
local Logger = require(game.ReplicatedStorage.Logger)
-- Per-logger filtering
Logger.Settings["Net"] = false -- silence Net logger
local log = Logger.new(script.Name)
-- Structured log
log:Print({ event = "PlayerJoined", userId = 1234 })
-- Lazy evaluation (compute() not called if logging is off)
log:Debug(function() return "State: " .. computeExpensiveState() end)
-- Grouped output
log:BeginGroup("Init")
log:Print("Loading modules...")
log:Print("Connecting remotes...")
log:EndGroup()
-- Timers
log:StartTimer("HeavyTask")
for i = 1, 1_000_000 do local _ = i * i end
log:Log("HeavyTask took:", log:GetTimer("HeavyTask"))
-- Event subscription
local disconnect = Logger.OnLog:Connect(function(entry)
if entry.logType == "Combat" then
-- forward to external analytics, etc.
end
end)
License
MIT License
© 2025 Crecentez / Lunoxi Studios
Notes
- Intended for server or shared modules
- Safe to leave in production when logging is disabled via
Logger.Settingsor the global attribute Logger.Config.Bufferedis useful for batching logs during startup then flushing after
Package Details
Install command (Click to copy)
Version
1.1.2
License
MIT
Safe for commercial use
Automated license review — not legal advice.
