Cleaned up logging invocations

This commit is contained in:
WatDuhHekBro 2021-05-08 08:32:45 -05:00
parent 736070d615
commit f643f61f29
No known key found for this signature in database
GPG key ID: E128514902DF8A05
19 changed files with 117 additions and 90 deletions

View file

@ -1,6 +1,7 @@
# TravBot-v3
<p align="center">
<!-- The image could potentially be a hyperlink to invite TravBot. -->
<img src="https://i.imgur.com/l2E2Tfi.png"/>
</p>
<p align="center">

View file

@ -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.

2
package-lock.json generated
View file

@ -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"

View file

@ -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"

View file

@ -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: "(<name>)",
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 { <pending> }.
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});
}

View file

@ -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", [

View file

@ -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.

View file

@ -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`
);

View file

@ -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";

View file

@ -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]);
}
});

View file

@ -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);

View file

@ -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()));
}

View file

@ -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!`);
}
}
});

View file

@ -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`
);
}

View file

@ -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",

View file

@ -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 = [

View file

@ -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;

View file

@ -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();
}
}
});

View file

@ -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) {