FiveM-Debugging: Fehler in Lua und JS schnell finden
Debugge FiveM-Skripte schneller: Serverprotokolle, Client-Ausgaben, txAdmin-Werkzeuge und bewährte Abläufe helfen dir, Fehler früh zu beheben.
Agency Scripts
Gründer und leitender Entwickler bei Agency Scripts
Die Kunst, FiveM-Skripte zu debuggen
Das Debugging von FiveM-Skripten unterscheidet sich grundlegend vom Debugging herkömmlicher Anwendungen. Du hast es mit einer Client-Server-Architektur zu tun, bei der Code in zwei separaten Laufzeitumgebungen ausgeführt wird, Events über das Netzwerk übertragen werden und sich der Spielzustand in jedem Frame ändert. Wenn etwas schiefgeht, kann der Fehler auf dem Server entstehen, sich aber auf dem Client zeigen, oder umgekehrt. Mit einer systematischen Herangehensweise an das Debugging sparst du unzählige Stunden und beschleunigst deinen Entwicklungsablauf erheblich. Dieser Leitfaden behandelt die wichtigsten Tools, Techniken und Vorgehensweisen, die professionelle FiveM-Entwickler täglich nutzen.
Print-Anweisungen effektiv nutzen
Das grundlegendste Debugging-Tool in FiveM ist die print() -Funktion. Sie effektiv zu nutzen, erfordert aber mehr, als nur Variablen auszugeben. Strukturiere deine Debug-Ausgaben mit Präfixen, die das Skript, die Seite (Client oder Server) und die Funktion kennzeichnen, in der die Ausgabe erfolgt. Verwende Farbcodes, damit wichtige Meldungen in der Konsole hervorstechen. Erstelle ein Debug-Hilfsprogramm, das du ein- und ausschalten kannst, ohne die Debug-Zeilen aus deinem Code entfernen zu müssen.
-- shared/debug.lua
local DEBUG_ENABLED = GetConvar('myresource_debug', 'false') == 'true'
local RESOURCE_NAME = GetCurrentResourceName()
function DebugLog(module, message...)
if not DEBUG_ENABLED then return end
local side = IsDuplicityVersion() and 'SERVER' else 'CLIENT'
local formatted = type(message) == 'string' and message:format(...) or tostring(message)
local timestamp = os.date('%H:%M:%S')
print(('[^3%s^0][^5%s^0][^2%s^0] %s'):format(
timestamp, RESOURCE_NAME, side .. ':' .. module, formatted
))
end
function DebugTable(module, tbl, depth)
if not DEBUG_ENABLED then return end
depth = depth or 0
local indent = string.rep(' ', depth)
if type(tbl) ~= 'table' then
DebugLog(module, '%s%s', indent, tostring(tbl))
return
end
for k, v in pairs(tbl) do
if type(v) == 'table' then
DebugLog(module, '%s%s = {', indent, tostring(k))
DebugTable(module, v, depth + 1)
DebugLog(module, '%s}', indent)
else
DebugLog(module, '%s%s = %s (%s)', indent, tostring(k), tostring(v), type(v))
end
end
end
Die Debug-Utility verwenden
Mit dieser Utility kannst du deine Scripts um Debug-Logs ergänzen, die in der Produktionsumgebung vollständig stumm bleiben. Aktiviere sie, indem du Folgendes set myresource_debug true zu deiner Serverkonfiguration hinzufügst, wenn du Fehler untersuchen musst. Die strukturierte Ausgabe macht es einfach, die Konsolen-Logs mit grep zu durchsuchen und genau das zu finden, wonach du suchst.
-- server/jobs.lua
RegisterNetEvent('myresource:startJob', function(jobName)
local src = source
DebugLog('jobs', 'Player %d attempting to start job: %s', src, jobName)
local playerData = GetPlayerData(src)
DebugTable('jobs', playerData)
if not playerData then
DebugLog('jobs', 'ERROR: No player data found for source %d', src)
return
end
if playerData.job == jobName then
DebugLog('jobs', 'Player %d already has job %s, skipping', src, jobName)
return
end
DebugLog('jobs', 'Job %s assigned to player %d successfully', jobName, src)
end)
Häufige FiveM-Fehler und ihre Lösungen
SCRIPT ERROR: attempt to index a nil value
Dies ist der häufigste Lua-Fehler bei der FiveM-Entwicklung. Er bedeutet, dass du versuchst, auf eine Eigenschaft oder Methode einer Variable zuzugreifen, die nilist. Die häufigsten Ursachen sind der Zugriff auf Spielerdaten, bevor sie geladen wurden, der Aufruf einer Framework-Funktion, die in deiner Version nicht existiert, oder das Auslesen eines Konfigurationswerts mit einem Tippfehler im Schlüssel. Prüfe immer auf nil, bevor du auf verschachtelte Eigenschaften zugreifst.
-- BAD: Will crash if GetPlayerData returns nil
local name = GetPlayerData(src).charinfo.firstname
-- GOOD: Defensive nil checks
local playerData = GetPlayerData(src)
if not playerData then
print('[ERROR] Player data is nil for source: ' .. src)
return
end
local charinfo = playerData.charinfo
if not charinfo then
print('[ERROR] charinfo missing for source: ' .. src)
return
end
local name = charinfo.firstname or 'Unknown'
SCRIPT ERROR: attempt to call a nil value
Dieser Fehler tritt auf, wenn du versuchst, eine Funktion aufzurufen, die nicht existiert. In FiveM passiert das häufig, wenn du einen Export aus einer Resource aufrufst, die noch nicht gestartet wurde, eine Framework-Funktion verwendest, die bei einem Update umbenannt wurde, oder vergisst, eine gemeinsame Datei in deinem Manifest zu laden. Prüfe deine fxmanifest.lua und stelle sicher, dass alle erforderlichen Dateien aufgeführt und in der richtigen Reihenfolge sind.
-- Safely calling an export that might not be available
local function SafeExport(resource, exportName...)
local success, result = pcall(function(...)
return exports[resource][exportName](...)
end...)
if not success then
print(('[^1ERROR^0] Failed to call export %s:%s - %s'):format(
resource, exportName, tostring(result)
))
return nil
end
return result
end
-- Usage
local inventory = SafeExport('ox_inventory', 'GetInventory', src)
Event wurde nicht registriert
Wenn du ein Event auslöst, für das kein Script einen Handler registriert hat, verwirft FiveM es stillschweigend, ohne einen Fehler in der Konsole auszugeben. Das kann die Fehlersuche frustrierend machen, weil alles richtig aussieht, aber nichts passiert. Verwende eine Hilfsfunktion, um vor dem Auslösen zu prüfen, ob Events registriert sind, und füge während der Entwicklung Logs rund um deine Event-Auslöser hinzu.
-- server/debug_events.lua
-- Wrap TriggerClientEvent to log when events fire
local originalTrigger = TriggerClientEvent
if GetConvar('myresource_debug', 'false') == 'true' then
TriggerClientEvent = function(eventName, target...)
print(('[^3EVENT^0] TriggerClientEvent: %s -> target: %s'):format(
eventName, tostring(target)
))
return originalTrigger(eventName, target...)
end
end
NUI-Fehlersuche mit DevTools
Bei Scripts mit NUI-Oberflächen sind die integrierten Chromium DevTools unverzichtbar. Öffne sie über die F8-Konsole, indem du nui_devtools eingibst, um den vollständigen Chrome-Inspektor aufzurufen. Damit kannst du im Tab „Elements“ die DOM-Struktur untersuchen, in der Konsole JavaScript-Fehler anzeigen, im Tab „Network“ das Laden von Ressourcen prüfen und im Tab „Sources“ Breakpoints setzen. Bei Problemen mit der NUI-Kommunikation solltest du beide Seiten der Nachrichtenverbindung protokollieren.
// nui/js/debug.js
// Log all incoming NUI messages
window.addEventListener('message', (event) => {
if (event.data && event.data.action) {
console.log(
'%c[NUI Received]%c ' + event.data.action,
'background: #2dd4bf; color: #000; padding: 2px 6px; border-radius: 3px;',
'color: #94a3b8;',
event.data
);
}
});
// Wrap fetch to log NUI callbacks
const originalFetch = window.fetch;
window.fetch = function(url, options) {
const body = options?.body ? JSON.parse(options.body) : null;
console.log(
'%c[NUI Callback]%c ' + url,
'background: #8b5cf6; color: #fff; padding: 2px 6px; border-radius: 3px;',
'color: #94a3b8;',
body
);
return originalFetch.apply(this, arguments);
};
Profiling mit Resmon und Timing
Über die einfache resmon Überwachung hinaus kannst du präzise Zeitmessungen in deine Scripts einbauen. Miss, wie lange bestimmte Vorgänge dauern, und protokolliere Warnungen, wenn sie akzeptable Grenzwerte überschreiten. Das ist besonders wichtig bei Datenbankabfragen, komplexen Berechnungen und Schleifen, die viele Entitäten verarbeiten.
-- shared/profiler.lua
local Profiler = {}
function Profiler.Start(label)
return {
label = label,
startTime = GetGameTimer()
}
end
function Profiler.Stop(timer, warnThresholdMs)
local elapsed = GetGameTimer() - timer.startTime
warnThresholdMs = warnThresholdMs or 5
if elapsed >= warnThresholdMs then
print(('[^1PERF WARNING^0] %s took %dms (threshold: %dms)'):format(
timer.label, elapsed, warnThresholdMs
))
elseif GetConvar('myresource_debug', 'false') == 'true' then
print(('[^2PERF^0] %s completed in %dms'):format(timer.label, elapsed))
end
return elapsed
end
-- Usage in a server event
RegisterNetEvent('myresource:heavyOperation', function(data)
local timer = Profiler.Start('heavyOperation')
-- ... expensive processing ...
local result = ProcessLargeDataSet(data)
Profiler.Stop(timer, 10) -- warn if over 10ms
end)
State-Bag-Fehlersuche
State Bags sind eine leistungsstarke, aber manchmal verwirrende Funktion. Wenn State-Bag-Werte nicht wie erwartet aktualisiert werden, liegt das meist daran, dass du sie für die falsche Entität festlegst, der Handler nicht den richtigen Bag-Namen abfängt oder die Replikation verzögert ist. Erstelle einen State-Bag-Inspector-Befehl, der alle Zustände einer bestimmten Entität ausgibt.
-- server/debug_statebags.lua
RegisterCommand('debugstate', function(source, args)
local targetId = tonumber(args[1])
if not targetId then
print('Usage: debugstate [playerId]')
return
end
local playerPed = GetPlayerPed(targetId)
if playerPed == 0 then
print('Player not found: ' .. targetId)
return
end
local entityState = Player(targetId).state
print(('[^3STATE BAGS^0] Player %d:'):format(targetId))
-- Print known state keys (state bags don't have an iterator)
local keysToCheck = {'job', 'gang', 'duty', 'dead', 'phone', 'inventory'}
for _, key in ipairs(keysToCheck) do
local val = entityState[key]
if val ~= nil then
print((' %s = %s (%s)'):format(key, tostring(val), type(val)))
end
end
end, true)
Unverzichtbare Checkliste zur Fehlersuche
- Prüfe beide Konsolen. Sieh dir immer sowohl die Serverkonsole (txAdmin oder Terminal) als auch die Clientkonsole (F8) auf Fehler an. Ein Fehler auf der einen Seite erklärt oft das fehlerhafte Verhalten auf der anderen.
- Prüfe den Status der Resource. Verwende
ensureum deine Resource neu zu starten undrestartum eine einzelne Resource neu zu starten. Prüferesmonund stelle sicher, dass die Resource tatsächlich läuft. - Teste in einer sauberen Umgebung. Deaktiviere andere Scripts, die mit denselben Systemen interagieren. Viele Fehler entstehen durch Konflikte zwischen Resources und nicht durch Fehler in einem einzelnen Script.
- Lies den Stacktrace der Fehlermeldung sorgfältig. Lua-Stack-Traces zeigen dir die genaue Datei und Zeilennummer. Lies sie von unten nach oben, um die Aufrufkette nachzuvollziehen, die zum Fehler geführt hat.
- Verwende pcall für fehleranfällige Vorgänge. Verpacke Datenbankabfragen, Export-Aufrufe und das Dekodieren von JSON in
pcalldamit Fehler abgefangen werden, ohne dass dein Script dadurch abstürzt. - Versioniere deine Configs. Wenn Spieler Bugs melden, frag sie, welche Version sie verwenden. Viele Probleme entstehen durch veraltete Config-Dateien nach einem Update.