--- 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, } )