The Debug API provides Lua scripts with debugging capabilities, allowing them to log messages, check debug mode status, and trace execution context. This API is particularly useful for troubleshooting and development.
The API is accessed via the global DebugApi table in Lua scripts.
The debug mode is controlled by the debug-mode setting in the plugin's config.yml file. When debug mode is enabled, debug logs are shown in the console. When disabled, only warning and error logs are shown.
Separately from DebugApi, Lua's own debug table is present in the Lua state whenever the execution watchdog is enabled, which is the default. The watchdog is implemented as the interpreter's debug hook, and installing it brings the standard library along with it. Setting script-timeout-ms: 0 disables the watchdog and debug is then absent, so a script that uses it should guard with if debug then ... end.
One caveat comes with that: the watchdog keeps the interpreter's per-instruction bookkeeping switched off while scripts are running normally, and only turns it on at the moment a script overruns its budget. A count or line hook installed by a script with debug.sethook therefore does not fire during normal execution. debug.traceback and the rest of the library work as usual.
Logs a debug message to the console. This message is only shown when debug mode is enabled.
Syntax:
DebugApi:log(message)Parameters:
message(string): The message to log
Example:
DebugApi:log("Player joined the game")
DebugApi:log("Processing item: " .. itemName)Output (when debug mode is enabled):
[INFO] [DEBUG][script.lua] Player joined the game
[INFO] [DEBUG][script.lua] Processing item: diamond
Logs a warning message to the console. This message is always shown regardless of debug mode.
Syntax:
DebugApi:warn(message)Parameters:
message(string): The warning message to log
Example:
DebugApi:warn("Player inventory is full")
DebugApi:warn("Invalid configuration value")Output:
[WARNING] [DEBUG][script.lua] Player inventory is full
[WARNING] [DEBUG][script.lua] Invalid configuration value
Logs an error message to the console. This message is always shown regardless of debug mode.
Syntax:
DebugApi:error(message)Parameters:
message(string): The error message to log
Example:
DebugApi:error("Failed to process player data")
DebugApi:error("Critical error in event handler")Output:
[SEVERE] [DEBUG][script.lua] Failed to process player data
[SEVERE] [DEBUG][script.lua] Critical error in event handler
Checks if debug mode is currently enabled.
Syntax:
local enabled = DebugApi:isEnabled()Returns:
enabled(boolean): true if debug mode is enabled, false otherwise
Example:
if DebugApi:isEnabled() then
DebugApi:log("Debug information: " .. data)
endLogs the current script execution context to the console. This is useful for tracking which script is currently being executed. This message is only shown when debug mode is enabled.
Syntax:
DebugApi:trace()Example:
DebugApi:trace()Output (when debug mode is enabled):
[INFO] [DEBUG][TRACE] Current script: myscript.lua
Output (when no script context):
[INFO] [DEBUG][TRACE] No current script context
Starts a named timer for performance measurement. This is useful for measuring execution time of code blocks. This only works when debug mode is enabled.
Syntax:
DebugApi:startTimer(name)Parameters:
name(string): The name of the timer
Example:
DebugApi:startTimer("database_query")
-- perform database query
DebugApi:stopTimer("database_query")Output (when debug mode is enabled):
[INFO] [DEBUG][TIMER][script.lua] Started timer: database_query
[INFO] [DEBUG][TIMER][script.lua] Stopped timer 'database_query': 42ms
Stops a named timer and logs the elapsed time in milliseconds. Returns the elapsed time as a number. This only works when debug mode is enabled.
Syntax:
local elapsedMs = DebugApi:stopTimer(name)Parameters:
name(string): The name of the timer to stop
Returns:
elapsedMs(number): The elapsed time in milliseconds, or nil if timer not found
Example:
DebugApi:startTimer("processing")
-- do some work
local time = DebugApi:stopTimer("processing")
print("Operation took " .. time .. "ms")Output (when debug mode is enabled):
[INFO] [DEBUG][TIMER][script.lua] Stopped timer 'processing': 123ms
Output (if timer not found):
[WARNING] [DEBUG][TIMER] Timer not found: processing
Note: Always call stopTimer in a finally block or use clearTimers to prevent memory leaks if the timer might not be stopped due to errors.
Clears all active timers. This is useful for cleaning up timers that were started but not stopped due to errors or early returns. This only works when debug mode is enabled.
Syntax:
DebugApi:clearTimers()Example:
function riskyOperation()
DebugApi:startTimer("operation")
local success, err = pcall(function()
-- risky code that might error
end)
if not success then
DebugApi:error("Operation failed: " .. err)
DebugApi:clearTimers() -- Clean up timers on error
return
end
DebugApi:stopTimer("operation")
endOutput (when debug mode is enabled):
[INFO] [DEBUG][TIMER][script.lua] Cleared 3 timer(s)
Note: This method removes all timers from the timer map, preventing memory leaks from unreleased timers.
Pretty-prints a Lua table to the console for debugging. This is useful for inspecting table contents and structure. This only works when debug mode is enabled.
Syntax:
DebugApi:printTable(table)Parameters:
table(table): The Lua table to print
Example:
local playerData = {
name = "Steve",
health = 20,
inventory = {"diamond", "iron"}
}
DebugApi:printTable(playerData)Output (when debug mode is enabled):
[INFO] [DEBUG][TABLE][script.lua] {name = "Steve", health = 20, inventory = {1 = "diamond", 2 = "iron"}}
Output (if table is truncated):
[INFO] [DEBUG][TABLE][script.lua] {key1 = "value1", key2 = "value2", ... (truncated)}
Notes:
- Nested tables are recursively printed up to a maximum depth
- Strings are shown with quotes
- Numbers are shown without quotes
- nil values are shown as
nil - Functions are shown as
[function] - Circular references are detected and rendered as
[table: circular reference] - A maximum depth of 10 is enforced; deeper tables render as
[table: max depth reached] - A shared rendering budget of 100 entries is enforced across the entire recursive call tree to prevent excessive log output
- The budget is not reset for nested tables or sibling branches
- When the budget is exhausted, the output appends
, ... (truncated)
-- Check if debug mode is enabled before expensive operations
if DebugApi:isEnabled() then
DebugApi:log("Starting complex calculation")
local result = complexCalculation()
DebugApi:log("Result: " .. result)
end-- Log debug information only when needed
function processItem(item)
if item == nil then
DebugApi:error("Item is nil")
return
end
if DebugApi:isEnabled() then
DebugApi:log("Processing item type: " .. item:getType())
end
-- Process the item
endfunction safeOperation()
local success, err = pcall(function()
-- risky operation
end)
if not success then
DebugApi:error("Operation failed: " .. err)
end
endpcall catches script errors, but deliberately not the abort raised when a script exceeds script-timeout-ms: a script looping on pcall would otherwise swallow the abort and keep the server hung. Cleanup placed in the failure branch above does not run when a script is aborted for running too long, so do not rely on pcall to release state that must be released no matter what.
function onPlayerJoin(event)
DebugApi:trace()
DebugApi:log("Player joined: " .. event:getPlayer():getName())
-- Handle player join
endfunction processPlayerData(player)
DebugApi:startTimer("data_processing")
-- Expensive operation
local data = fetchPlayerData(player)
processData(data)
local elapsed = DebugApi:stopTimer("data_processing")
if elapsed and elapsed > 100 then
DebugApi:warn("Slow data processing: " .. elapsed .. "ms")
end
endfunction safeTimedOperation()
DebugApi:startTimer("operation")
local success, err = pcall(function()
-- operation that might fail
end)
if not success then
DebugApi:error("Operation failed: " .. err)
DebugApi:clearTimers() -- Clean up on error
return
end
DebugApi:stopTimer("operation")
endfunction debugPlayerInventory(player)
local inv = player:getInventory()
local inventory = {}
for i = 1, inv:getSize() do
local item = inv:getItem(i - 1)
if item ~= nil then
inventory[i] = item:getType()
end
end
DebugApi:log("Player inventory contents:")
DebugApi:printTable(inventory)
endfunction handleEvent(event)
if not DebugApi:isEnabled() then
-- Fast path when debug mode is off
return processEvent(event)
end
DebugApi:trace()
DebugApi:startTimer("event_handling")
local eventData = {
type = event:getEventName(),
player = event:getPlayer():getName()
}
DebugApi:printTable(eventData)
local result = processEvent(event)
DebugApi:stopTimer("event_handling")
return result
end- All log messages include the script name in the format
[DEBUG][script_name]when called from within a script context - The
log,trace,startTimer,stopTimer,clearTimers, andprintTablemethods only output when debug mode is enabled - The
warnanderrormethods always output regardless of debug mode - Debug mode can be toggled at runtime using the
/minecraftluascripting reloadconfigcommand - Timers are stored per DebugApi instance in a ConcurrentHashMap, which is thread-safe
- Always call
stopTimeror useclearTimersto prevent memory leaks from unreleased timers printTablerecursively prints nested tables up to a maximum depth and detects circular referencesprintTableenforces a shared rendering budget of 100 entries across the entire recursive call tree to prevent excessive log output