Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions src/config/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* Log Engine Configuration
*
* Configures @wgtechlabs/log-engine for the Discord bot.
* This replaces the custom logger wrapper with direct log-engine usage.
*
* Configuration:
* - Uses LogMode.DEBUG when DEBUG_MODE=true, otherwise LogMode.INFO
* - Excludes ISO timestamps (includeIsoTimestamp: false)
* - Includes local time formatting (includeLocalTime: true)
* - No custom output handlers - uses log-engine defaults
*
* @module config/logger
*/

import { LogEngine, LogMode } from '@wgtechlabs/log-engine';

// Configure LogEngine based on environment variables
const debugMode: boolean = process.env.DEBUG_MODE === 'true';

// Set the log mode based on DEBUG_MODE environment variable
// In debug mode, show all logs; otherwise show info and above
const logMode = debugMode ? LogMode.DEBUG : LogMode.INFO;

// Configure LogEngine with the required format settings
LogEngine.configure({
mode: logMode,
format: {
includeIsoTimestamp: false,
includeLocalTime: true,
},
});

// Export LogEngine for use throughout the application
export { LogEngine };

// Re-export LogMode for convenience
export { LogMode };
22 changes: 11 additions & 11 deletions src/deploy_commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { REST, Routes } from 'discord.js';
import * as fs from 'node:fs';
import * as path from 'node:path';
import * as dotenv from 'dotenv';
import logger from './utils/logger';
import { LogEngine } from './config/logger';

// Load environment variables
dotenv.config();
Expand All @@ -33,17 +33,17 @@ const { DISCORD_BOT_TOKEN, CLIENT_ID, GUILD_ID } = process.env;
* Validate required environment variables
*/
if (!DISCORD_BOT_TOKEN) {
logger.error('DISCORD_BOT_TOKEN is required but not set in environment variables');
LogEngine.error('DISCORD_BOT_TOKEN is required but not set in environment variables');
process.exit(1);
}

if (!CLIENT_ID) {
logger.error('CLIENT_ID is required but not set in environment variables');
LogEngine.error('CLIENT_ID is required but not set in environment variables');
process.exit(1);
}

if (!GUILD_ID) {
logger.error('GUILD_ID is required but not set in environment variables');
LogEngine.error('GUILD_ID is required but not set in environment variables');
process.exit(1);
}

Expand Down Expand Up @@ -91,14 +91,14 @@ for (const folder of commandFolders) {
// Validate command structure and add to deployment array
if ('data' in command && 'execute' in command) {
commands.push(command.data.toJSON());
logger.debug(`Loaded command from ${filePath}`);
LogEngine.debug(`Loaded command from ${filePath}`);
}
else {
logger.warn(`The command at ${filePath} is missing a required "data" or "execute" property.`);
LogEngine.warn(`The command at ${filePath} is missing a required "data" or "execute" property.`);
}
}
catch (error) {
logger.error(`Failed to load command from ${filePath}:`, error);
LogEngine.error(`Failed to load command from ${filePath}:`, error);
}
}
}
Expand All @@ -118,10 +118,10 @@ const rest = new REST().setToken(DISCORD_BOT_TOKEN);
*/
(async (): Promise<void> => {
try {
logger.info(`Started refreshing ${commands.length} application (/) commands.`);
LogEngine.info(`Started refreshing ${commands.length} application (/) commands.`);

if (commands.length === 0) {
logger.warn('No commands found to deploy.');
LogEngine.warn('No commands found to deploy.');
return;
}

Expand All @@ -131,11 +131,11 @@ const rest = new REST().setToken(DISCORD_BOT_TOKEN);
{ body: commands }
) as any[];

logger.info(`Successfully reloaded ${data.length} application (/) commands.`);
LogEngine.info(`Successfully reloaded ${data.length} application (/) commands.`);
}
catch (error) {
// Log any deployment errors for debugging
logger.error('Command deployment failed:', error);
LogEngine.error('Command deployment failed:', error);
process.exit(1);
}
})();
4 changes: 2 additions & 2 deletions src/events/error.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Events } from 'discord.js';
import * as logger from '../utils/logger';
import { LogEngine } from '../config/logger';

/**
* Global Discord.js Error Event Handler
Expand All @@ -18,5 +18,5 @@ export const name = Events.Error;
export const once = false;
export function execute(error: Error): void {
// Log the error with full stack trace for troubleshooting
logger.error(`Discord.js Client Error: ${error.stack || error}`);
LogEngine.error(`Discord.js Client Error: ${error.stack || error}`);
}
20 changes: 10 additions & 10 deletions src/events/interactionCreate.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Events, ChannelType, MessageFlags, Interaction, CommandInteraction, ModalSubmitInteraction } from 'discord.js';
import { createTicket, bindTicketWithThread } from '../services/unthread';
import * as logger from '../utils/logger';
import { LogEngine } from '../config/logger';
import { setKey } from '../utils/memory';
import { getOrCreateCustomer, getCustomerByDiscordId, updateCustomer } from '../utils/customerUtils';

Expand Down Expand Up @@ -36,7 +36,7 @@ async function handleSupportModal(interaction: ModalSubmitInteraction): Promise<
// If no email provided, try to get existing customer record
const existingCustomer = await getCustomerByDiscordId(interaction.user.id);
email = existingCustomer?.email || `${interaction.user.username}@discord.user`;
logger.debug(`Using fallback email for user ${interaction.user.id}: ${email}`);
LogEngine.debug(`Using fallback email for user ${interaction.user.id}: ${email}`);
} else {
// If email provided, update or create customer record
const existingCustomer = await getCustomerByDiscordId(interaction.user.id);
Expand All @@ -46,10 +46,10 @@ async function handleSupportModal(interaction: ModalSubmitInteraction): Promise<
} else {
await getOrCreateCustomer(interaction.user, email);
}
logger.debug(`Stored email for user ${interaction.user.id}: ${email}`);
LogEngine.debug(`Stored email for user ${interaction.user.id}: ${email}`);
}

logger.debug(`Support ticket submitted: ${title}, ${issue}, email: ${email}`);
LogEngine.debug(`Support ticket submitted: ${title}, ${issue}, email: ${email}`);

// Acknowledge interaction immediately to prevent Discord timeout
// Using ephemeral reply so only the submitter can see it
Expand All @@ -61,7 +61,7 @@ async function handleSupportModal(interaction: ModalSubmitInteraction): Promise<
try {
// Step 1: Create ticket in unthread.io using external API
ticket = await createTicket(interaction.user, title, issue, email);
logger.debug('Ticket created:', ticket);
LogEngine.debug('Ticket created:', ticket);

// Validate ticket creation was successful
if (!ticket.friendlyId) {
Expand Down Expand Up @@ -108,17 +108,17 @@ async function handleSupportModal(interaction: ModalSubmitInteraction): Promise<
} catch (error) {
// Handle any failures in the ticket creation workflow
// This could be API errors, permission issues, or Discord rate limits
logger.error('Ticket creation failed:', error);
LogEngine.error('Ticket creation failed:', error);

// Cleanup: If we created a mapping but failed afterwards, clean it up
if (ticket && ticket.id && thread && thread.id) {
try {
// Remove the mapping to prevent orphaned entries
await setKey(`ticket:discord:${thread.id}`, null, 1); // Set with short TTL to delete
await setKey(`ticket:unthread:${ticket.id}`, null, 1);
logger.info(`Cleaned up orphaned ticket mapping: Discord thread ${thread.id} <-> Unthread ticket ${ticket.id}`);
LogEngine.info(`Cleaned up orphaned ticket mapping: Discord thread ${thread.id} <-> Unthread ticket ${ticket.id}`);
} catch (cleanupError) {
logger.error('Failed to cleanup orphaned ticket mapping:', cleanupError);
LogEngine.error('Failed to cleanup orphaned ticket mapping:', cleanupError);
}
}

Expand All @@ -134,7 +134,7 @@ async function handleSlashCommand(interaction: CommandInteraction): Promise<void

// Check if command exists in our registered commands
if (!command) {
logger.error(`No command matching ${interaction.commandName} was found.`);
LogEngine.error(`No command matching ${interaction.commandName} was found.`);
return;
}

Expand All @@ -144,7 +144,7 @@ async function handleSlashCommand(interaction: CommandInteraction): Promise<void
await command.execute(interaction);
} catch (error) {
// Log the full error for debugging
logger.error(error);
LogEngine.error('Command execution error:', error);

// Handle response based on interaction state
// If we already replied or deferred, use followUp
Expand Down
22 changes: 11 additions & 11 deletions src/events/messageCreate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Events, Message } from "discord.js";
import { version } from "../../package.json";
import { sendMessageToUnthread, getTicketByDiscordThreadId, getCustomerById } from "../services/unthread";
import { isValidatedForumChannel } from "../utils/channelUtils";
import * as logger from "../utils/logger";
import { LogEngine } from "../config/logger";

/**
* Message Creation Event Handler
Expand Down Expand Up @@ -57,7 +57,7 @@ export async function execute(message: Message): Promise<void> {
const isForumPost = isValidForum && message.id === message.channel.id;

if (isForumPost) {
logger.debug(`Skipping forum post ID ${message.id} that created thread ${message.channel.id}`);
LogEngine.debug(`Skipping forum post ID ${message.id} that created thread ${message.channel.id}`);
return;
}

Expand All @@ -73,9 +73,9 @@ export async function execute(message: Message): Promise<void> {
const referenced = await message.channel.messages.fetch(message.reference.messageId);
quotedMessage = `> ${referenced.content}`;
messageToSend = `${quotedMessage}\n\n${message.content}`;
logger.debug(`Added quote context from message ${message.reference.messageId}`);
LogEngine.debug(`Added quote context from message ${message.reference.messageId}`);
} catch (err) {
logger.error('Error fetching the referenced message:', err);
LogEngine.error('Error fetching the referenced message:', err);
// Continue with original message if quote retrieval fails
}
}
Expand All @@ -97,15 +97,15 @@ export async function execute(message: Message): Promise<void> {
// Add attachments list to the message with separator characters
messageToSend = messageToSend || '';
messageToSend += `\n\nAttachments: ${attachmentLinks.join(' | ')}`;
logger.debug(`Added ${attachments.length} attachments to message`);
LogEngine.debug(`Added ${attachments.length} attachments to message`);
}
}

// Retrieve or create customer email for Unthread ticket association
const customer = await getCustomerById(message.author.id);
const email = customer?.email || `${message.author.username}@discord.user`;

logger.debug(`Forwarding message to Unthread ticket ${ticketMapping.unthreadTicketId}`, {
LogEngine.debug(`Forwarding message to Unthread ticket ${ticketMapping.unthreadTicketId}`, {
threadId: message.channel.id,
authorId: message.author.id,
hasAttachments: message.attachments.size > 0,
Expand All @@ -119,12 +119,12 @@ export async function execute(message: Message): Promise<void> {
messageToSend,
email
);
logger.info(`Forwarded message to Unthread for ticket ${ticketMapping.unthreadTicketId}`, response);
LogEngine.info(`Forwarded message to Unthread for ticket ${ticketMapping.unthreadTicketId}`, response);
} else {
logger.debug(`Message in thread ${message.channel.id} has no Unthread ticket mapping, skipping`);
LogEngine.debug(`Message in thread ${message.channel.id} has no Unthread ticket mapping, skipping`);
}
} catch (error) {
logger.error("Error sending message to Unthread:", error);
LogEngine.error("Error sending message to Unthread:", error);
// Consider adding error notification to the thread in production environments
}
}
Expand Down Expand Up @@ -158,12 +158,12 @@ async function handleLegacyCommands(message: Message): Promise<void> {
if (message.content === "!!ping") {
const latency = Date.now() - message.createdTimestamp;
await message.reply(`Latency is ${latency}ms.`);
logger.info(`Responded to ping command with latency ${latency}ms`);
LogEngine.info(`Responded to ping command with latency ${latency}ms`);
}

// Check version - helps track which bot version is running in production
if (message.content === "!!version") {
await message.reply(`Version: ${version}`);
logger.info(`Responded to version command with version ${version}`);
LogEngine.info(`Responded to version command with version ${version}`);
}
}
6 changes: 3 additions & 3 deletions src/events/messageDelete.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Events, Message } from 'discord.js';
import * as logger from '../utils/logger';
import { LogEngine } from '../config/logger';
import { setKey, getKey } from '../utils/memory';

/**
Expand Down Expand Up @@ -44,8 +44,8 @@ export async function execute(message: Message): Promise<void> {
// Update the cache
await setKey(channelKey, filteredList, 60000); // 1 minute TTL

logger.debug(`Cached deleted message ID: ${message.id} from channel: ${message.channel.id}`);
LogEngine.debug(`Cached deleted message ID: ${message.id} from channel: ${message.channel.id}`);
} catch (error) {
logger.error('Error caching deleted message:', error);
LogEngine.error('Error caching deleted message:', error);
}
}
10 changes: 5 additions & 5 deletions src/events/ready.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

import { Events, ActivityType, Client } from 'discord.js';
import * as packageJSON from '../../package.json';
import logger from '../utils/logger';
import { LogEngine } from '../config/logger';
import channelUtils from '../utils/channelUtils';

const { getValidatedForumChannelIds } = channelUtils;
Expand Down Expand Up @@ -51,7 +51,7 @@ const readyEvent = {
});

// Log successful initialization with version information for monitoring
logger.info(`Logged in as ${bot.user?.tag} @ v${packageJSON.version}`);
LogEngine.info(`Logged in as ${bot.user?.tag} @ v${packageJSON.version}`);

// Validate forum channel configuration on startup
try {
Expand All @@ -61,16 +61,16 @@ const readyEvent = {
const invalidCount = allChannelIds.length - validForumChannels.length;

if (invalidCount > 0) {
logger.warn(`${invalidCount} channel(s) in FORUM_CHANNEL_IDS are not forum channels and will be ignored`);
LogEngine.warn(`${invalidCount} channel(s) in FORUM_CHANNEL_IDS are not forum channels and will be ignored`);
}

if (validForumChannels.length > 0) {
logger.info(`Monitoring ${validForumChannels.length} forum channel(s) for ticket creation`);
LogEngine.info(`Monitoring ${validForumChannels.length} forum channel(s) for ticket creation`);
}
}
}
catch (error) {
logger.error('Error validating forum channels on startup:', error);
LogEngine.error('Error validating forum channels on startup:', error);
}
},
};
Expand Down
Loading