Skip to content

Commit

Permalink
Error logging improvements (#125)
Browse files Browse the repository at this point in the history
* Send errors to #error-log channel

* test error

* Print out more info; Ignore CommandError

* Fix handler name

* Move test command temporarily

* Include symbol in error msg to signify if it was sent to error-log channel

* Test error in button listener

* Add time to error to debug Unknown interaction errors
  • Loading branch information
camero2734 authored Jul 7, 2024
1 parent 389fea8 commit 9a0eb6e
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 9 deletions.
3 changes: 2 additions & 1 deletion app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ client.on("messageReactionAdd", async (reaction, user) => {
});

client.on("interactionCreate", async (interaction) => {
const receivedInteractionAt = new Date();
if (interaction.isChatInputCommand()) {
const commandIdentifier = SlashCommand.getIdentifierFromInteraction(interaction);
const command = SlashCommands.get(commandIdentifier);
Expand Down Expand Up @@ -165,7 +166,7 @@ client.on("interactionCreate", async (interaction) => {
);
} catch (e) {
console.log("Error in interaction handler", e);
ErrorHandler(interaction, e);
ErrorHandler(interaction, e, interactionHandler.name, receivedInteractionAt);
}
} else if (interaction.isAutocomplete()) {
const commandIdentifier = SlashCommand.getIdentifierFromInteraction(interaction);
Expand Down
1 change: 1 addition & 0 deletions src/Configuration/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export const channelIDs = <const>{
staff: "470324442082312192",
positivity: "470451678294835200",
bottest: "470406597860917249",
errorlog: "604458274141765633",
houseofgold: "470339161967165440",
hiatusmemes: "470434533884297216",
halloffame: "470335826321211393",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,8 @@ const genModalSubmitId = command.addInteractionListener("verifmodaldone", ["seed
const staffEmbed = new EmbedBuilder()
.setAuthor({ name: ctx.user.username, iconURL: ctx.user.displayAvatarURL() })
.setTitle(`${correct ? "Passed" : "Failed"}: Part 1`)
.setColor(correct ? "Blurple" : 0xff8888);
.setColor(correct ? "Blurple" : 0xff8888)
.setFooter({ text: ctx.user.id });

for (const key in partOne) {
const inputted = ctx.fields.getTextInputValue(key);
Expand Down
17 changes: 15 additions & 2 deletions src/InteractionEntrypoints/slashcommands/staff/test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ApplicationCommandOptionType, roleMention } from "discord.js";
import { ActionRowBuilder, ApplicationCommandOptionType, ButtonBuilder, ButtonStyle, roleMention } from "discord.js";
import { userIDs } from "../../../Configuration/config";
import { CommandError } from "../../../Configuration/definitions";
import F from "../../../Helpers/funcs";
Expand All @@ -24,7 +24,14 @@ command.setHandler(async (ctx) => {
const roles = await ctx.guild.roles.fetch();
const withColor = roles.filter(r => r.hexColor.toLowerCase() === "#ffc6d5");
if (ctx.opts.num === 1) {
// Nothing
const button = new ButtonBuilder()
.setLabel("Test button")
.setStyle(ButtonStyle.Primary)
.setCustomId(genTestId({ num: '4' }));

const actionRow = new ActionRowBuilder<ButtonBuilder>().setComponents(button);

await ctx.editReply({ content: "Test", components: [actionRow] });
} else if (ctx.opts.num === 2) {
await updateCurrentSongBattleMessage();
} else if (ctx.opts.num === 3) {
Expand Down Expand Up @@ -54,4 +61,10 @@ command.setHandler(async (ctx) => {
}
});

const genTestId = command.addInteractionListener("testCommandBtn", ["num"], async (ctx) => {
await ctx.deferReply({ ephemeral: true });

throw new Error("Test error");
});

export default command;
2 changes: 1 addition & 1 deletion src/Structures/EntrypointBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export abstract class InteractionEntrypoint<
EntrypointEvents.emit("entrypointFinished", { entrypoint: this, ctx });
} catch (e) {
EntrypointEvents.emit("entrypointErrored", { entrypoint: this, ctx });
ErrorHandler(ctx, e);
ErrorHandler(ctx, e, this.identifier);
}
}

Expand Down
66 changes: 62 additions & 4 deletions src/Structures/Errors.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { CommandInteraction, DMChannel, EmbedBuilder, Interaction, TextChannel } from "discord.js";
import { CommandError } from "../Configuration/definitions";
import { ChannelType, CommandInteraction, DMChannel, EmbedBuilder, GuildTextBasedChannel, Interaction, InteractionType, TextBasedChannel, TextChannel } from "discord.js";
import { nanoid } from "nanoid";
import { guild } from "../../app";
import { channelIDs } from "../Configuration/config";
import { CommandError } from "../Configuration/definitions";
import F from "../Helpers/funcs";

const getReplyMethod = async (ctx: CommandInteraction) => {
if (!ctx.isRepliable() || !ctx.isChatInputCommand()) return ctx.followUp;
Expand All @@ -9,8 +12,27 @@ const getReplyMethod = async (ctx: CommandInteraction) => {
return ctx.editReply;
}

export const ErrorHandler = async (ctx: TextChannel | DMChannel | Interaction, e: unknown) => {
let errorChannel: GuildTextBasedChannel | null = null;
const getErrorChannel = async () => {
if (errorChannel) return errorChannel;

const channel = await guild?.channels.fetch(channelIDs.errorlog);
if (!channel || channel.type !== ChannelType.GuildText) return null;

return errorChannel = channel;
}

const getChannelName = (ctx: TextBasedChannel | Interaction): string => {
if ("name" in ctx) return `Text: ${ctx.name}`;
if ("recipient" in ctx) return `DM: ${ctx.recipient?.tag}`;

const interactionType = F.keys(InteractionType).find(k => InteractionType[k as keyof typeof InteractionType] === ctx.type) || "Unknown Interaction";
return `${interactionType}: ${ctx.user.tag} in ${ctx.channel ? getChannelName(ctx.channel) : "?"}`
}

export const ErrorHandler = async (ctx: TextChannel | DMChannel | Interaction, e: unknown, handler?: string, receivedInteractionAt?: Date) => {
const errorId = nanoid();
const errorDelta = receivedInteractionAt ? Date.now() - receivedInteractionAt.getTime() : null;

console.log("===================================");
console.log("|| ||");
Expand All @@ -20,6 +42,42 @@ export const ErrorHandler = async (ctx: TextChannel | DMChannel | Interaction, e
console.log("===================================");
if (e instanceof Error) console.log(e.stack);

let sentInErrorChannel = false;
const errorChannel = await getErrorChannel();
if (errorChannel && !(e instanceof CommandError)) {
const embed = new EmbedBuilder()
.setTitle("An error occurred!")
.setColor("DarkRed")
.setDescription(`An error occurred in ${ctx.constructor.name}.\nError ID: ${errorId}`)
.addFields({
name: "Channel",
value: getChannelName(ctx),
})
.setTimestamp()
.setFooter({ text: errorId });

if (handler) {
embed.addFields({
name: "Handler",
value: handler,
});
}

if (errorDelta) {
embed.addFields({
name: "Time to Error",
value: `${errorDelta}ms`,
});
}

embed.addFields({
name: "Error",
value: `\`\`\`js\n${e instanceof Error ? e.stack : e}\`\`\``,
})
await errorChannel.send({ embeds: [embed] });
sentInErrorChannel = true;
}

const ectx = ctx as unknown as CommandInteraction & { send: CommandInteraction["reply"] };
ectx.send = await getReplyMethod(ectx) as typeof ectx["send"];

Expand All @@ -41,7 +99,7 @@ export const ErrorHandler = async (ctx: TextChannel | DMChannel | Interaction, e
console.log(`Unknown error:`, e);
const embed = new EmbedBuilder()
.setTitle("An unknown error occurred!")
.setFooter({ text: `DEMA internet machine really broke. Error ${errorId}` });
.setFooter({ text: `DEMA internet machine really broke. Error ${errorId} ${sentInErrorChannel ? "📝" : ""}` });
ectx.send({ embeds: [embed], components: [], ephemeral: true });
}
};

0 comments on commit 9a0eb6e

Please sign in to comment.