mirror of
https://github.com/keanuplayz/TravBot-v3.git
synced 2024-08-15 02:33:12 +00:00
Cleaned up logging invocations
This commit is contained in:
parent
736070d615
commit
f643f61f29
19 changed files with 117 additions and 90 deletions
|
@ -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">
|
||||
|
|
|
@ -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
2
package-lock.json
generated
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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});
|
||||
}
|
||||
|
|
|
@ -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", [
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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`
|
||||
);
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
|
23
src/modules/eventLogging.ts
Normal file
23
src/modules/eventLogging.ts
Normal 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()));
|
||||
}
|
|
@ -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!`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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`
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue