diff --git a/src/config/logger.ts b/src/config/logger.ts new file mode 100644 index 0000000..bf7134b --- /dev/null +++ b/src/config/logger.ts @@ -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 }; \ No newline at end of file diff --git a/src/deploy_commands.ts b/src/deploy_commands.ts index db983a5..551e59f 100644 --- a/src/deploy_commands.ts +++ b/src/deploy_commands.ts @@ -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(); @@ -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); } @@ -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); } } } @@ -118,10 +118,10 @@ const rest = new REST().setToken(DISCORD_BOT_TOKEN); */ (async (): Promise => { 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; } @@ -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); } })(); \ No newline at end of file diff --git a/src/events/error.ts b/src/events/error.ts index 434986b..5f3e660 100644 --- a/src/events/error.ts +++ b/src/events/error.ts @@ -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 @@ -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}`); } \ No newline at end of file diff --git a/src/events/interactionCreate.ts b/src/events/interactionCreate.ts index 16929e1..624887c 100644 --- a/src/events/interactionCreate.ts +++ b/src/events/interactionCreate.ts @@ -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'; @@ -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); @@ -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 @@ -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) { @@ -108,7 +108,7 @@ 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) { @@ -116,9 +116,9 @@ async function handleSupportModal(interaction: ModalSubmitInteraction): Promise< // 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); } } @@ -134,7 +134,7 @@ async function handleSlashCommand(interaction: CommandInteraction): Promise { 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; } @@ -73,9 +73,9 @@ export async function execute(message: Message): Promise { 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 } } @@ -97,7 +97,7 @@ export async function execute(message: Message): Promise { // 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`); } } @@ -105,7 +105,7 @@ export async function execute(message: Message): Promise { 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, @@ -119,12 +119,12 @@ export async function execute(message: Message): Promise { 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 } } @@ -158,12 +158,12 @@ async function handleLegacyCommands(message: Message): Promise { 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}`); } } \ No newline at end of file diff --git a/src/events/messageDelete.ts b/src/events/messageDelete.ts index 2adbeb4..a4ed4bb 100644 --- a/src/events/messageDelete.ts +++ b/src/events/messageDelete.ts @@ -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'; /** @@ -44,8 +44,8 @@ export async function execute(message: Message): Promise { // 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); } } \ No newline at end of file diff --git a/src/events/ready.ts b/src/events/ready.ts index b7b6f14..5c9c1da 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -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; @@ -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 { @@ -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); } }, }; diff --git a/src/events/threadCreate.ts b/src/events/threadCreate.ts index 46f6273..f00ec6a 100644 --- a/src/events/threadCreate.ts +++ b/src/events/threadCreate.ts @@ -9,7 +9,7 @@ import { Events, EmbedBuilder, PermissionFlagsBits, ThreadChannel, Message } from 'discord.js'; import { createTicket, bindTicketWithThread } from '../services/unthread'; import { withRetry } from '../utils/retry'; -import * as logger from '../utils/logger'; +import { LogEngine } from '../config/logger'; import { getOrCreateCustomer, getCustomerByDiscordId } from '../utils/customerUtils'; import { isValidatedForumChannel } from '../utils/channelUtils'; import { version } from '../../package.json'; @@ -22,18 +22,18 @@ export async function execute(thread: ThreadChannel): Promise { const isValidForum = await isValidatedForumChannel(thread.parentId || ''); if (!isValidForum) return; } catch (error: any) { - logger.error('Error validating forum channel:', error.message); - logger.error(`Thread: "${thread.name}" (${thread.id}) in Guild: ${thread.guild.name} (${thread.guild.id})`); - logger.error('Skipping thread processing due to validation error'); + LogEngine.error('Error validating forum channel:', error.message); + LogEngine.error(`Thread: "${thread.name}" (${thread.id}) in Guild: ${thread.guild.name} (${thread.guild.id})`); + LogEngine.error('Skipping thread processing due to validation error'); return; } - logger.info(`New forum post detected in monitored channel: ${thread.name}`); + LogEngine.info(`New forum post detected in monitored channel: ${thread.name}`); // Check bot permissions before proceeding with any Discord actions const botMember = thread.guild.members.me; if (!botMember) { - logger.error('Bot member not found in guild'); + LogEngine.error('Bot member not found in guild'); return; } @@ -47,7 +47,7 @@ export async function execute(thread: ThreadChannel): Promise { // Check permissions in the parent forum channel const parentChannel = thread.parent; if (!parentChannel) { - logger.error('Parent channel not found for thread'); + LogEngine.error('Parent channel not found for thread'); return; } @@ -64,10 +64,10 @@ export async function execute(thread: ThreadChannel): Promise { } }); - logger.error(`Cannot create support tickets in forum channel "${parentChannel.name}" (${parentChannel.id})`); - logger.error(`Missing permissions: ${permissionNames.join(', ')}`); - logger.error(`Action required: Ask a server administrator to grant the bot these permissions in the forum channel.`); - logger.error(`Guild: ${thread.guild.name} (${thread.guild.id})`); + LogEngine.error(`Cannot create support tickets in forum channel "${parentChannel.name}" (${parentChannel.id})`); + LogEngine.error(`Missing permissions: ${permissionNames.join(', ')}`); + LogEngine.error(`Action required: Ask a server administrator to grant the bot these permissions in the forum channel.`); + LogEngine.error(`Guild: ${thread.guild.name} (${thread.guild.id})`); return; } @@ -90,14 +90,14 @@ export async function execute(thread: ThreadChannel): Promise { } }); - logger.error(`Cannot process forum thread "${thread.name}" (${thread.id})`); - logger.error(`Missing thread permissions: ${threadPermissionNames.join(', ')}`); - logger.error(`Action required: Ask a server administrator to grant the bot these permissions for forum threads.`); - logger.error(`Guild: ${thread.guild.name} (${thread.guild.id})`); + LogEngine.error(`Cannot process forum thread "${thread.name}" (${thread.id})`); + LogEngine.error(`Missing thread permissions: ${threadPermissionNames.join(', ')}`); + LogEngine.error(`Action required: Ask a server administrator to grant the bot these permissions for forum threads.`); + LogEngine.error(`Guild: ${thread.guild.name} (${thread.guild.id})`); return; } - logger.info(`Permission check passed for forum thread "${thread.name}" in channel "${parentChannel.name}"`); + LogEngine.info(`Permission check passed for forum thread "${thread.name}" in channel "${parentChannel.name}"`); let firstMessage: Message | undefined; // Declare in higher scope for error logging access @@ -158,15 +158,15 @@ export async function execute(thread: ThreadChannel): Promise { content: `Hello <@${author.id}>, we have received your ticket and will respond shortly. Please check this thread for updates.` }); - logger.info(`Forum post converted to ticket: #${ticket.friendlyId}`); + LogEngine.info(`Forum post converted to ticket: #${ticket.friendlyId}`); } catch (error: any) { if (error.message.includes('timeout')) { - logger.error('Ticket creation is taking longer than expected. Please wait and try again.'); - logger.error(`Thread: "${thread.name}" (${thread.id}) in Guild: ${thread.guild.name} (${thread.guild.id})`); + LogEngine.error('Ticket creation is taking longer than expected. Please wait and try again.'); + LogEngine.error(`Thread: "${thread.name}" (${thread.id}) in Guild: ${thread.guild.name} (${thread.guild.id})`); } else { - logger.error('An error occurred while creating the ticket:', error.message); - logger.error(`Thread: "${thread.name}" (${thread.id}) in Guild: ${thread.guild.name} (${thread.guild.id})`); - logger.error(`Author: ${firstMessage?.author?.tag || 'Unknown'} (${firstMessage?.author?.id || 'Unknown'})`); + LogEngine.error('An error occurred while creating the ticket:', error.message); + LogEngine.error(`Thread: "${thread.name}" (${thread.id}) in Guild: ${thread.guild.name} (${thread.guild.id})`); + LogEngine.error(`Author: ${firstMessage?.author?.tag || 'Unknown'} (${firstMessage?.author?.id || 'Unknown'})`); } try { @@ -186,19 +186,19 @@ export async function execute(thread: ThreadChannel): Promise { .setTimestamp(); await thread.send({ embeds: [errorEmbed] }); - logger.info('Sent error notification to user in thread'); + LogEngine.info('Sent error notification to user in thread'); } else { - logger.warn('Cannot send error message to user - missing permissions'); - logger.warn('Users will not be notified of the ticket creation failure'); - logger.warn('Administrator action required: Grant bot "Send Messages in Threads" and "View Channel" permissions'); + LogEngine.warn('Cannot send error message to user - missing permissions'); + LogEngine.warn('Users will not be notified of the ticket creation failure'); + LogEngine.warn('Administrator action required: Grant bot "Send Messages in Threads" and "View Channel" permissions'); } } catch (sendError: any) { - logger.error('Could not send error message to thread:', sendError.message); + LogEngine.error('Could not send error message to thread:', sendError.message); if (sendError.code === 50001) { - logger.error('Error Code 50001: Missing Access - Bot lacks permission to send messages in this thread'); - logger.error('Administrator action required: Grant bot "Send Messages in Threads" permission'); + LogEngine.error('Error Code 50001: Missing Access - Bot lacks permission to send messages in this thread'); + LogEngine.error('Administrator action required: Grant bot "Send Messages in Threads" permission'); } - logger.error(`Thread: "${thread.name}" (${thread.id}) in Guild: ${thread.guild.name} (${thread.guild.id})`); + LogEngine.error(`Thread: "${thread.name}" (${thread.id}) in Guild: ${thread.guild.name} (${thread.guild.id})`); } } } \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 40c058b..e308335 100644 --- a/src/index.ts +++ b/src/index.ts @@ -49,7 +49,7 @@ import express from 'express'; import * as dotenv from 'dotenv'; import { BotConfig } from './types/discord'; import { webhookHandler } from './services/webhook'; -import logger from './utils/logger'; +import { LogEngine } from './config/logger'; import './types/global'; // Load environment variables @@ -60,7 +60,7 @@ const { DISCORD_BOT_TOKEN, PORT } = process.env as Partial; // 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); } @@ -162,7 +162,7 @@ app.post('/webhook/unthread', webhookHandler); * This server must be publicly accessible for Unthread to send webhooks. */ app.listen(port, () => { - logger.info(`Server listening on port ${port}`); + LogEngine.info(`Server listening on port ${port}`); }); /** @@ -192,22 +192,22 @@ try { if ('data' in command && 'execute' in command) { client.commands.set(command.data.name, command); - logger.debug(`Loaded command: ${command.data.name}`); + LogEngine.debug(`Loaded command: ${command.data.name}`); } 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); } } } - logger.info(`Loaded ${client.commands.size} commands successfully.`); + LogEngine.info(`Loaded ${client.commands.size} commands successfully.`); } catch (error) { - logger.error('Failed to load commands directory:', error); + LogEngine.error('Failed to load commands directory:', error); } /** @@ -237,17 +237,17 @@ try { client.on(event.name, (...args: any[]) => event.execute(...args)); } - logger.debug(`Loaded event: ${event.name}`); + LogEngine.debug(`Loaded event: ${event.name}`); } catch (error) { - logger.error(`Failed to load event from ${filePath}:`, error); + LogEngine.error(`Failed to load event from ${filePath}:`, error); } } - logger.info(`Loaded ${eventFiles.length} events successfully.`); + LogEngine.info(`Loaded ${eventFiles.length} events successfully.`); } catch (error) { - logger.error('Failed to load events directory:', error); + LogEngine.error('Failed to load events directory:', error); } /** @@ -259,9 +259,9 @@ catch (error) { client.login(DISCORD_BOT_TOKEN) .then(() => { global.discordClient = client; - logger.info('Discord client is ready and set globally.'); + LogEngine.info('Discord client is ready and set globally.'); }) .catch((error: Error) => { - logger.error('Failed to login Discord client:', error); + LogEngine.error('Failed to login Discord client:', error); process.exit(1); }); \ No newline at end of file diff --git a/src/services/unthread.ts b/src/services/unthread.ts index 9dad38c..d0d9b1a 100644 --- a/src/services/unthread.ts +++ b/src/services/unthread.ts @@ -19,7 +19,7 @@ import { decodeHtmlEntities } from '../utils/decodeHtmlEntities'; import { setKey, getKey } from '../utils/memory'; import { withRetry } from '../utils/retry'; import { EmbedBuilder, User, ThreadChannel } from 'discord.js'; -import * as logger from '../utils/logger'; +import { LogEngine } from '../config/logger'; import { isDuplicateMessage, containsDiscordAttachments, processQuotedContent } from '../utils/messageUtils'; import { findDiscordThreadByTicketId, findDiscordThreadByTicketIdWithRetry } from '../utils/threadUtils'; import { getOrCreateCustomer, getCustomerByDiscordId } from '../utils/customerUtils'; @@ -112,11 +112,11 @@ export async function getCustomerById(discordId: string): Promise { // Enhanced debugging: Initial request context - logger.info(`Creating ticket for user: ${user.tag} (${user.id})`); - logger.debug(`Env: API_KEY=${process.env.UNTHREAD_API_KEY ? process.env.UNTHREAD_API_KEY.length + 'chars' : 'NOT_SET'}, TRIAGE_ID=${JSON.stringify(process.env.UNTHREAD_TRIAGE_CHANNEL_ID || 'NOT_SET')}, INBOX_ID=${JSON.stringify(process.env.UNTHREAD_EMAIL_INBOX_ID || 'NOT_SET')}`); + LogEngine.info(`Creating ticket for user: ${user.tag} (${user.id})`); + LogEngine.debug(`Env: API_KEY=${process.env.UNTHREAD_API_KEY ? process.env.UNTHREAD_API_KEY.length + 'chars' : 'NOT_SET'}, TRIAGE_ID=${JSON.stringify(process.env.UNTHREAD_TRIAGE_CHANNEL_ID || 'NOT_SET')}, INBOX_ID=${JSON.stringify(process.env.UNTHREAD_EMAIL_INBOX_ID || 'NOT_SET')}`); const customer = await getOrCreateCustomer(user, email); - logger.debug(`Customer: ${customer?.customerId || 'unknown'} (${customer?.email || email})`); + LogEngine.debug(`Customer: ${customer?.customerId || 'unknown'} (${customer?.email || email})`); const requestPayload = { type: 'email', @@ -132,8 +132,8 @@ export async function createTicket(user: User, title: string, issue: string, ema }, }; - logger.info(`POST https://api.unthread.io/api/conversations`); - logger.debug(`Payload: ${JSON.stringify(requestPayload)}`); + LogEngine.info(`POST https://api.unthread.io/api/conversations`); + LogEngine.debug(`Payload: ${JSON.stringify(requestPayload)}`); const response = await fetch('https://api.unthread.io/api/conversations', { method: 'POST', @@ -144,29 +144,29 @@ export async function createTicket(user: User, title: string, issue: string, ema body: JSON.stringify(requestPayload), }); - logger.debug(`Response status: ${response.status}`); + LogEngine.debug(`Response status: ${response.status}`); if (!response.ok) { const errorText = await response.text(); - logger.error(`Failed to create ticket: ${response.status} - ${errorText}`); + LogEngine.error(`Failed to create ticket: ${response.status} - ${errorText}`); throw new Error(`Failed to create ticket: ${response.status}`); } const data = await response.json(); - logger.info(`Ticket created successfully:`, data); + LogEngine.info(`Ticket created successfully:`, data); // Validate required fields in response if (!data.id) { - logger.error(`Ticket response missing required 'id' field:`, data); + LogEngine.error(`Ticket response missing required 'id' field:`, data); throw new Error('Ticket was created but response is missing required fields'); } if (!data.friendlyId) { - logger.error(`Ticket response missing required 'friendlyId' field:`, data); + LogEngine.error(`Ticket response missing required 'friendlyId' field:`, data); throw new Error('Ticket was created but friendlyId is missing'); } - logger.info(`Created ticket ${data.friendlyId} (${data.id}) for user ${user.tag}`); + LogEngine.info(`Created ticket ${data.friendlyId} (${data.id}) for user ${user.tag}`); return data as UnthreadTicket; } @@ -185,7 +185,7 @@ export async function bindTicketWithThread(unthreadTicketId: string, discordThre await setKey(`ticket:discord:${discordThreadId}`, { unthreadTicketId, discordThreadId }); await setKey(`ticket:unthread:${unthreadTicketId}`, { unthreadTicketId, discordThreadId }); - logger.info(`Bound Discord thread ${discordThreadId} with Unthread ticket ${unthreadTicketId}`); + LogEngine.info(`Bound Discord thread ${discordThreadId} with Unthread ticket ${unthreadTicketId}`); } /** @@ -222,8 +222,8 @@ export async function getTicketByUnthreadTicketId(unthreadTicketId: string): Pro export async function handleWebhookEvent(payload: WebhookPayload): Promise { const { event, data } = payload; - logger.info(`Processing webhook event: ${event}`); - logger.debug(`Event data:`, data); + LogEngine.info(`Processing webhook event: ${event}`); + LogEngine.debug(`Event data:`, data); try { switch (event) { @@ -234,13 +234,13 @@ export async function handleWebhookEvent(payload: WebhookPayload): Promise await handleStatusUpdated(data); break; case 'conversation.created': - logger.debug('Conversation created event received - no action needed for Discord integration'); + LogEngine.debug('Conversation created event received - no action needed for Discord integration'); break; default: - logger.debug(`Unhandled webhook event type: ${event}`); + LogEngine.debug(`Unhandled webhook event type: ${event}`); } } catch (error: any) { - logger.error(`Error processing webhook event ${event}:`, error); + LogEngine.error(`Error processing webhook event ${event}:`, error); throw error; } } @@ -256,7 +256,7 @@ async function handleMessageCreated(data: any): Promise { const message = data.message; if (!conversationId || !message) { - logger.warn('Message created event missing required data'); + LogEngine.warn('Message created event missing required data'); return; } @@ -272,7 +272,7 @@ async function handleMessageCreated(data: any): Promise { ); if (!discordThread) { - logger.warn(`No Discord thread found for conversation ${conversationId}`); + LogEngine.warn(`No Discord thread found for conversation ${conversationId}`); return; } @@ -283,13 +283,13 @@ async function handleMessageCreated(data: any): Promise { // Skip duplicate messages if (await isDuplicateMessage(discordThread.id, messageContent)) { - logger.debug(`Skipping duplicate message in thread ${discordThread.id}`); + LogEngine.debug(`Skipping duplicate message in thread ${discordThread.id}`); return; } // Skip messages that contain Discord attachments if (containsDiscordAttachments(messageContent)) { - logger.debug(`Skipping message with Discord attachments in thread ${discordThread.id}`); + LogEngine.debug(`Skipping message with Discord attachments in thread ${discordThread.id}`); return; } @@ -306,13 +306,13 @@ async function handleMessageCreated(data: any): Promise { .setTimestamp(new Date(message.createdAt)); await discordThread.send({ embeds: [embed] }); - logger.info(`Forwarded message from Unthread to Discord thread ${discordThread.id}`); + LogEngine.info(`Forwarded message from Unthread to Discord thread ${discordThread.id}`); } } catch (error: any) { if (error.message.includes('No Discord thread found')) { - logger.warn(`Thread mapping not found for conversation ${conversationId} - this is normal for conversations not created via Discord`); + LogEngine.warn(`Thread mapping not found for conversation ${conversationId} - this is normal for conversations not created via Discord`); } else { - logger.error(`Error handling message created event for conversation ${conversationId}:`, error); + LogEngine.error(`Error handling message created event for conversation ${conversationId}:`, error); } } } @@ -327,7 +327,7 @@ async function handleStatusUpdated(data: any): Promise { const conversation = data.conversation; if (!conversation) { - logger.warn('Status updated event missing conversation data'); + LogEngine.warn('Status updated event missing conversation data'); return; } @@ -338,7 +338,7 @@ async function handleStatusUpdated(data: any): Promise { ); if (!discordThread) { - logger.debug(`No Discord thread found for conversation ${conversation.id}`); + LogEngine.debug(`No Discord thread found for conversation ${conversation.id}`); return; } @@ -358,22 +358,22 @@ async function handleStatusUpdated(data: any): Promise { .setTimestamp(); await discordThread.send({ embeds: [embed] }); - logger.info(`Updated status for ticket ${conversation.friendlyId} in Discord thread ${discordThread.id}`); + LogEngine.info(`Updated status for ticket ${conversation.friendlyId} in Discord thread ${discordThread.id}`); // Close Discord thread if ticket is closed/resolved if (conversation.status === 'closed' || conversation.status === 'resolved') { try { await discordThread.setArchived(true); - logger.info(`Archived Discord thread ${discordThread.id} for ${conversation.status} ticket`); + LogEngine.info(`Archived Discord thread ${discordThread.id} for ${conversation.status} ticket`); } catch (error: any) { - logger.warn(`Failed to archive Discord thread ${discordThread.id}:`, error.message); + LogEngine.warn(`Failed to archive Discord thread ${discordThread.id}:`, error.message); } } } catch (error: any) { if (error.message.includes('No Discord thread found')) { - logger.debug(`Thread mapping not found for conversation ${conversation.id} - this is normal for conversations not created via Discord`); + LogEngine.debug(`Thread mapping not found for conversation ${conversation.id} - this is normal for conversations not created via Discord`); } else { - logger.error(`Error handling status update for conversation ${conversation.id}:`, error); + LogEngine.error(`Error handling status update for conversation ${conversation.id}:`, error); } } } @@ -406,7 +406,7 @@ export async function sendMessageToUnthread( }, }; - logger.debug(`Sending message to Unthread conversation ${conversationId}:`, requestData); + LogEngine.debug(`Sending message to Unthread conversation ${conversationId}:`, requestData); const response = await fetch(`https://api.unthread.io/api/conversations/${conversationId}/messages`, { method: 'POST', @@ -419,11 +419,11 @@ export async function sendMessageToUnthread( if (!response.ok) { const errorText = await response.text(); - logger.error(`Failed to send message to Unthread: ${response.status} - ${errorText}`); + LogEngine.error(`Failed to send message to Unthread: ${response.status} - ${errorText}`); throw new Error(`Failed to send message to Unthread: ${response.status}`); } const responseData = await response.json(); - logger.debug(`Message sent to Unthread successfully:`, responseData); + LogEngine.debug(`Message sent to Unthread successfully:`, responseData); return responseData; } \ No newline at end of file diff --git a/src/services/webhook.ts b/src/services/webhook.ts index 3501c6e..55c02ac 100644 --- a/src/services/webhook.ts +++ b/src/services/webhook.ts @@ -15,7 +15,7 @@ import { Request, Response } from 'express'; import { createHmac } from 'crypto'; import { WebhookPayload } from '../types/unthread'; -import logger from '../utils/logger'; +import { LogEngine } from '../config/logger'; // @ts-ignore - JavaScript module without type declarations import { handleWebhookEvent as unthreadWebhookHandler } from './unthread'; @@ -52,7 +52,7 @@ type WebhookEventPayload = WebhookPayload | UrlVerificationPayload; */ function verifySignature(req: WebhookRequest): boolean { if (!SIGNING_SECRET) { - logger.error('UNTHREAD_WEBHOOK_SECRET not configured'); + LogEngine.error('UNTHREAD_WEBHOOK_SECRET not configured'); return false; } @@ -81,10 +81,10 @@ function webhookHandler(req: Request, res: Response): void { // Cast to WebhookRequest for access to rawBody const webhookReq = req as WebhookRequest; - logger.debug('Webhook received:', webhookReq.rawBody); + LogEngine.debug('Webhook received:', webhookReq.rawBody); if (!verifySignature(webhookReq)) { - logger.error('Signature verification failed.'); + LogEngine.error('Signature verification failed.'); res.sendStatus(403); return; } @@ -94,7 +94,7 @@ function webhookHandler(req: Request, res: Response): void { // Respond to URL verification events if (event === 'url_verification') { - logger.info('URL verification webhook received'); + LogEngine.info('URL verification webhook received'); res.sendStatus(200); return; } @@ -102,10 +102,10 @@ function webhookHandler(req: Request, res: Response): void { // Process other webhook events coming from Unthread try { unthreadWebhookHandler(body as WebhookPayload); - logger.debug(`Successfully processed webhook event: ${event}`); + LogEngine.debug(`Successfully processed webhook event: ${event}`); } catch (error) { - logger.error('Error processing webhook event:', error); + LogEngine.error('Error processing webhook event:', error); // Still return 200 to prevent webhook retries for application errors } diff --git a/src/types/discord.ts b/src/types/discord.ts index b34509a..bd480ca 100644 --- a/src/types/discord.ts +++ b/src/types/discord.ts @@ -43,16 +43,6 @@ export interface BotConfig { PORT?: string; } -/** - * Logger interface for type safety - */ -export interface Logger { - debug: (...args: any[]) => void; - info: (...args: any[]) => void; - warn: (...args: any[]) => void; - error: (...args: any[]) => void; -} - /** * Cache key-value operations interface */ diff --git a/src/utils/channelUtils.ts b/src/utils/channelUtils.ts index 9bb8ee3..81fd345 100644 --- a/src/utils/channelUtils.ts +++ b/src/utils/channelUtils.ts @@ -7,7 +7,7 @@ * @module utils/channelUtils */ -import logger from './logger'; +import { LogEngine } from '../config/logger'; import { ChannelType } from 'discord.js'; import '../types/global'; @@ -29,20 +29,20 @@ interface GlobalDiscordClient { async function isForumChannel(channelId: string): Promise { try { if (!global.discordClient) { - logger.warn('Discord client not available for channel type validation'); + LogEngine.warn('Discord client not available for channel type validation'); return false; } const channel = await global.discordClient.channels.fetch(channelId); if (!channel) { - logger.warn(`Channel ${channelId} not found`); + LogEngine.warn(`Channel ${channelId} not found`); return false; } return channel.type === ChannelType.GuildForum; } catch (error) { - logger.error(`Error checking channel type for ${channelId}:`, error); + LogEngine.error(`Error checking channel type for ${channelId}:`, error); return false; } } @@ -66,10 +66,10 @@ async function validateForumChannelIds(forumChannelIds: string): Promise { cachedForumChannelIds = await validateForumChannelIds(forumChannelIds); lastValidationTime = now; - logger.info(`Validated ${cachedForumChannelIds.length} forum channels from FORUM_CHANNEL_IDS`); + LogEngine.info(`Validated ${cachedForumChannelIds.length} forum channels from FORUM_CHANNEL_IDS`); return cachedForumChannelIds; } diff --git a/src/utils/customerUtils.ts b/src/utils/customerUtils.ts index 17fdaf3..679f45f 100644 --- a/src/utils/customerUtils.ts +++ b/src/utils/customerUtils.ts @@ -10,7 +10,7 @@ */ import { setKey, getKey } from './memory'; -import * as logger from './logger'; +import { LogEngine } from '../config/logger'; import { User } from 'discord.js'; interface Customer { @@ -84,12 +84,12 @@ export async function getOrCreateCustomer(user: User, email: string = ''): Promi let customer = await getKey(key) as Customer | null; if (customer) { - logger.debug(`Found cached customer for Discord user ${user.id}`); + LogEngine.debug(`Found cached customer for Discord user ${user.id}`); return customer; } // Customer not found in cache, create a new one - logger.debug(`Creating new customer record for Discord user ${user.id}`); + LogEngine.debug(`Creating new customer record for Discord user ${user.id}`); const customerId = await createCustomerInUnthread(user); // Construct customer object with both Discord and Unthread identifiers @@ -103,7 +103,7 @@ export async function getOrCreateCustomer(user: User, email: string = ''): Promi // Store customer in cache for future lookups await setKey(key, customer); - logger.info(`Created new customer record for ${user.username} (${user.id})`); + LogEngine.info(`Created new customer record for ${user.username} (${user.id})`); return customer; } @@ -144,6 +144,6 @@ export async function updateCustomer(customer: Customer): Promise { const key = `customer:${customer.discordId}`; await setKey(key, customer); - logger.debug(`Updated customer record for ${customer.discordUsername} (${customer.discordId})`); + LogEngine.debug(`Updated customer record for ${customer.discordUsername} (${customer.discordId})`); return customer; } \ No newline at end of file diff --git a/src/utils/database.ts b/src/utils/database.ts index e437c3e..6fbc0ac 100644 --- a/src/utils/database.ts +++ b/src/utils/database.ts @@ -18,7 +18,7 @@ * @module utils/database */ import { createKeyv } from '@keyv/redis'; -import * as logger from './logger'; +import { LogEngine } from '../config/logger'; /** * Redis Keyv Instance @@ -48,12 +48,12 @@ async function testRedisConnection(): Promise { const retrievedValue = await keyv.get(testKey); if (retrievedValue === testValue) { - logger.info('Successfully connected to Redis'); + LogEngine.info('Successfully connected to Redis'); } else { - logger.warn('Redis connection test failed: value mismatch'); + LogEngine.warn('Redis connection test failed: value mismatch'); } } catch (error: any) { - logger.error('Redis connection error:', error.message); + LogEngine.error('Redis connection error:', error.message); } } @@ -67,7 +67,7 @@ testRedisConnection(); * This helps with monitoring Redis health and debugging issues. */ keyv.on('error', (error: Error) => { - logger.error('Redis error:', error.message); + LogEngine.error('Redis error:', error.message); }); export default keyv; \ No newline at end of file diff --git a/src/utils/logger.ts b/src/utils/logger.ts deleted file mode 100644 index 275880f..0000000 --- a/src/utils/logger.ts +++ /dev/null @@ -1,144 +0,0 @@ -/** - * Logger Module - * - * A clean logging utility that integrates with @wgtechlabs/log-engine. - * Provides different logging levels with clean output - no emojis, only error type and local time. - * - * Usage: - * import logger from './utils/logger'; - * logger.debug('Detailed information for debugging'); - * logger.info('Important application events'); - * logger.warn('Warning conditions'); - * logger.error('Error conditions'); - * - * Configuration: - * Set DEBUG_MODE=true in your .env file to enable debug logs. - * Debug logs are hidden by default in production environments. - * - * @module utils/logger - */ - -import { Logger } from '../types/discord'; - -// Import LogEngine and LogMode from @wgtechlabs/log-engine -// Note: Using require() as this package may not have full TypeScript definitions -const { LogEngine, LogMode } = require('@wgtechlabs/log-engine') as any; - -// Configure LogEngine based on environment variables -// This controls the logging level and output behavior -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; - -/** - * Gets a formatted local timestamp string for the current time - * Format: HH:MM:SS - * - * @returns Formatted local time - */ -function getLocalTime(): string { - const now = new Date(); - const hours = String(now.getHours()).padStart(2, '0'); - const minutes = String(now.getMinutes()).padStart(2, '0'); - const seconds = String(now.getSeconds()).padStart(2, '0'); - - return `${hours}:${minutes}:${seconds}`; -} - -/** - * Custom output handler that shows only error type and local time - * Removes ISO timestamp and follows clean, minimal format - * - * @param level - Log level (DEBUG, INFO, WARN, ERROR) - * @param message - Log message (may contain ANSI codes and timestamps) - * @param data - Additional data (optional) - */ -function customOutputHandler(level: string, message: string, data?: any): void { - const localTime = getLocalTime(); - const levelUpper = level.toUpperCase(); - - // Remove ANSI color codes and extract the actual message content - let cleanMessage = message.replace(/\u001b\[[0-9;]*m/g, ''); - - // Extract just the message text after the formatted prefixes - // Pattern: [timestamp][time][LEVEL]: actual message - const messageMatch = cleanMessage.match(/^.*?\[.*?\].*?\[.*?\].*?\[.*?\]:\s*(.*)$/); - if (messageMatch && messageMatch[1]) { - cleanMessage = messageMatch[1].trim(); - - // Remove any JSON data that might be appended to the message - cleanMessage = cleanMessage.replace(/\s*\{.*\}$/, ''); - } - - if (data !== undefined && data !== null) { - console.log(`[${localTime}][${levelUpper}]: ${cleanMessage}`, data); - } else { - console.log(`[${localTime}][${levelUpper}]: ${cleanMessage}`); - } -} - -// Configure LogEngine with custom output handler to meet requirements -LogEngine.configure({ - mode: logMode, - outputHandler: customOutputHandler, - suppressConsoleOutput: true // Disable default console output to use our custom handler -}); - -/** - * Log debug information (only visible when DEBUG_MODE=true) - * Used for detailed troubleshooting information that is too verbose for regular operation - * - * @param args - Arguments to log (strings, objects, etc.) - */ -function debug(...args: any[]): void { - LogEngine.debug(...args); -} - -/** - * Log informational messages (always visible) - * Used for general operational information about system activities - * - * @param args - Arguments to log (strings, objects, etc.) - */ -function info(...args: any[]): void { - LogEngine.info(...args); -} - -/** - * Log warning messages (always visible) - * Used for warning conditions that don't prevent the application from working - * but indicate potential problems or unexpected behavior - * - * @param args - Arguments to log (strings, objects, etc.) - */ -function warn(...args: any[]): void { - LogEngine.warn(...args); -} - -/** - * Log error messages (always visible) - * Used for error conditions that prevent normal operation but don't crash the application - * - * @param args - Arguments to log (strings, objects, etc.) - */ -function error(...args: any[]): void { - LogEngine.error(...args); -} - -/** - * Logger instance implementing the Logger interface - */ -const logger: Logger = { - debug, - info, - warn, - error, -}; - -// Export the logging functions for use in other modules -export default logger; - -// Export individual functions for named imports -export { debug, info, warn, error }; \ No newline at end of file diff --git a/src/utils/messageUtils.ts b/src/utils/messageUtils.ts index 65ad5fa..7fa622e 100644 --- a/src/utils/messageUtils.ts +++ b/src/utils/messageUtils.ts @@ -11,7 +11,7 @@ */ import { MessageProcessingResult } from '../types/utils'; -import logger from './logger'; +import { LogEngine } from '../config/logger'; /** * Represents a message for processing @@ -59,7 +59,7 @@ function isDuplicateMessage(messages: ProcessableMessage[], newContent: string): // Check for exact content match const exactDuplicate = messages.some(msg => msg.content === trimmedContent); if (exactDuplicate) { - logger.debug('Exact duplicate message detected'); + LogEngine.debug('Exact duplicate message detected'); return true; } @@ -87,7 +87,7 @@ function isDuplicateMessage(messages: ProcessableMessage[], newContent: string): }); if (contentDuplicate) { - logger.debug('Content duplicate message detected (fuzzy match)'); + LogEngine.debug('Content duplicate message detected (fuzzy match)'); return true; } } diff --git a/src/utils/retry.ts b/src/utils/retry.ts index 6a57bd5..03bfc51 100644 --- a/src/utils/retry.ts +++ b/src/utils/retry.ts @@ -8,7 +8,7 @@ */ import { RetryConfig } from '../types/utils'; -import logger from './logger'; +import { LogEngine } from '../config/logger'; /** * Retry operation configuration options @@ -53,26 +53,26 @@ async function withRetry( while (attempt < maxAttempts) { try { - logger.info(`Attempt ${attempt + 1}/${maxAttempts} for ${operationName}...`); + LogEngine.info(`Attempt ${attempt + 1}/${maxAttempts} for ${operationName}...`); // Execute the operation const result = await operation(); // If we get here, the operation succeeded if (attempt > 0) { - logger.info(`${operationName} succeeded on attempt ${attempt + 1}`); + LogEngine.info(`${operationName} succeeded on attempt ${attempt + 1}`); } return result; } catch (error) { lastError = error as Error; - logger.debug(`Attempt ${attempt + 1} failed: ${lastError.message}`); + LogEngine.debug(`Attempt ${attempt + 1} failed: ${lastError.message}`); if (attempt < maxAttempts - 1) { // Calculate delay with linear backoff const delayMs = baseDelayMs * (attempt + 1); - logger.info(`Retrying in ${delayMs / 1000}s... (attempt ${attempt + 1}/${maxAttempts})`); + LogEngine.info(`Retrying in ${delayMs / 1000}s... (attempt ${attempt + 1}/${maxAttempts})`); await new Promise(resolve => setTimeout(resolve, delayMs)); } } @@ -81,7 +81,7 @@ async function withRetry( } // If we get here, all attempts failed - logger.error(`${operationName} failed after ${maxAttempts} attempts. Last error: ${lastError?.message}`); + LogEngine.error(`${operationName} failed after ${maxAttempts} attempts. Last error: ${lastError?.message}`); throw new Error(`${operationName} failed after ${maxAttempts} attempts: ${lastError?.message || 'Unknown error'}`); } diff --git a/src/utils/threadUtils.ts b/src/utils/threadUtils.ts index 62ef9e7..76ff105 100644 --- a/src/utils/threadUtils.ts +++ b/src/utils/threadUtils.ts @@ -9,7 +9,7 @@ * when performing common thread-related operations across the application. */ -import * as logger from './logger'; +import { LogEngine } from '../config/logger'; interface TicketMapping { discordThreadId: string; @@ -77,7 +77,7 @@ export async function findDiscordThreadByTicketIdWithRetry( if (!isLastAttempt && withinRetryWindow && isMappingError) { const delay = baseDelayMs * attempt; // Progressive delay: 1s, 2s, 3s - logger.debug(`Mapping not found for ticket ${unthreadTicketId}, attempt ${attempt}/${maxAttempts}. Retrying in ${delay}ms... (${timeSinceStart}ms since start)`); + LogEngine.debug(`Mapping not found for ticket ${unthreadTicketId}, attempt ${attempt}/${maxAttempts}. Retrying in ${delay}ms... (${timeSinceStart}ms since start)`); await new Promise(resolve => setTimeout(resolve, delay)); continue; @@ -102,9 +102,9 @@ export async function findDiscordThreadByTicketIdWithRetry( // Log with enhanced context if (enhancedError.context.likelyRaceCondition) { - logger.warn(`Potential race condition detected for ticket ${unthreadTicketId}: mapping not found after ${maxAttempts} attempts over ${totalTime}ms`); + LogEngine.warn(`Potential race condition detected for ticket ${unthreadTicketId}: mapping not found after ${maxAttempts} attempts over ${totalTime}ms`); } else { - logger.error(`Ticket mapping genuinely missing for ${unthreadTicketId} (checked after ${totalTime}ms)`); + LogEngine.error(`Ticket mapping genuinely missing for ${unthreadTicketId} (checked after ${totalTime}ms)`); } throw enhancedError; @@ -139,7 +139,7 @@ export async function findDiscordThreadByTicketId( const ticketMapping = await lookupFunction(unthreadTicketId); if (!ticketMapping) { const error = new Error(`No Discord thread found for Unthread ticket ${unthreadTicketId}`); - logger.error(error.message); + LogEngine.error(error.message); throw error; } @@ -148,18 +148,18 @@ export async function findDiscordThreadByTicketId( const discordThread = await (global as any).discordClient.channels.fetch(ticketMapping.discordThreadId); if (!discordThread) { const error = new Error(`Discord thread with ID ${ticketMapping.discordThreadId} not found.`); - logger.error(error.message); + LogEngine.error(error.message); throw error; } - logger.debug(`Found Discord thread: ${discordThread.id}`); + LogEngine.debug(`Found Discord thread: ${discordThread.id}`); return { ticketMapping, discordThread }; } catch (error: any) { - logger.error(`Error fetching Discord thread for ticket ${unthreadTicketId}: ${error.message}`); + LogEngine.error(`Error fetching Discord thread for ticket ${unthreadTicketId}: ${error.message}`); throw error; } } \ No newline at end of file