Zurück zum Blog
Guide7 Min. Lesezeit

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 ensure um deine Resource neu zu starten und restart um eine einzelne Resource neu zu starten. Prüfe resmon und 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 pcall damit 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.

Bereit loszulegen?

Hol dir die Scripts in unserem Shop, oder komm auf den Discord für Support, Updates und einen Blick auf das, was kommt.