Een Discord bot fout duikt meestal op het slechtst mogelijke moment op: laat in de avond, wanneer gebruikers het actiefst zijn, stopt de bot plotseling met reageren op commando's, vult een lange traceback de terminal en valt het proces om. Het goede nieuws is dat de overgrote meerderheid van deze situaties voorspelbaar is en dat je met goede foutafhandeling je bot in de lucht kunt houden. In dit artikel behandel ik de veelvoorkomende fouttypes, het gebruik van try/except op commandoniveau, het opzetten van een global error handler en correct loggen — zowel aan de discord.py- als de discord.js-kant.
Waarom foutafhandeling belangrijk is
Een bot is een langdurig proces. Eén niet-afgevangen exception in één commando zal, afhankelijk van het framework, dat commando stilletjes laten falen of in het ergste geval de hele bot platleggen. Voor de gebruiker leest dat simpelweg als "de bot is kapot". Een degelijke strategie voor foutafhandeling doet drie dingen:
- Isoleren: een fout in één commando treft de andere niet.
- Informeren: de gebruiker krijgt een zinvolle melding, geen traceback.
- Vastleggen: als ontwikkelaar zie je in de logs wat er stuk ging en waarom.
De meest voorkomende fouten
Voor je fouten kunt afhandelen, moet je ze herkennen. In de praktijk kom je deze het vaakst tegen:
- Permissiefouten: de bot probeert in een kanaal te posten of een rol toe te kennen zonder de rechten daartoe. In discord.py is dat
discord.Forbidden; in discord.js is het50013 Missing Permissions. - Rate limit (429): te veel verzoeken in te korte tijd. De libraries plaatsen die meestal automatisch in een wachtrij, maar let op bij het versturen van berichten in een lus.
- Resource niet gevonden (404): reageren op een verwijderd bericht, een lid ophalen dat niet meer bestaat.
- Ongeldige gebruikersinvoer: tekst waar een getal werd verwacht, een ontbrekend argument. In discord.py:
BadArgument/MissingRequiredArgument. - Token-/gateway-problemen: een verkeerd token, of privileged intents die nooit zijn aangezet.
try/except op commandoniveau
De eerste verdedigingslinie is een try/except-blok dat de riskante operatie direct omhult. De kernregel: gebruik nooit een kale except: — vang altijd de specifieke exception af die je verwacht, en laat onverwachte exceptions doorbubbelen zodat de global handler en de logs ze zien.
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="Beslissing van een moderator")
await ctx.send(f"{member} is van de server verwijderd.")
except discord.Forbidden:
await ctx.send("Ik heb geen rechten om dit lid te kicken.")
except discord.HTTPException as e:
await ctx.send("Er trad een netwerkfout op tijdens de operatie.")
raise # opnieuw gooien zodat de logger en global handler ze zien
Hier leggen we de twee bekende gevallen (geen rechten, netwerkfout) uit aan de gebruiker; in de discord.HTTPException-tak gooien we de fout met raise opnieuw na het antwoorden, zodat die alsnog in de logs terechtkomt.
Een global error handler opzetten
In elk afzonderlijk commando een try/except schrijven is niet houdbaar. Definieer in plaats daarvan een centrale foutvanger. In discord.py is dat het 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"Ontbrekend argument: {error.param.name}")
elif isinstance(error, commands.BadArgument):
await ctx.send("Je hebt een ongeldige waarde ingevoerd.")
elif isinstance(error, commands.CommandOnCooldown):
await ctx.send(f"Wacht even {error.retry_after:.1f}s.")
elif isinstance(error, commands.MissingPermissions):
await ctx.send("Je hebt geen rechten voor dit commando.")
else:
logger.exception("Onverwachte fout", exc_info=error)
await ctx.send("Er trad een onverwachte fout op en die is gelogd.")
Als je slash commands (app commands) gebruikt, moet je een aparte handler toewijzen via tree.on_error; de klassieke on_command_error voor prefix-commando's dekt slash commands niet.
Aan de discord.js-kant is de logica vergelijkbaar: je luistert naar het client-event en handelt ook niet-afgevangen rejected promises af:
client.on('interactionCreate', async interaction => {
if (!interaction.isChatInputCommand()) return;
try {
await handleCommand(interaction);
} catch (err) {
console.error(err);
const reply = { content: 'Er is een fout opgetreden.', ephemeral: true };
if (interaction.replied || interaction.deferred) {
await interaction.followUp(reply);
} else {
await interaction.reply(reply);
}
}
});
process.on('unhandledRejection', err => console.error('Unhandled:', err));
Correct loggen
Werken met print() lijkt in het begin aantrekkelijk, maar is onbruikbaar in productie. Gebruik de ingebouwde logging-module van Python: die biedt niveaus (INFO, WARNING, ERROR), tijdstempels en schrijven naar een bestand.
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(),
],
)
Enkele praktische tips:
- Gebruik
logger.exception(...)daar waar je de fout afvangt; dat voegt de traceback automatisch toe. - Log nooit geheimen zoals tokens of wachtwoorden.
- Roteer je logbestanden (
RotatingFileHandler) zodat de schijf niet volloopt. - Kritieke fouten naar een apart Discord-webhookkanaal sturen is een praktische manier om problemen vroeg te vangen.
In de lucht blijven zonder te crashen
Zelfs met goede handlers kan je bot ooit omvallen. Draai het proces daarom onder een supervisor: een systemd-service op Linux, of een process manager als pm2, herstart de bot automatisch wanneer die stopt. Het doel is niet om de crash te verbergen, maar om de logs te lezen, de oorzaak te verhelpen en de supervisor enkel als vangnet te zien.
Veelgestelde vragen
Moet ik elk commando in try/except verpakken?
Nee. Gebruik een lokale try/except alleen waar je een aangepaste gebruikersmelding wilt, of rond een bekende riskante operatie (berichten verwijderen, rollen toekennen). Voor al het andere volstaat de centrale on_command_error.
Waarom worden fouten in slash commands niet afgevangen?
Omdat on_command_error alleen prefix-commando's dekt. Voor slash (app) commands moet je apart de eigen foutafhandelaar van de commandoboom definiëren (tree.on_error).
Ik krijg steeds rate limit-fouten — wat moet ik doen?
Vermijd het achter elkaar versturen van berichten in een lus, splits bulkoperaties in batches en vertrouw op de ingebouwde wachtrij van de library. Groepeer je operaties in plaats van elk verzoek met een handmatige sleep te vertragen.
Crasht je bot voortdurend of kun je de fouten niet volgen? Ik help je graag met het opzetten van degelijke foutafhandeling en logging en het productieklaar maken van je bot. Neem contact met me op.