A real-time debug console for LÖVE2D games that runs in your web browser.
- Multiple Consoles - Create separate consoles for different systems (gameplay, network, audio, etc.)
- Real-time Logging - See logs appear instantly in your browser
- Search & Filter - Search logs and filter by level (info, success, warning, error, debug)
- Custom Commands - Register commands that can be executed from the browser
- Watchable Variables - Monitor values in real-time without logging every frame
- Lightweight - Pure Lua implementation with no external dependencies
- Download the latest release
- Copy
conduit.luainto your LÖVE project - Require it in your
main.lua:
Conduit = require("conduit")Conduit = require("conduit")
Conduit:init() -- Initialize the Conduit framework
-- Setup System Console
Conduit:console("system")
Conduit.system:success("[Conduit] System console initialized.")
Conduit.system:group("Stats", 1)
Conduit.system:watch("FPS", function() return love.timer.getFPS() end, "Stats", 1)
Conduit.system:watch("Memory Usage", function()
local mem = collectgarbage("count")
if mem < 1024 then
return string.format("%.0f KB", mem)
else
return string.format("%.0f MB", mem / 1024)
end
end, "Stats", 2)
-- Setup Gameplay Console
Conduit:console("gameplay")
Conduit.gameplay:success("[Conduit] Gameplay console initialized.")
Conduit.gameplay:group("Player", 1)
Conduit.gameplay:watch("Health", function() return player.health end, "Player", 1)
function love.load()
player = { health = 100 }
end
function love.update(dt)
Conduit:update() -- Update the Conduit framework
end
function love.quit()
Conduit:shutdown() -- Shutdown the Conduit framework
endNavigate to http://localhost:8080 to see the index page with a list of all your consoles.
Conduit:init({
port = 8080, -- HTTP server port
timestamps = true, -- Show timestamps on logs
max_logs = 1000, -- Maximum logs per console
max_watchables = 100, -- Maximum watchables per console
refresh_interval = 200 -- Milliseconds between browser updates
})console:log("Normal message") -- ▸ White
console:success("Success message") -- ✓ Green
console:warn("Warning message") -- ⚠ Yellow
console:error("Error message") -- ✖ Red
console:debug("Debug message") -- ○ Graylocal player = { x = 100, y = 200, health = 75 }
console:log(tostring(player)) -- Convert to string firstconsole:log("Line 1\nLine 2\nLine 3")console:clear() -- Clear all logs from this consoleCreate separate consoles for different systems:
local game_console = Conduit:console("gameplay")
local network_console = Conduit:console("network")
local audio_console = Conduit:console("audio")
game_console:log("Player spawned")
network_console:log("Connected to server")
audio_console:log("Music volume: 80%")Each console is independent with its own logs, commands, and watchables.
When you create a console, Conduit automatically creates an alias for easy access:
-- Create console
Conduit:console("gameplay")
-- Access via alias (no variable needed!)
Conduit.gameplay:log("Using alias")
Conduit.gameplay:success("Much cleaner!")
-- Still works with variables if you prefer
gameplay = Conduit:console("gameplay")
gameplay:log("Using variable")This is especially useful for global access across multiple files:
-- main.lua
Conduit = require("conduit")
Conduit:init()
Conduit:console("gameplay")
-- player.lua
function Player:takeDamage()
Conduit.gameplay:warn("Player took damage!") -- No need to pass console around
end
-- enemy.lua
function Enemy:spawn()
Conduit.gameplay:log("Enemy spawned") -- Same here!
endEach console is independent with its own logs, commands, and watchables.
Register commands that can be executed from the browser console:
-- Register a command
console:register_command("spawn", function(console, args)
console:success("Enemy spawned at (100, 200)")
-- Your spawn logic here
end, "Spawn an enemy")
console:register_command("reset", function(console, args)
console:warn("Resetting game state...")
-- Your reset logic here
console:success("Game reset complete!")
end, "Reset the game")Every console has these commands by default:
help- Show all available commandsclear- Clear the consolestats- Show console statistics
Register a command that appears on ALL consoles:
Conduit:register_global_command("fps", function(console, args)
console:log("FPS: " .. love.timer.getFPS())
end, "Show current FPS")Monitor values in real-time without spamming logs:
local player = { health = 100, x = 50, y = 30 }
local enemies = {}
-- Watch simple values
console:watch("FPS", function()
return love.timer.getFPS()
end)
console:watch("Player Health", function()
return player.health
end)
console:watch("Enemy Count", function()
return #enemies
end)
-- Watch formatted values
console:watch("Position", function()
return string. format("(%.1f, %.1f)", player.x, player.y)
end)
console:watch("Memory", function()
local kb = collectgarbage("count")
return string.format("%. 2f MB", kb / 1024)
end)Organize watchables into groups with custom ordering:
-- Define groups with display order
console:group("System", 1) -- Shows first
console:group("Player", 2) -- Shows second
console: group("Gameplay", 3) -- Shows third
-- Add watchables to groups with item order
console:watch("FPS", function() return love.timer.getFPS() end, "System", 1)
console:watch("Memory", function() return "12 MB" end, "System", 2)
console:watch("Health", function() return player.health end, "Player", 1)
console:watch("Position", function() return "(50, 30)" end, "Player", 2)
console:watch("Wave", function() return current_wave end, "Gameplay", 1)
console:watch("Enemies", function() return #enemies end, "Gameplay", 2)console:unwatch("FPS") -- Remove a single watchable
console:unwatch_group("System") -- Remove all watchables in a groupIn the browser console:
- Search box - Filter logs by text content
- Dropdown - Filter by log level (all, info, success, warning, error, debug)
- Clear button - Clear all logs
| Method | Description |
|---|---|
Conduit:init(options) |
Initialize Conduit with config options |
Conduit:update() |
Update server (call every frame) |
Conduit:shutdown() |
Clean shutdown |
Conduit:console(name) |
Create or get a console |
Conduit:register_global_command(name, callback, desc) |
Register command on all consoles |
After creating a console with Conduit:console("name"), you can access it two ways:
-- Method 1: Store in variable
local console = Conduit:console("gameplay")
console:log("Hello")
-- Method 2: Use alias (recommended for global access)
Conduit:console("gameplay")
Conduit.gameplay:log("Hello")| Method | Description |
|---|---|
console:log(message) |
Log info message (white) |
console:success(message) |
Log success message (green) |
console:warn(message) |
Log warning message (yellow) |
console:error(message) |
Log error message (red) |
console:debug(message) |
Log debug message (gray) |
console:clear() |
Clear all logs |
console:get_logs() |
Get array of log objects |
| Method | Description |
|---|---|
console:register_command(name, callback, desc) |
Register a command |
console:execute_command(name, args) |
Execute a command (internal) |
| Method | Description |
|---|---|
console:watch(name, getter, group, order) |
Add a watchable variable |
console:unwatch(name) |
Remove a watchable |
console:group(name, order) |
Define a watchable group order |
console:unwatch_group(name) |
Remove all watchables in group |
console:get_watchables() |
Get watchables data (internal) |
| Method | Description |
|---|---|
console:get_stats() |
Get console statistics |
If port 8080 is occupied, change it:
Conduit:init({ port = 8081 })- Make sure your LÖVE game is running
- Check the console output for the correct URL
- Try accessing from
http://127.0.0.1:8080instead oflocalhost
- Ensure you're calling
Conduit:update()every frame - Check that your getter functions don't error (errors show as "Error: ..." in the value)
- Try lowering
refresh_intervalfor faster updates
- Increase
refresh_interval(e.g., 500ms instead of 200ms) - Reduce
max_logsto limit memory usage - Limit the number of watchables
-
Adjust refresh interval - Higher = less CPU, lower = more responsive
Conduit:init({ refresh_interval = 500 }) -- Update every 500ms
-
Limit logs - Keep only recent logs
Conduit:init({ max_logs = 500 })
-
Optimize watchable getters - Keep getter functions fast
-- Good console:watch("FPS", function() return love.timer.getFPS() end) -- Bad - too slow console:watch("Total Enemies", function() local count = 0 for _, entity in ipairs(all_entities) do if entity.type == "enemy" then count = count + 1 end end return count end)
-
Use multiple consoles - Separate concerns for easier filtering
-
Use aliases for cleaner code -
Conduit.gameplay:log()is more readable than passing console objects around
MIT License - See LICENSE file for details
Contributions are welcome! Please feel free to submit a Pull Request.