[How-to] Handle client script errors on server-side

Here is my rude but working solution how to handle client script errors on server-side:

local _AddEventHandler = AddEventHandler

local function FormatStackTrace()
	return Citizen.InvokeNative(`FORMAT_STACK_TRACE` & 0xFFFFFFFF, nil, 0, Citizen.ResultAsString())
end

AddEventHandler = function(eventName, callback)
	_AddEventHandler(eventName, function(...)
		local args = { ... }
		xpcall(function() callback(table.unpack(args)) end, function(message)
			local error = {
				message = message,
				stackTrace = FormatStackTrace()
			}
			Citizen.Trace(error.message..'\n')
			Citizen.Trace(error.stackTrace)
			TriggerServerEvent('gotClientScriptError', error)
		end)
	end)
end

Download

  1. Add this script to your resource client_scripts before all other client scripts.
  2. Add server event handler to handle it (print in console, send to Discord channel etc.)
    E.g.:
RegisterNetEvent('gotClientScriptError')
AddEventHandler('gotClientScriptError', function(error)
    Citizen.Trace('Got an error from client '..source..':\n'..error.message..'\n'..error.stackTrace..'\n')
end)
1 Like

Is this needed for a particular reason? As source is exposed in all Server event handlers.

No, ofc you don’t need to do it.
My point was to save people from using global source variable, as I believe not all of them understand that it can be changed during event handler execution.

But yeah, probably I shouldn’t think about it here.
Fixed.

1 Like

Was just curious.

this is not working for client threads / functions that are triggered outside of TriggerEvent.

local oldTrace = Citizen.Trace

local errorWords = {"failure", "error", "not", "failed", "not safe", "invalid", "cannot", ".lua", "server", "client", "attempt", "traceback", "stack", "function"}

function error(...)
    local resource = GetCurrentResourceName()
    TriggerServerEvent("error", resource, args)
end

function Citizen.Trace(...)
    oldTrace(...)
    if type(...) == "string" then
        args = string.lower(...)
        for _, word in ipairs(errorWords) do
            if string.find(args, word) then
                error(...)
                return
            end
        end
    end
end