mirror of https://github.com/stijndcl/didier
Log errors in Discord channels
parent
2e3b4823d0
commit
b54aed24e8
|
@ -49,6 +49,12 @@ class Discord(commands.Cog):
|
||||||
await birthdays.add_birthday(session, ctx.author.id, date)
|
await birthdays.add_birthday(session, ctx.author.id, date)
|
||||||
await self.client.confirm_message(ctx.message)
|
await self.client.confirm_message(ctx.message)
|
||||||
|
|
||||||
|
@commands.command(name="Join", usage="[Thread]")
|
||||||
|
async def join(self, ctx: commands.Context, thread: discord.Thread):
|
||||||
|
"""Make Didier join a thread"""
|
||||||
|
if thread.me is not None:
|
||||||
|
return await ctx.reply()
|
||||||
|
|
||||||
|
|
||||||
async def setup(client: Didier):
|
async def setup(client: Didier):
|
||||||
"""Load the cog"""
|
"""Load the cog"""
|
||||||
|
|
|
@ -43,7 +43,7 @@ class Owner(commands.Cog):
|
||||||
return await self.client.is_owner(ctx.author)
|
return await self.client.is_owner(ctx.author)
|
||||||
|
|
||||||
@commands.command(name="Error", aliases=["Raise"])
|
@commands.command(name="Error", aliases=["Raise"])
|
||||||
async def _error(self, ctx: commands.Context, message: str = "Debug"):
|
async def _error(self, ctx: commands.Context, *, message: str = "Debug"):
|
||||||
"""Raise an exception for debugging purposes"""
|
"""Raise an exception for debugging purposes"""
|
||||||
raise Exception(message)
|
raise Exception(message)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
import discord
|
||||||
|
from discord.ext import commands
|
||||||
|
|
||||||
|
from didier.utils.discord.constants import Limits
|
||||||
|
from didier.utils.types.string import abbreviate
|
||||||
|
|
||||||
|
__all__ = ["create_error_embed"]
|
||||||
|
|
||||||
|
|
||||||
|
def _get_traceback(exception: Exception) -> str:
|
||||||
|
"""Get a proper representation of the exception"""
|
||||||
|
tb = traceback.format_exception(type(exception), exception, exception.__traceback__)
|
||||||
|
error_string = ""
|
||||||
|
for line in tb:
|
||||||
|
# Don't add endless tracebacks
|
||||||
|
if line.strip().startswith("The above exception was the direct cause of"):
|
||||||
|
break
|
||||||
|
|
||||||
|
# Escape Discord markdown formatting
|
||||||
|
error_string += line.replace(r"*", r"\*").replace(r"_", r"\_")
|
||||||
|
if line.strip():
|
||||||
|
error_string += "\n"
|
||||||
|
|
||||||
|
return abbreviate(error_string, Limits.EMBED_FIELD_VALUE_LENGTH)
|
||||||
|
|
||||||
|
|
||||||
|
def create_error_embed(ctx: commands.Context, exception: Exception) -> discord.Embed:
|
||||||
|
"""Create an embed for the traceback of an exception"""
|
||||||
|
description = _get_traceback(exception)
|
||||||
|
|
||||||
|
if ctx.guild is None:
|
||||||
|
origin = "DM"
|
||||||
|
else:
|
||||||
|
origin = f"{ctx.channel.mention} ({ctx.guild.name})"
|
||||||
|
|
||||||
|
invocation = f"{ctx.author.display_name} in {origin}"
|
||||||
|
|
||||||
|
embed = discord.Embed(colour=discord.Colour.red())
|
||||||
|
embed.set_author(name="Error")
|
||||||
|
embed.add_field(name="Command", value=f"{ctx.message.content}", inline=True)
|
||||||
|
embed.add_field(name="Context", value=invocation, inline=True)
|
||||||
|
embed.add_field(name="Exception", value=abbreviate(str(exception), Limits.EMBED_FIELD_VALUE_LENGTH), inline=False)
|
||||||
|
embed.add_field(name="Traceback", value=description, inline=False)
|
||||||
|
|
||||||
|
return embed
|
|
@ -10,6 +10,7 @@ import settings
|
||||||
from database.crud import custom_commands
|
from database.crud import custom_commands
|
||||||
from database.engine import DBSession
|
from database.engine import DBSession
|
||||||
from database.utils.caches import CacheManager
|
from database.utils.caches import CacheManager
|
||||||
|
from didier.data.embeds.error_embed import create_error_embed
|
||||||
from didier.utils.discord.prefix import get_prefix
|
from didier.utils.discord.prefix import get_prefix
|
||||||
|
|
||||||
__all__ = ["Didier"]
|
__all__ = ["Didier"]
|
||||||
|
@ -139,6 +140,9 @@ class Didier(commands.Bot):
|
||||||
|
|
||||||
await self.process_commands(message)
|
await self.process_commands(message)
|
||||||
|
|
||||||
|
# TODO easter eggs
|
||||||
|
# TODO stats
|
||||||
|
|
||||||
async def _try_invoke_custom_command(self, message: discord.Message) -> bool:
|
async def _try_invoke_custom_command(self, message: discord.Message) -> bool:
|
||||||
"""Check if the message tries to invoke a custom command
|
"""Check if the message tries to invoke a custom command
|
||||||
|
|
||||||
|
@ -162,11 +166,50 @@ class Didier(commands.Bot):
|
||||||
# Nothing found
|
# Nothing found
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def on_command_error(self, context: commands.Context, exception: commands.CommandError, /) -> None:
|
async def on_thread_create(self, thread: discord.Thread):
|
||||||
"""Event triggered when a regular command errors"""
|
"""Event triggered when a new thread is created"""
|
||||||
# Print everything to the logs/stderr
|
await thread.join()
|
||||||
await super().on_command_error(context, exception)
|
|
||||||
|
|
||||||
# If developing, do nothing special
|
async def on_command_error(self, ctx: commands.Context, exception: commands.CommandError, /):
|
||||||
|
"""Event triggered when a regular command errors"""
|
||||||
|
# If working locally, print everything to your console
|
||||||
if settings.SANDBOX:
|
if settings.SANDBOX:
|
||||||
|
await super().on_command_error(ctx, exception)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# If commands have their own error handler, let it handle the error instead
|
||||||
|
if hasattr(ctx.command, "on_error"):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Ignore exceptions that aren't important
|
||||||
|
if isinstance(
|
||||||
|
exception,
|
||||||
|
(
|
||||||
|
commands.CommandNotFound,
|
||||||
|
commands.CheckFailure,
|
||||||
|
commands.TooManyArguments,
|
||||||
|
),
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Print everything that we care about to the logs/stderr
|
||||||
|
await super().on_command_error(ctx, exception)
|
||||||
|
|
||||||
|
if isinstance(exception, commands.MessageNotFound):
|
||||||
|
return await ctx.reply("This message could not be found.", ephemeral=True, delete_after=10)
|
||||||
|
|
||||||
|
if isinstance(
|
||||||
|
exception,
|
||||||
|
(
|
||||||
|
commands.BadArgument,
|
||||||
|
commands.MissingRequiredArgument,
|
||||||
|
commands.UnexpectedQuoteError,
|
||||||
|
commands.ExpectedClosingQuoteError,
|
||||||
|
),
|
||||||
|
):
|
||||||
|
return await ctx.reply("Invalid arguments.", ephemeral=True, delete_after=10)
|
||||||
|
|
||||||
|
if settings.ERRORS_CHANNEL is not None:
|
||||||
|
embed = create_error_embed(ctx, exception)
|
||||||
|
channel = self.get_channel(settings.ERRORS_CHANNEL)
|
||||||
|
await channel.send(embed=embed)
|
||||||
|
|
|
@ -61,3 +61,5 @@ black
|
||||||
flake8
|
flake8
|
||||||
mypy
|
mypy
|
||||||
```
|
```
|
||||||
|
|
||||||
|
It's also convenient to have code-formatting happen automatically on-save. The [`Black documentation`](https://black.readthedocs.io/en/stable/integrations/editors.html) explains how to set this up for different types of editors.
|
||||||
|
|
Loading…
Reference in New Issue