From f643f61f29f463684fc0665b1a7c6c6ea897e6ab Mon Sep 17 00:00:00 2001 From: WatDuhHekBro Date: Sat, 8 May 2021 08:32:45 -0500 Subject: [PATCH] Cleaned up logging invocations --- README.md | 1 + docs/Overview.md | 15 +++++++++++++ package-lock.json | 2 +- package.json | 2 +- src/commands/system/admin.ts | 17 +++++++++++++-- src/commands/utility/info.ts | 2 +- src/commands/utility/scanemotes.ts | 3 ++- src/commands/utility/translate.ts | 2 +- src/index.ts | 2 ++ src/modules/channelDefaults.ts | 10 +++++++-- src/modules/emoteRegistry.ts | 34 ++++++------------------------ src/modules/eventLogging.ts | 23 ++++++++++++++++++++ src/modules/guildMemberAdd.ts | 2 +- src/modules/lavalink.ts | 3 ++- src/modules/ready.ts | 2 +- src/modules/setup.ts | 10 --------- src/modules/storage.ts | 32 ++++++++++++++-------------- src/modules/systemInfo.ts | 27 +++++++----------------- src/structures.ts | 18 +++++++++++----- 19 files changed, 117 insertions(+), 90 deletions(-) create mode 100644 src/modules/eventLogging.ts diff --git a/README.md b/README.md index f5affc7..1ae7013 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # TravBot-v3

+

diff --git a/docs/Overview.md b/docs/Overview.md index 620f832..9cd3831 100644 --- a/docs/Overview.md +++ b/docs/Overview.md @@ -4,6 +4,7 @@ - [Setting up the development environment](#setting-up-the-development-environment) - [Adding a new command](#adding-a-new-command) - [Adding a new non-command feature](#adding-a-new-non-command-feature) +- [Notes](#notes) # Introduction @@ -68,3 +69,17 @@ export const client = new Client(); import "./modules/myModule"; ``` + +# Notes + +## Logger + +All calls to `console.error`, `console.warn`, `console.log`, and `console.debug` will also add to an in-memory log you can download, noted by verbosity levels `Error`, `Warn`, `Info`, and `Verbose` respectively. +- `Error`: This indicates stuff that could or is breaking at least some functionality of the bot. +- `Warn`: This indicates stuff that should probably be fixed but isn't going to break the bot. +- `Info`: Used for general events such as joining/leaving guilds for example, but try not to go overboard on logging everything. +- `Verbose`: This is used as a sort of separator for logging potentially error-prone events so that if an error occurs, you can find the context that error originated from. +- In order to make reading the logs easier, context should be provided with each call. For example, if a call is being made from the storage module, you'd do something like `console.log("[storage]", "the message")`. + - `[name]` indicates a module + - `:name:` indicates a command + - If a message is clear enough as to what the context was though, it's probably unnecessary to include this prefix. However, you should definitely attach context prefixes to error objects, who knows where those might originate. diff --git a/package-lock.json b/package-lock.json index 502e24a..59d7bdd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ "mathjs": "^9.3.0", "moment": "^2.29.1", "ms": "^2.1.3", - "onion-lasers": "^1.1.1", + "onion-lasers": "^1.1.2", "relevant-urban": "^2.0.0", "translate-google": "^1.4.3", "weather-js": "^2.0.0" diff --git a/package.json b/package.json index 4434e7c..e94ee70 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "mathjs": "^9.3.0", "moment": "^2.29.1", "ms": "^2.1.3", - "onion-lasers": "^1.1.1", + "onion-lasers": "^1.1.2", "relevant-urban": "^2.0.0", "translate-google": "^1.4.3", "weather-js": "^2.0.0" diff --git a/src/commands/system/admin.ts b/src/commands/system/admin.ts index d78f2ff..9db1605 100644 --- a/src/commands/system/admin.ts +++ b/src/commands/system/admin.ts @@ -217,7 +217,14 @@ export default new NamedCommand({ description: "Sets the name that the channel will be reset to once no more members are in the channel.", usage: "()", - run: "Please provide a new voice channel name.", + async run({send, guild, message}) { + const voiceChannel = message.member?.voice.channel; + if (!voiceChannel) return send("You are not in a voice channel."); + const guildStorage = Storage.getGuild(guild!.id); + delete guildStorage.channelNames[voiceChannel.id]; + Storage.save(); + return send(`Successfully removed the default channel name for ${voiceChannel}.`); + }, any: new RestCommand({ async run({send, guild, message, combined}) { const voiceChannel = message.member?.voice.channel; @@ -226,6 +233,8 @@ export default new NamedCommand({ const newName = combined; if (!voiceChannel) return send("You are not in a voice channel."); + if (!guild!.me?.hasPermission(Permissions.FLAGS.MANAGE_CHANNELS)) + return send("I can't change channel names without the `Manage Channels` permission."); guildStorage.channelNames[voiceChannel.id] = newName; Storage.save(); @@ -324,8 +333,12 @@ export default new NamedCommand({ async run({send, message, channel, guild, author, member, client, args, combined}) { try { let evaled = eval(combined); + // If promises like message.channel.send() are invoked, await them so unnecessary error reports don't leak into the command handler. + // Also, it's more useful to see the value rather than Promise { }. + if (evaled instanceof Promise) evaled = await evaled; if (typeof evaled !== "string") evaled = require("util").inspect(evaled); - send(clean(evaled), {code: "js", split: true}); + // Also await this send call so that if the message is empty, it doesn't leak into the command handler. + await send(clean(evaled), {code: "js", split: true}); } catch (err) { send(clean(err), {code: "js", split: true}); } diff --git a/src/commands/utility/info.ts b/src/commands/utility/info.ts index e18f6e2..9a789f7 100644 --- a/src/commands/utility/info.ts +++ b/src/commands/utility/info.ts @@ -65,7 +65,7 @@ export default new NamedCommand({ `**❯ Creation Date:** ${utc(client.user?.createdTimestamp).format("Do MMMM YYYY HH:mm:ss")}`, `**❯ Node.JS:** ${process.version}`, `**❯ Version:** v${process.env.npm_package_version}`, - `**❯ Discord.JS:** ${djsversion}`, + `**❯ Discord.JS:** v${djsversion}`, "\u200b" ]) .addField("System", [ diff --git a/src/commands/utility/scanemotes.ts b/src/commands/utility/scanemotes.ts index 7845b19..917f0bb 100644 --- a/src/commands/utility/scanemotes.ts +++ b/src/commands/utility/scanemotes.ts @@ -129,6 +129,7 @@ export default new NamedCommand({ if (reaction.count !== userReactions + botReactions) { console.warn( + "[scanemotes]", `[Channel: ${channel.id}, Message: ${msg.id}] A reaction count of ${reaction.count} was expected but was given ${userReactions} user reactions and ${botReactions} bot reactions.` ); warnings++; @@ -159,7 +160,7 @@ export default new NamedCommand({ "y" )}.` ); - console.log(`Finished operation in ${finishTime - startTime} ms.`); + console.log("[scanemotes]", `Finished operation in ${finishTime - startTime} ms.`); channel.stopTyping(); // Display stats on emote usage. diff --git a/src/commands/utility/translate.ts b/src/commands/utility/translate.ts index 85a6486..eb4075c 100644 --- a/src/commands/utility/translate.ts +++ b/src/commands/utility/translate.ts @@ -32,7 +32,7 @@ export default new NamedCommand({ }); }) .catch((error) => { - console.error(error); + console.error("[translate]", error); send( `${error}\nPlease use the following list: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes` ); diff --git a/src/index.ts b/src/index.ts index 65e7d70..7a85389 100644 --- a/src/index.ts +++ b/src/index.ts @@ -75,3 +75,5 @@ import "./modules/messageEmbed"; import "./modules/guildMemberAdd"; import "./modules/streamNotifications"; import "./modules/channelDefaults"; +// This module must be loaded last for the dynamic event reading to work properly. +import "./modules/eventLogging"; diff --git a/src/modules/channelDefaults.ts b/src/modules/channelDefaults.ts index 5fcb84a..b346f2f 100644 --- a/src/modules/channelDefaults.ts +++ b/src/modules/channelDefaults.ts @@ -1,11 +1,17 @@ import {client} from "../index"; import {Storage} from "../structures"; +import {Permissions} from "discord.js"; client.on("voiceStateUpdate", async (before, after) => { - const channel = before.channel!; + const channel = before.channel; const {channelNames} = Storage.getGuild(after.guild.id); - if (channel?.members.size === 0 && channel?.id in channelNames) { + if ( + channel && + channel.members.size === 0 && + channel.id in channelNames && + before.guild.me?.hasPermission(Permissions.FLAGS.MANAGE_CHANNELS) + ) { channel.setName(channelNames[channel.id]); } }); diff --git a/src/modules/emoteRegistry.ts b/src/modules/emoteRegistry.ts index 914ecbd..e261390 100644 --- a/src/modules/emoteRegistry.ts +++ b/src/modules/emoteRegistry.ts @@ -24,31 +24,9 @@ function updateGlobalEmoteRegistry(): void { FileManager.write("public/emote-registry", data, true); } -client.on("emojiCreate", (emote) => { - console.log(`Updated emote registry. ${emote.name}`); - updateGlobalEmoteRegistry(); -}); - -client.on("emojiDelete", () => { - console.log("Updated emote registry."); - updateGlobalEmoteRegistry(); -}); - -client.on("emojiUpdate", () => { - console.log("Updated emote registry."); - updateGlobalEmoteRegistry(); -}); - -client.on("guildCreate", () => { - console.log("Updated emote registry."); - updateGlobalEmoteRegistry(); -}); - -client.on("guildDelete", () => { - console.log("Updated emote registry."); - updateGlobalEmoteRegistry(); -}); - -client.on("ready", () => { - updateGlobalEmoteRegistry(); -}); +client.on("emojiCreate", updateGlobalEmoteRegistry); +client.on("emojiDelete", updateGlobalEmoteRegistry); +client.on("emojiUpdate", updateGlobalEmoteRegistry); +client.on("guildCreate", updateGlobalEmoteRegistry); +client.on("guildDelete", updateGlobalEmoteRegistry); +client.on("ready", updateGlobalEmoteRegistry); diff --git a/src/modules/eventLogging.ts b/src/modules/eventLogging.ts new file mode 100644 index 0000000..8a5752f --- /dev/null +++ b/src/modules/eventLogging.ts @@ -0,0 +1,23 @@ +// This will keep track of the last event that occurred to provide context to error messages. +// Like with logging each command invocation, it's not a good idea to pollute the logs with this kind of stuff when it works most of the time. +// However, it's also a pain to debug when no context is provided for an error message. +import {client} from ".."; + +let lastEvent = "N/A"; + +// A generic process handler is set to catch unhandled rejections other than the ones from Lavalink and Discord. +process.on("unhandledRejection", (reason: any) => { + const isLavalinkError = reason?.code === "ECONNREFUSED"; + const isDiscordError = reason?.name === "DiscordAPIError"; + + if (!isLavalinkError) + if (!isDiscordError || lastEvent !== "message") + // If it's a DiscordAPIError on a message event, I'll make the assumption that it comes from the command handler. + console.error(`@${lastEvent}\n${reason.stack}`); +}); + +// This will dynamically attach all known events instead of doing it manually. +// As such, it needs to be placed after all other events are attached or the tracking won't be done properly. +for (const event of client.eventNames()) { + client.on(event, () => (lastEvent = event.toString())); +} diff --git a/src/modules/guildMemberAdd.ts b/src/modules/guildMemberAdd.ts index 9a26ffd..2d441ac 100644 --- a/src/modules/guildMemberAdd.ts +++ b/src/modules/guildMemberAdd.ts @@ -68,7 +68,7 @@ client.on("guildMemberAdd", async (member) => { ); } } else { - console.error(`"${welcomeChannel}" is not a valid text channel ID!`); + console.error("[modules/guildMemberAdd]", `"${welcomeChannel}" is not a valid text channel ID!`); } } }); diff --git a/src/modules/lavalink.ts b/src/modules/lavalink.ts index 8b18a7a..3a81f02 100644 --- a/src/modules/lavalink.ts +++ b/src/modules/lavalink.ts @@ -33,7 +33,8 @@ for (const listener of process.listeners("unhandledRejection")) { process.on("unhandledRejection", (reason: any) => { if (reason?.code === "ECONNREFUSED") { - console.error( + // This is console.warn instead of console.error because on development environments, unless Lavalink is being tested, it won't interfere with the bot's functionality. + console.warn( `[discord.js-lavalink-musicbot] Caught unhandled rejection: ${reason.stack}\nIf this is causing issues, head to the support server at https://discord.gg/dNN4azK` ); } diff --git a/src/modules/ready.ts b/src/modules/ready.ts index 3b83964..55a5a37 100644 --- a/src/modules/ready.ts +++ b/src/modules/ready.ts @@ -4,7 +4,7 @@ import {Config, Storage} from "../structures"; client.once("ready", () => { if (client.user) { console.ready( - `Logged in as ${client.user.tag}, ready to serve ${client.users.cache.size} users in ${client.guilds.cache.size} servers..` + `Logged in as ${client.user.tag}, ready to serve ${client.users.cache.size} users in ${client.guilds.cache.size} servers.` ); client.user.setActivity({ type: "LISTENING", diff --git a/src/modules/setup.ts b/src/modules/setup.ts index f1df71a..dede737 100644 --- a/src/modules/setup.ts +++ b/src/modules/setup.ts @@ -3,16 +3,6 @@ import inquirer from "inquirer"; import Storage from "./storage"; import {Config} from "../structures"; -// A generic process handler is set to catch unhandled rejections other than the ones from Lavalink and Discord. -process.on("unhandledRejection", (reason: any) => { - const isLavalinkError = reason?.code === "ECONNREFUSED"; - const isDiscordError = reason?.name === "DiscordAPIError"; - - if (!isLavalinkError && !isDiscordError) { - console.error(reason.stack); - } -}); - // This file is called (or at least should be called) automatically as long as a config file doesn't exist yet. // And that file won't be written until the data is successfully initialized. const prompts = [ diff --git a/src/modules/storage.ts b/src/modules/storage.ts index c38ddc6..9cdb360 100644 --- a/src/modules/storage.ts +++ b/src/modules/storage.ts @@ -14,18 +14,18 @@ const Storage = { data = JSON.parse(file); } catch (error) { if (process.argv[2] !== "dev") { - console.warn(`Malformed JSON data (header: ${header}), backing it up.`, file); - fs.writeFile( - `${path}.backup`, - file, - generateHandler(`Backup file of "${header}" successfully written as ${file}.`) - ); + console.warn("[storage.read]", `Malformed JSON data (header: ${header}), backing it up.`, file); + fs.writeFile(`${path}.backup`, file, (error) => { + if (error) console.error("[storage.read]", error); + console.log("[storage.read]", `Backup file of "${header}" successfully written as ${file}.`); + }); } } } return data; }, + // There is no need to log successfully written operations as it pollutes the log with useless info for debugging. write(header: string, data: object, asynchronous = true) { this.open("data"); const path = `data/${header}.json`; @@ -34,12 +34,17 @@ const Storage = { const result = JSON.stringify(data, null, "\t"); if (asynchronous) - fs.writeFile(path, result, generateHandler(`"${header}" sucessfully spaced and written.`)); + fs.writeFile(path, result, (error) => { + if (error) console.error("[storage.write]", error); + }); else fs.writeFileSync(path, result); } else { const result = JSON.stringify(data); - if (asynchronous) fs.writeFile(path, result, generateHandler(`"${header}" sucessfully written.`)); + if (asynchronous) + fs.writeFile(path, result, (error) => { + if (error) console.error("[storage.write]", error); + }); else fs.writeFileSync(path, result); } }, @@ -54,15 +59,10 @@ const Storage = { }, close(path: string) { if (fs.existsSync(path) && fs.readdirSync(path).length === 0) - fs.rmdir(path, generateHandler(`"${path}" successfully closed.`)); + fs.rmdir(path, (error) => { + if (error) console.error("[storage.close]", error); + }); } }; -export function generateHandler(message: string) { - return (error: Error | null) => { - if (error) console.error(error); - else console.debug(message); - }; -} - export default Storage; diff --git a/src/modules/systemInfo.ts b/src/modules/systemInfo.ts index f9a0b64..afc64ca 100644 --- a/src/modules/systemInfo.ts +++ b/src/modules/systemInfo.ts @@ -1,24 +1,9 @@ import {client} from "../index"; -import {GuildChannel, TextChannel} from "discord.js"; +import {TextChannel} from "discord.js"; import {Config} from "../structures"; -client.on("channelCreate", async (channel) => { - const botGuilds = client.guilds; - - if (channel instanceof GuildChannel) { - const createdGuild = await botGuilds.fetch(channel.guild.id); - console.log(`Channel created in '${createdGuild.name}' called '#${channel.name}'`); - } -}); - -client.on("channelDelete", async (channel) => { - const botGuilds = client.guilds; - - if (channel instanceof GuildChannel) { - const createdGuild = await botGuilds.fetch(channel.guild.id); - console.log(`Channel deleted in '${createdGuild.name}' called '#${channel.name}'`); - } -}); +// Logging which guilds the bot is added to and removed from makes sense. +// However, logging the specific channels that are added/removed is a tad bit privacy-invading. client.on("guildCreate", (guild) => { console.log( @@ -51,7 +36,11 @@ client.on("guildDelete", (guild) => { if (channel && channel.type === "text") { (channel as TextChannel).send(`\`${guild.name}\` (\`${guild.id}\`) removed the bot.`); } else { - console.warn(`${Config.systemLogsChannel} is not a valid text channel for system logs!`); + console.warn( + `${Config.systemLogsChannel} is not a valid text channel for system logs! Removing it from storage.` + ); + Config.systemLogsChannel = null; + Config.save(); } } }); diff --git a/src/structures.ts b/src/structures.ts index f1b7396..f40fb29 100644 --- a/src/structures.ts +++ b/src/structures.ts @@ -138,7 +138,10 @@ class Guild { /** Gets a member's profile if they exist and generate one if not. */ public getMember(id: string): Member { if (!/\d{17,}/g.test(id)) - console.warn(`"${id}" is not a valid user ID! It will be erased when the data loads again.`); + console.warn( + "[structures]", + `"${id}" is not a valid user ID! It will be erased when the data loads again.` + ); if (id in this.members) return this.members[id]; else { @@ -164,7 +167,10 @@ class StorageStructure extends GenericStructure { /** Gets a user's profile if they exist and generate one if not. */ public getUser(id: string): User { if (!/\d{17,}/g.test(id)) - console.warn(`"${id}" is not a valid user ID! It will be erased when the data loads again.`); + console.warn( + "[structures]", + `"${id}" is not a valid user ID! It will be erased when the data loads again.` + ); if (id in this.users) return this.users[id]; else { @@ -177,7 +183,10 @@ class StorageStructure extends GenericStructure { /** Gets a guild's settings if they exist and generate one if not. */ public getGuild(id: string): Guild { if (!/\d{17,}/g.test(id)) - console.warn(`"${id}" is not a valid guild ID! It will be erased when the data loads again.`); + console.warn( + "[structures]", + `"${id}" is not a valid guild ID! It will be erased when the data loads again.` + ); if (id in this.guilds) return this.guilds[id]; else { @@ -195,8 +204,7 @@ export let Storage = new StorageStructure(FileManager.read("storage")); // This part will allow the user to manually edit any JSON files they want while the program is running which'll update the program's cache. // However, fs.watch is a buggy mess that should be avoided in production. While it helps test out stuff for development, it's not a good idea to have it running outside of development as it causes all sorts of issues. if (IS_DEV_MODE) { - watch("data", (event, filename) => { - console.debug("File Watcher:", event, filename); + watch("data", (_event, filename) => { const header = filename.substring(0, filename.indexOf(".json")); switch (header) {