aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Discord-Bots

Discord-Bot-Fehlerbehandlung: try/except und Global Handler

Ein Discord-Bot-Fehler taucht meist zum denkbar schlechtesten Zeitpunkt auf: spät am Abend, wenn die Nutzer am aktivsten sind, hört der Bot plötzlich auf, auf Befehle zu reagieren, ein langer Traceback füllt das Terminal und der Prozess stürzt ab. Die gute Nachricht: Die große Mehrheit dieser Situationen ist vorhersehbar, und mit ordentlicher Fehlerbehandlung kannst du deinen Bot am Leben halten. In diesem Artikel behandle ich die häufigen Fehlertypen, den Einsatz von try/except auf Befehlsebene, das Einrichten eines Global Error Handlers und korrektes Logging — sowohl auf der discord.py- als auch der discord.js-Seite.

Warum Fehlerbehandlung wichtig ist

Ein Bot ist ein langlaufender Prozess. Eine einzige nicht abgefangene Exception in einem Befehl wird je nach Framework entweder diesen Befehl stillschweigend scheitern lassen oder im schlimmsten Fall den ganzen Bot lahmlegen. Für den Nutzer liest sich das schlicht als „der Bot ist kaputt". Eine solide Fehlerbehandlungsstrategie leistet drei Dinge:

  • Isolieren: Ein Fehler in einem Befehl beeinträchtigt die anderen nicht.
  • Informieren: Der Nutzer erhält eine sinnvolle Meldung, keinen Traceback.
  • Aufzeichnen: Als Entwickler siehst du im Log, was kaputtging und warum.

Die häufigsten Fehler

Bevor du Fehler behandeln kannst, musst du sie erkennen. In der Praxis triffst du am häufigsten auf diese:

  • Berechtigungsfehler: Der Bot versucht, in einem Kanal zu posten oder eine Rolle zu vergeben, ohne die Rechte dazu zu haben. In discord.py ist das discord.Forbidden; in discord.js ist es 50013 Missing Permissions.
  • Rate Limit (429): zu viele Anfragen in zu kurzer Zeit. Die Bibliotheken stellen diese meist automatisch in eine Warteschlange, aber Vorsicht beim Senden von Nachrichten in einer Schleife.
  • Ressource nicht gefunden (404): auf eine gelöschte Nachricht reagieren, ein nicht mehr existierendes Mitglied abrufen.
  • Ungültige Nutzereingabe: Text, wo eine Zahl erwartet wurde, ein fehlendes Argument. In discord.py: BadArgument / MissingRequiredArgument.
  • Token-/Gateway-Probleme: ein falsches Token oder nie aktivierte Privileged Intents.

try/except auf Befehlsebene

Die erste Verteidigungslinie ist ein try/except-Block, der die riskante Operation direkt umschließt. Die Kernregel: verwende niemals ein nacktes except: — fange immer die konkrete Exception ab, die du erwartest, und lass unerwartete nach oben durchblubbern, damit der Global Handler und die Logs sie sehen.

import discord
from discord.ext import commands

bot = commands.Bot(command_prefix="!", intents=discord.Intents.default())

@bot.command()
async def kick(ctx, member: discord.Member):
    try:
        await member.kick(reason="Moderator-Entscheidung")
        await ctx.send(f"{member} wurde vom Server entfernt.")
    except discord.Forbidden:
        await ctx.send("Ich habe keine Berechtigung, dieses Mitglied zu kicken.")
    except discord.HTTPException as e:
        await ctx.send("Während der Operation trat ein Netzwerkfehler auf.")
        raise  # erneut werfen, damit Logger und Global Handler sie sehen

Hier erklären wir dem Nutzer die zwei bekannten Fälle (keine Berechtigung, Netzwerkfehler); im discord.HTTPException-Zweig werfen wir den Fehler nach der Antwort mit raise erneut, damit er trotzdem in den Logs landet.

Einen Global Error Handler einrichten

In jeden einzelnen Befehl ein try/except zu schreiben, ist nicht tragbar. Definiere stattdessen einen zentralen Fehlerfänger. In discord.py ist das das on_command_error-Event:

import logging
logger = logging.getLogger("bot")

@bot.event
async def on_command_error(ctx, error):
    if isinstance(error, commands.MissingRequiredArgument):
        await ctx.send(f"Fehlendes Argument: {error.param.name}")
    elif isinstance(error, commands.BadArgument):
        await ctx.send("Du hast einen ungültigen Wert eingegeben.")
    elif isinstance(error, commands.CommandOnCooldown):
        await ctx.send(f"Bitte warte {error.retry_after:.1f}s.")
    elif isinstance(error, commands.MissingPermissions):
        await ctx.send("Du hast keine Berechtigung für diesen Befehl.")
    else:
        logger.exception("Unerwarteter Fehler", exc_info=error)
        await ctx.send("Ein unerwarteter Fehler trat auf und wurde protokolliert.")

Wenn du Slash-Commands (App Commands) nutzt, musst du über tree.on_error einen separaten Handler zuweisen; das klassische on_command_error für Prefix-Befehle deckt Slash-Commands nicht ab.

Auf der discord.js-Seite ist die Logik ähnlich: Du hörst auf das Client-Event und behandelst zusätzlich nicht abgefangene rejected Promises:

client.on('interactionCreate', async interaction => {
  if (!interaction.isChatInputCommand()) return;
  try {
    await handleCommand(interaction);
  } catch (err) {
    console.error(err);
    const reply = { content: 'Ein Fehler ist aufgetreten.', ephemeral: true };
    if (interaction.replied || interaction.deferred) {
      await interaction.followUp(reply);
    } else {
      await interaction.reply(reply);
    }
  }
});

process.on('unhandledRejection', err => console.error('Unhandled:', err));

Korrektes Logging

Mit print() zu arbeiten wirkt anfangs verlockend, ist in der Produktion aber unbrauchbar. Nutze das eingebaute logging-Modul von Python: Es bietet Level (INFO, WARNING, ERROR), Zeitstempel und das Schreiben in eine Datei.

import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
    handlers=[
        logging.FileHandler("bot.log", encoding="utf-8"),
        logging.StreamHandler(),
    ],
)

Ein paar praktische Tipps:

  • Verwende logger.exception(...) dort, wo du den Fehler abfängst; das hängt den Traceback automatisch an.
  • Logge niemals Geheimnisse wie Tokens oder Passwörter.
  • Rotiere deine Logdateien (RotatingFileHandler), damit die Festplatte nicht volläuft.
  • Kritische Fehler an einen eigenen Discord-Webhook-Kanal zu senden, ist eine praktische Methode, um Probleme früh zu erkennen.

Online bleiben, ohne abzustürzen

Selbst mit guten Handlern kann dein Bot eines Tages ausfallen. Betreibe den Prozess daher unter einem Supervisor: Ein systemd-Service unter Linux oder ein Process Manager wie pm2 startet den Bot beim Stoppen automatisch neu. Ziel ist nicht, den Absturz zu verstecken, sondern die Logs zu lesen, die Ursache zu beheben und den Supervisor nur als Sicherheitsnetz zu betrachten.

Häufige Fragen

Soll ich jeden Befehl in try/except verpacken?

Nein. Verwende ein lokales try/except nur dort, wo du eine eigene Nutzermeldung willst, oder um eine bekannte riskante Operation (Nachrichten löschen, Rollen vergeben). Für alles andere reicht das zentrale on_command_error.

Warum werden Fehler in Slash-Commands nicht abgefangen?

Weil on_command_error nur Prefix-Befehle abdeckt. Für Slash- (App-)Commands musst du separat den eigenen Fehler-Handler des Command-Trees definieren (tree.on_error).

Ich bekomme ständig Rate-Limit-Fehler — was tun?

Vermeide es, Nachrichten in einer Schleife hintereinander zu senden, teile Massenoperationen in Batches auf und vertraue der eingebauten Warteschlange der Bibliothek. Gruppiere deine Operationen, statt jede Anfrage mit einem manuellen sleep zu verlangsamen.

Stürzt dein Bot ständig ab oder kannst du seine Fehler nicht verfolgen? Ich helfe dir, eine solide Fehlerbehandlung und Logging einzurichten und deinen Bot produktionsreif zu machen. Nimm Kontakt mit mir auf.

Bu kategorideki tüm yazılar →

Devamı için