199 lines
5.7 KiB
Lua
199 lines
5.7 KiB
Lua
--- This part is unrelated to pcall/xpcall usage. Scroll to "Usage
|
|
--- example" below to see the relevant parts.
|
|
|
|
|
|
-- use efficient deque or implement our own FIFO type of thing
|
|
local formspec_log = futil and futil.Deque and futil.Deque()
|
|
if not formspec_log then
|
|
-- no deque implementation found
|
|
local deq = {}
|
|
deq.size = function(self)
|
|
return #self
|
|
end
|
|
deq.push_front = function(self, v)
|
|
table.insert(self, v)
|
|
end
|
|
deq.pop_back = function(self)
|
|
local v = self[1]
|
|
table.remove(self, 1)
|
|
return v
|
|
end
|
|
deq.iterate = function(self)
|
|
local i = 0
|
|
return function()
|
|
i = i + 1
|
|
local v = self[i]
|
|
if v then
|
|
return v
|
|
end
|
|
end
|
|
end
|
|
deq.__index = deq
|
|
formspec_log = setmetatable({}, deq)
|
|
end
|
|
|
|
local LOG_SIZE = 100
|
|
|
|
-- use deque for logging, pushing oldest log item out
|
|
local function log_add(msg)
|
|
core.log('error', type(msg) == "string" and msg or dump(msg))
|
|
formspec_log:push_front(msg)
|
|
if formspec_log:size() > LOG_SIZE then
|
|
formspec_log:pop_back()
|
|
end
|
|
end
|
|
|
|
|
|
local function formspec_log_clear()
|
|
formspec_log:clear()
|
|
end
|
|
|
|
|
|
-- passes to xpcall to be able to capture the call stack
|
|
local function error_printer(err)
|
|
log_add(err)
|
|
log_add(debug.traceback("----------------------", 2))
|
|
return "error was logged"
|
|
end
|
|
|
|
|
|
-- Takes any function and returns a function that takes and returns
|
|
-- same [multiple] values if no error happens. On error, logs it.
|
|
local function safe_wrap(func)
|
|
local function wrap_f(...)
|
|
local res = table.pack(xpcall(func, error_printer, ...))
|
|
if res[1] then
|
|
-- no error, just return rest of the values
|
|
return select(2, unpack(res))
|
|
else
|
|
-- error was logged by error_printer already
|
|
local msg = dump(res[2])
|
|
if msg then
|
|
log_add(msg)
|
|
end
|
|
return nil
|
|
end
|
|
end
|
|
return wrap_f
|
|
end
|
|
|
|
|
|
local function formspec_log_show(playername)
|
|
local log_items = {}
|
|
for item in formspec_log:iterate() do
|
|
table.insert(log_items, type(item) == "string" and item or dump(item))
|
|
end
|
|
local formspec_template = [[ formspec_version[6] size[16,9] textarea[0.1,0.4;15.8,8.5;log;log;%s] ]]
|
|
local log_text = core.formspec_escape(table.concat(log_items,'\n'))
|
|
core.show_formspec(playername, "yl_commons:formspec_log", string.format(formspec_template, log_text))
|
|
end
|
|
|
|
|
|
-- These commands will allow us to see the errors saved in
|
|
-- the `formspec_log` after they happen.
|
|
core.register_chatcommand(
|
|
"log_show",
|
|
{
|
|
privs = { server = true },
|
|
func = function(playername, _params)
|
|
formspec_log_show(playername)
|
|
return true
|
|
end,
|
|
}
|
|
)
|
|
|
|
core.register_chatcommand(
|
|
"log_clear",
|
|
{
|
|
privs = { server = true },
|
|
func = function(playername, _params)
|
|
formspec_log_clear(playername)
|
|
return true
|
|
end,
|
|
}
|
|
)
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
--- Usage example
|
|
--------------------------------------------------------------------------------
|
|
|
|
local function bugged(playername, params)
|
|
for _=1,tonumber(params) do
|
|
core.chat_send_all("Hello!")
|
|
end
|
|
return true, playername
|
|
end
|
|
|
|
|
|
-- this command will crash if you pass it wrong arguments
|
|
core.register_chatcommand(
|
|
"bugged",
|
|
{
|
|
privs = { },
|
|
func = function(playername, params)
|
|
return bugged(playername, params)
|
|
end,
|
|
}
|
|
)
|
|
|
|
-- This is the simplest safe version using `pcall`, it will return
|
|
-- status and error message on crash.
|
|
|
|
-- This command definition does not require any of the error handling
|
|
-- code above. The limitation is that this will only give us the error
|
|
-- message.
|
|
core.register_chatcommand(
|
|
"bugged_safe",
|
|
{
|
|
privs = { },
|
|
func = function(playername, params)
|
|
local status, ret1, ret2 = pcall(bugged, playername, params)
|
|
-- `status` will be `false` if function call raised an error
|
|
-- `ret1` will contain the error message on error, or first returned value on no errors
|
|
-- `ret2` will contain second returned value on no errors
|
|
-- etc.
|
|
return status, string.format("%s | %s", ret1, ret2)
|
|
end,
|
|
}
|
|
)
|
|
|
|
-- This version allows us to get the error stack trace. We get the
|
|
-- trace by passing error handler function `error_printer` to xpcall.
|
|
-- (our helper funciton, `error_printer` will be called on error
|
|
-- without unwinding the stack, so we can call `debug` functions to
|
|
-- examine the error).
|
|
core.register_chatcommand(
|
|
"bugged_safe2",
|
|
{
|
|
privs = { },
|
|
func = function(playername, params)
|
|
-- NOTE: if this does not work, you may need to make sure you're using luajit or newer lua
|
|
local status, ret1, ret2 = xpcall(bugged, error_printer, playername, params)
|
|
-- `status` will be `false` if function call raised an error
|
|
|
|
-- `ret1` will contain first returned value in case of no
|
|
-- errors, or, in case of error, the value returned by the
|
|
-- error handler function.
|
|
|
|
-- `ret2` will contain second returned value on no errors
|
|
-- etc.
|
|
return status, string.format("%s | %s", ret1, ret2)
|
|
end,
|
|
}
|
|
)
|
|
|
|
-- This is equivalent to `xpcall` safe2 version, but uses generic
|
|
-- wrapper function we defined above.
|
|
core.register_chatcommand(
|
|
"bugged_safe3",
|
|
{
|
|
privs = { },
|
|
func = function(playername, params)
|
|
local safe_bugged = safe_wrap(bugged)
|
|
return safe_bugged(playername, params)
|
|
end,
|
|
}
|
|
)
|