feat(meta): Improve bugsplat.

This commit is contained in:
2023-09-10 07:14:37 +03:00
parent 6343d47072
commit 57a4e641ba
3 changed files with 105 additions and 9 deletions

View File

@@ -11,6 +11,7 @@ from discord.app_commands.errors import CommandInvokeError as appCommandInvokeEr
from aiohttp import ClientSession from aiohttp import ClientSession
from data import Database from data import Database
from utils.lib import tabulate
from .config import Conf from .config import Conf
from .logger import logging_context, log_context, log_action_stack, log_wrap, set_logging_context from .logger import logging_context, log_context, log_action_stack, log_wrap, set_logging_context
@@ -192,7 +193,7 @@ class LionBot(Bot):
pass pass
except asyncio.TimeoutError: except asyncio.TimeoutError:
pass pass
except Exception: except Exception as e:
logger.exception( logger.exception(
f"Caught an unknown CommandInvokeError while executing: {cmd_str}", f"Caught an unknown CommandInvokeError while executing: {cmd_str}",
extra={'action': 'BotError', 'with_ctx': True} extra={'action': 'BotError', 'with_ctx': True}
@@ -201,10 +202,36 @@ class LionBot(Bot):
error_embed = discord.Embed(title="Something went wrong!") error_embed = discord.Embed(title="Something went wrong!")
error_embed.description = ( error_embed.description = (
"An unexpected error occurred while processing your command!\n" "An unexpected error occurred while processing your command!\n"
"Our development team has been notified, and the issue should be fixed soon.\n" "Our development team has been notified, and the issue will be addressed soon.\n"
"If the error persists, please contact our support team and give them the following number: " "If the error persists, or you have any questions, please contact our [support team]({link}) "
f"`{ctx.interaction.id if ctx.interaction else ctx.message.id}`" "and give them the extra details below."
) ).format(link=self.config.bot.support_guild)
details = {}
details['error'] = f"`{repr(e)}`"
if ctx.interaction:
details['interactionid'] = f"`{ctx.interaction.id}`"
if ctx.command:
details['cmd'] = f"`{ctx.command.qualified_name}`"
if ctx.author:
details['author'] = f"`{ctx.author.id}` -- `{ctx.author}`"
if ctx.guild:
details['guild'] = f"`{ctx.guild.id}` -- `{ctx.guild.name}`"
details['my_guild_perms'] = f"`{ctx.guild.me.guild_permissions.value}`"
if ctx.author:
ownerstr = ' (owner)' if ctx.author == ctx.guild.owner else ''
details['author_guild_perms'] = f"`{ctx.author.guild_permissions.value}{ownerstr}`"
if ctx.channel.type is discord.enums.ChannelType.private:
details['channel'] = "`Direct Message`"
elif ctx.channel:
details['channel'] = f"`{ctx.channel.id}` -- `{ctx.channel.name}`"
details['my_channel_perms'] = f"`{ctx.channel.permissions_for(ctx.guild.me).value}`"
if ctx.author:
details['author_channel_perms'] = f"`{ctx.channel.permissions_for(ctx.author).value}`"
details['shard'] = f"`{self.shardname}`"
details['log_stack'] = f"`{log_action_stack.get()}`"
table = '\n'.join(tabulate(*details.items()))
error_embed.add_field(name='Details', value=table)
try: try:
await ctx.error_reply(embed=error_embed) await ctx.error_reply(embed=error_embed)

View File

@@ -1,12 +1,15 @@
import logging import logging
import discord
from discord import Interaction from discord import Interaction
from discord.app_commands import CommandTree from discord.app_commands import CommandTree
from discord.app_commands.errors import AppCommandError, CommandInvokeError from discord.app_commands.errors import AppCommandError, CommandInvokeError
from discord.enums import InteractionType from discord.enums import InteractionType
from discord.app_commands.namespace import Namespace from discord.app_commands.namespace import Namespace
from .logger import logging_context, set_logging_context, log_wrap from utils.lib import tabulate
from .logger import logging_context, set_logging_context, log_wrap, log_action_stack
from .errors import SafeCancellation from .errors import SafeCancellation
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -17,7 +20,7 @@ class LionTree(CommandTree):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self._call_tasks = set() self._call_tasks = set()
async def on_error(self, interaction, error) -> None: async def on_error(self, interaction: discord.Interaction, error) -> None:
try: try:
if isinstance(error, CommandInvokeError): if isinstance(error, CommandInvokeError):
raise error.original raise error.original
@@ -28,6 +31,51 @@ class LionTree(CommandTree):
pass pass
except Exception: except Exception:
logger.exception(f"Unhandled exception in interaction: {interaction}", extra={'action': 'TreeError'}) logger.exception(f"Unhandled exception in interaction: {interaction}", extra={'action': 'TreeError'})
if not interaction.is_expired():
splat = self.bugsplat(interaction, error)
try:
if interaction.response.is_done():
await interaction.followup.send(embed=splat, ephemeral=True)
else:
await interaction.response.send_message(embed=splat, ephemeral=True)
except discord.HTTPException:
pass
def bugsplat(self, interaction, e):
error_embed = discord.Embed(title="Something went wrong!", colour=discord.Colour.red())
error_embed.description = (
"An unexpected error occurred during this interaction!\n"
"Our development team has been notified, and the issue will be addressed soon.\n"
"If the error persists, or you have any questions, please contact our [support team]({link}) "
"and give them the extra details below."
).format(link=interaction.client.config.bot.support_guild)
details = {}
details['error'] = f"`{repr(e)}`"
details['interactionid'] = f"`{interaction.id}`"
details['interactiontype'] = f"`{interaction.type}`"
if interaction.command:
details['cmd'] = f"`{interaction.command.qualified_name}`"
if interaction.user:
details['user'] = f"`{interaction.user.id}` -- `{interaction.user}`"
if interaction.guild:
details['guild'] = f"`{interaction.guild.id}` -- `{interaction.guild.name}`"
details['my_guild_perms'] = f"`{interaction.guild.me.guild_permissions.value}`"
if interaction.user:
ownerstr = ' (owner)' if interaction.user == interaction.guild.owner else ''
details['user_guild_perms'] = f"`{interaction.user.guild_permissions.value}{ownerstr}`"
if interaction.channel.type is discord.enums.ChannelType.private:
details['channel'] = "`Direct Message`"
elif interaction.channel:
details['channel'] = f"`{interaction.channel.id}` -- `{interaction.channel.name}`"
details['my_channel_perms'] = f"`{interaction.channel.permissions_for(interaction.guild.me).value}`"
if interaction.user:
details['user_channel_perms'] = f"`{interaction.channel.permissions_for(interaction.user).value}`"
details['shard'] = f"`{interaction.client.shardname}`"
details['log_stack'] = f"`{log_action_stack.get()}`"
table = '\n'.join(tabulate(*details.items()))
error_embed.add_field(name='Details', value=table)
return error_embed
def _from_interaction(self, interaction: Interaction) -> None: def _from_interaction(self, interaction: Interaction) -> None:
@log_wrap(context=f"iid: {interaction.id}", isolate=False) @log_wrap(context=f"iid: {interaction.id}", isolate=False)

View File

@@ -230,9 +230,20 @@ class LeoUI(View):
) )
except Exception: except Exception:
logger.exception( logger.exception(
f"Unhandled interaction exception occurred in item {item!r} of LeoUI {self!r}", f"Unhandled interaction exception occurred in item {item!r} of LeoUI {self!r} from interaction: "
f"{interaction.data}",
extra={'with_ctx': True, 'action': 'UIError'} extra={'with_ctx': True, 'action': 'UIError'}
) )
# Explicitly handle the bugsplat ourselves
if not interaction.is_expired():
splat = interaction.client.tree.bugsplat(interaction, error)
try:
if interaction.response.is_done():
await interaction.followup.send(embed=splat, ephemeral=True)
else:
await interaction.response.send_message(embed=splat, ephemeral=True)
except discord.HTTPException:
pass
class MessageUI(LeoUI): class MessageUI(LeoUI):
@@ -461,9 +472,19 @@ class LeoModal(Modal):
raise error raise error
except Exception: except Exception:
logger.exception( logger.exception(
f"Unhandled interaction exception occurred in {self!r}", f"Unhandled interaction exception occurred in {self!r}. Interaction: {interaction.data}",
extra={'with_ctx': True, 'action': 'ModalError'} extra={'with_ctx': True, 'action': 'ModalError'}
) )
# Explicitly handle the bugsplat ourselves
if not interaction.is_expired():
splat = interaction.client.tree.bugsplat(interaction, error)
try:
if interaction.response.is_done():
await interaction.followup.send(embed=splat, ephemeral=True)
else:
await interaction.response.send_message(embed=splat, ephemeral=True)
except discord.HTTPException:
pass
def error_handler_for(exc): def error_handler_for(exc):