From 288c0eb08ff1058d6f48a682e05f994b32cd2c67 Mon Sep 17 00:00:00 2001 From: JT2M0L3Y Date: Wed, 21 Oct 2026 19:31:02 -0700 Subject: [PATCH] pass root logger, don't init new ones --- README.md | 9 ++++++--- src/commands/deleteModel.ts | 6 ++---- src/commands/pullModel.ts | 6 ++---- src/commands/shutoff.ts | 6 ++---- src/commands/summary.ts | 8 +++----- src/commands/switchModel.ts | 6 ++---- src/components/logger.ts | 2 +- src/events/interactionCreate.ts | 6 +++--- src/events/messageCreate.ts | 24 ++++++++++++------------ src/events/ready.ts | 4 ++-- src/events/threadDelete.ts | 12 ++++++------ src/utils/commands.ts | 5 +++-- src/utils/events.ts | 10 +++++----- src/utils/handlers/chatHistoryHandler.ts | 4 ++-- src/utils/handlers/configHandler.ts | 2 +- src/utils/messageNormal.ts | 8 ++++---- 16 files changed, 56 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index 95dd139..4dcc41d 100644 --- a/README.md +++ b/README.md @@ -31,12 +31,15 @@ entry. Set `LOG_LEVEL` to configure the minimum level (defaults to `info`). ```ts import { createLogger } from './components/index.js' -const logger = createLogger({ component: 'client' }) -logger.info({ username }, 'Client started') +const logger = createLogger({ component: 'request', requestId }) +logger.info('Request started') ``` Log entries are JSON and include the structured fields provided to the log -method, along with `component` and (outside production) `caller`. +method, along with `component` and (outside production) `caller`. The root Pino +instance is shared; create one child when entering a context, then pass that +same child logger through the context's work. Create another child only when +you need to add more scoped bindings; child creation does not mutate its parent. ## Environment Setup * Clone this repo using `git clone https://github.com/kevinthedang/discord-ollama.git` or just use [GitHub Desktop](https://desktop.github.com/) to clone the repo. diff --git a/src/commands/deleteModel.ts b/src/commands/deleteModel.ts index 1fa4b76..71ac66a 100644 --- a/src/commands/deleteModel.ts +++ b/src/commands/deleteModel.ts @@ -2,9 +2,7 @@ import { ApplicationCommandOptionType, ChatInputCommandInteraction, Client, Comm import { UserCommand, SlashCommand } from '../utils/index.js' import { ollama } from '../client.js' import { ModelResponse } from 'ollama' -import { createLogger } from '../components/index.js' - -const logger = createLogger({ component: 'command', command: 'delete-model' }) +import type { Logger as PinoLogger } from 'pino' export const DeleteModel: SlashCommand = { name: 'delete-model', @@ -21,7 +19,7 @@ export const DeleteModel: SlashCommand = { ], // Delete Model locally stored - run: async (client: Client, interaction: ChatInputCommandInteraction) => { + run: async (client: Client, interaction: ChatInputCommandInteraction, logger: PinoLogger) => { // defer reply to avoid timeout await interaction.deferReply() const modelInput: string = interaction.options.getString('model-name') as string diff --git a/src/commands/pullModel.ts b/src/commands/pullModel.ts index f7b4da3..9c93788 100644 --- a/src/commands/pullModel.ts +++ b/src/commands/pullModel.ts @@ -2,9 +2,7 @@ import { ApplicationCommandOptionType, Client, ChatInputCommandInteraction, Mess import { ollama } from "../client.js" import { ModelResponse } from "ollama" import { UserCommand, SlashCommand } from "../utils/index.js" -import { createLogger } from "../components/index.js" - -const logger = createLogger({ component: 'command', command: 'pull-model' }) +import type { Logger as PinoLogger } from 'pino' export const PullModel: SlashCommand = { name: 'pull-model', @@ -21,7 +19,7 @@ export const PullModel: SlashCommand = { ], // Pull for model from Ollama library - run: async (client: Client, interaction: ChatInputCommandInteraction) => { + run: async (client: Client, interaction: ChatInputCommandInteraction, logger: PinoLogger) => { // defer reply to avoid timeout await interaction.deferReply() const modelInput: string = interaction.options.getString('model-to-pull') as string diff --git a/src/commands/shutoff.ts b/src/commands/shutoff.ts index a167fc1..5cebd80 100644 --- a/src/commands/shutoff.ts +++ b/src/commands/shutoff.ts @@ -1,15 +1,13 @@ import { Client, CommandInteraction, MessageFlags } from 'discord.js' import { AdminCommand, SlashCommand } from '../utils/index.js' -import { createLogger } from '../components/index.js' - -const logger = createLogger({ component: 'command', command: 'shutoff' }) +import type { Logger as PinoLogger } from 'pino' export const Shutoff: SlashCommand = { name: 'shutoff', description: 'shutdown the bot. You will need to manually bring it online again. Administrator Only.', // Query for message information and set the style - run: async (client: Client, interaction: CommandInteraction) => { + run: async (client: Client, interaction: CommandInteraction, logger: PinoLogger) => { // fetch channel and message const channel = await client.channels.fetch(interaction.channelId) if (!channel || !AdminCommand.includes(channel.type)) return diff --git a/src/commands/summary.ts b/src/commands/summary.ts index 2838b8e..d21cf75 100644 --- a/src/commands/summary.ts +++ b/src/commands/summary.ts @@ -1,16 +1,14 @@ import { Client, CommandInteraction, MessageFlags } from "discord.js"; import { accessChannelContext, SlashCommand, summarizeContextHistory, UserCommand, UserMessage } from "../utils/index.js"; -import { createLogger } from "../components/index.js" +import type { Logger as PinoLogger } from 'pino' import { ollama } from "../client.js" -const logger = createLogger({ component: 'command', command: 'summary' }) - export const Summary: SlashCommand = { name: 'summary', description: 'provides a summary of the chat history.', // Generate Summary from additional context - run: async (client: Client, interaction: CommandInteraction) => { + run: async (client: Client, interaction: CommandInteraction, logger: PinoLogger) => { // fetch channel const channel = await client.channels.fetch(interaction.channelId) if (!channel || !UserCommand.includes(channel.type)) return @@ -44,7 +42,7 @@ export const Summary: SlashCommand = { }) // todo: instead of a default of codellama, we can user default model somehow. Look into later. - const response: string = await summarizeContextHistory(ollama, "codellama", channelContext) + const response: string = await summarizeContextHistory(logger, ollama, "codellama", channelContext) logger.info({ response }, 'Generated channel summary') diff --git a/src/commands/switchModel.ts b/src/commands/switchModel.ts index fe29690..345a37d 100644 --- a/src/commands/switchModel.ts +++ b/src/commands/switchModel.ts @@ -2,9 +2,7 @@ import { ApplicationCommandOptionType, Client, ChatInputCommandInteraction } fro import { ollama } from "../client.js" import { ModelResponse } from "ollama" import { openConfig, UserCommand, SlashCommand } from "../utils/index.js" -import { createLogger } from "../components/index.js" - -const logger = createLogger({ component: 'command', command: 'switch-model' }) +import type { Logger as PinoLogger } from 'pino' export const SwitchModel: SlashCommand = { name: 'switch-model', @@ -21,7 +19,7 @@ export const SwitchModel: SlashCommand = { ], // Switch user preferred model if available in local library - run: async (client: Client, interaction: ChatInputCommandInteraction) => { + run: async (client: Client, interaction: ChatInputCommandInteraction, logger: PinoLogger) => { await interaction.deferReply() const modelInput: string = interaction.options.getString('model-to-use') as string diff --git a/src/components/logger.ts b/src/components/logger.ts index e608ff7..55debdb 100644 --- a/src/components/logger.ts +++ b/src/components/logger.ts @@ -2,7 +2,7 @@ import 'dotenv/config' import pino, { type Logger as PinoLogger } from 'pino' import { pinoCaller } from 'pino-caller' -const rootLogger = pino({ +export const rootLogger = pino({ level: process.env.LOG_LEVEL ?? 'info' }) diff --git a/src/events/interactionCreate.ts b/src/events/interactionCreate.ts index 5633b78..8ea3fb9 100644 --- a/src/events/interactionCreate.ts +++ b/src/events/interactionCreate.ts @@ -5,15 +5,15 @@ import commands from '../commands/index.js' * Interaction creation listener for the client * @param interaction the interaction received from the server */ -export default event(Events.InteractionCreate, async ({ log, client }, interaction) => { +export default event(Events.InteractionCreate, async ({ logger, client }, interaction) => { if (!interaction.isCommand() || !interaction.isChatInputCommand()) return - log.info({ command: interaction.commandName, user: interaction.user.tag }, 'Interaction received') + logger.info({ command: interaction.commandName, user: interaction.user.tag }, 'Interaction received') // ensure command exists, otherwise kill event const command = commands.find(command => command.name === interaction.commandName) if (!command) return // the command exists, execute it - command.run(client, interaction) + command.run(client, interaction, logger) }) \ No newline at end of file diff --git a/src/events/messageCreate.ts b/src/events/messageCreate.ts index 05eca76..d8e48cf 100644 --- a/src/events/messageCreate.ts +++ b/src/events/messageCreate.ts @@ -11,10 +11,10 @@ import { * * @param message the message received from the channel */ -export default event(Events.MessageCreate, async ({ log, msgHist, channelHistory, ollama, client, defaultModel }, message) => { +export default event(Events.MessageCreate, async ({ logger, msgHist, channelHistory, ollama, client, defaultModel }, message) => { const clientId = client.user!!.id let cleanedMessage = clean(message.content, clientId) - log.info({ author: message.author.tag, channelId: message.channelId, message: cleanedMessage }, 'Received message') + logger.info({ author: message.author.tag, channelId: message.channelId, message: cleanedMessage }, 'Received message') // Do not respond if bot talks in the chat if (message.author.username === message.client.user.username) return @@ -25,7 +25,7 @@ export default event(Events.MessageCreate, async ({ log, msgHist, channelHistory if (channelInfo?.messages) resolve(channelInfo.messages) else { - log.info({ channelId: message.channelId }, 'Channel context does not exist; creating it') + logger.info({ channelId: message.channelId }, 'Channel context does not exist; creating it') resolve([]) } }) @@ -40,7 +40,7 @@ export default event(Events.MessageCreate, async ({ log, msgHist, channelHistory if (channelInfo?.messages) resolve(channelInfo.messages) else { - log.info({ channelId: message.channelId }, 'Channel context does not exist; creating it') + logger.info({ channelId: message.channelId }, 'Channel context does not exist; creating it') } }) }) @@ -108,7 +108,7 @@ export default event(Events.MessageCreate, async ({ log, msgHist, channelHistory } catch (error) { ++attempt if (attempt < maxRetries) { - log.warn({ attempt, delay }, 'Failed to retrieve server preferences; retrying') + logger.warn({ attempt, delay }, 'Failed to retrieve server preferences; retrying') await new Promise(ret => setTimeout(ret, delay)) } else throw new Error(`Could not retrieve Server Preferences, please try chatting again...`) @@ -132,11 +132,11 @@ export default event(Events.MessageCreate, async ({ log, msgHist, channelHistory // check if there is a set capacity in config else if (typeof config.options['modify-capacity'] !== 'number') - log.info({ capacity: msgHist.capacity }, 'Capacity is undefined; using default') + logger.info({ capacity: msgHist.capacity }, 'Capacity is undefined; using default') else if (config.options['modify-capacity'] === msgHist.capacity) - log.info({ capacity: msgHist.capacity }, 'Configured capacity matches current capacity') + logger.info({ capacity: msgHist.capacity }, 'Configured capacity matches current capacity') else { - log.info({ capacity: config.options['modify-capacity'] }, 'Updating context capacity') + logger.info({ capacity: config.options['modify-capacity'] }, 'Updating context capacity') msgHist.capacity = config.options['modify-capacity'] } @@ -153,7 +153,7 @@ export default event(Events.MessageCreate, async ({ log, msgHist, channelHistory } catch (error) { ++attempt if (attempt < maxRetries) { - log.warn({ attempt, delay }, 'Failed to retrieve user preferences; retrying') + logger.warn({ attempt, delay }, 'Failed to retrieve user preferences; retrying') await new Promise(ret => setTimeout(ret, delay)) } else throw new Error(`Could not retrieve User Preferences, please try chatting again...`) @@ -167,7 +167,7 @@ export default event(Events.MessageCreate, async ({ log, msgHist, channelHistory if (channelInfo?.messages) resolve(channelInfo.messages) else { - log.info({ channelId: message.channelId, username: message.author.username }, 'User channel history does not exist; creating it') + logger.info({ channelId: message.channelId, username: message.author.username }, 'User channel history does not exist; creating it') resolve([]) } }) @@ -183,7 +183,7 @@ export default event(Events.MessageCreate, async ({ log, msgHist, channelHistory if (channelInfo?.messages) resolve(channelInfo.messages) else { - log.info({ channelId: message.channelId, username: message.author.username }, 'User channel history does not exist; creating it') + logger.info({ channelId: message.channelId, username: message.author.username }, 'User channel history does not exist; creating it') reject(new Error(`Failed to find ${message.author.username}'s history. Try chatting again.`)) } }) @@ -209,7 +209,7 @@ export default event(Events.MessageCreate, async ({ log, msgHist, channelHistory }) // response string for ollama to put its response - const response: string = await normalMessage(message, ollama, model, msgHist, shouldStream) + const response: string = await normalMessage(logger, message, ollama, model, msgHist, shouldStream) // If something bad happened, remove user query and stop if (response == undefined) { msgHist.pop(); return } diff --git a/src/events/ready.ts b/src/events/ready.ts index da895ac..96cb772 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -3,7 +3,7 @@ import { event, Events, registerCommands } from '../utils/index.js' import commands from '../commands/index.js' // Log when the bot successfully logs in and export it -export default event(Events.ClientReady, ({ log }, client) => { +export default event(Events.ClientReady, ({ logger }, client) => { // Register the commands associated with the bot upon loggin in registerCommands(client, commands) @@ -13,5 +13,5 @@ export default event(Events.ClientReady, ({ log }, client) => { type: ActivityType.Custom }) - log.info({ username: client.user.username }, 'Client ready') + logger.info({ username: client.user.username }, 'Client ready') }) \ No newline at end of file diff --git a/src/events/threadDelete.ts b/src/events/threadDelete.ts index 1c3e0a7..3beb985 100644 --- a/src/events/threadDelete.ts +++ b/src/events/threadDelete.ts @@ -5,16 +5,16 @@ import fs from 'fs' /** * Event to remove the associated .json file for a thread once deleted */ -export default event(Events.ThreadDelete, async ({ log }, thread: ThreadChannel) => { +export default event(Events.ThreadDelete, async ({ logger }, thread: ThreadChannel) => { // iterate through every guild member in the thread and delete their history, except the bot try { - log.info({ memberCount: thread.memberCount!! - 1 }, 'Deleting thread history') + logger.info({ memberCount: thread.memberCount!! - 1 }, 'Deleting thread history') const dirPath = 'data/' // read all files in data/ fs.readdir(dirPath, (error, files) => { if (error) { - log.error({ err: error, directory: dirPath }, 'Failed to read history directory') + logger.error({ err: error, directory: dirPath }, 'Failed to read history directory') return } @@ -28,13 +28,13 @@ export default event(Events.ThreadDelete, async ({ log }, thread: ThreadChannel) const filePath = dirPath + file fs.unlink(filePath, error => { if (error) - log.error({ err: error, file: filePath }, 'Failed to delete thread history file') + logger.error({ err: error, file: filePath }, 'Failed to delete thread history file') else - log.info({ file: filePath }, 'Deleted thread history file') + logger.info({ file: filePath }, 'Deleted thread history file') }) }) }) } catch (error) { - log.error({ err: error, threadId: thread.id }, 'Failed to delete user history files') + logger.error({ err: error, threadId: thread.id }, 'Failed to delete user history files') } }) \ No newline at end of file diff --git a/src/utils/commands.ts b/src/utils/commands.ts index 89ca2a9..9afa17a 100644 --- a/src/utils/commands.ts +++ b/src/utils/commands.ts @@ -1,5 +1,6 @@ -import { ChatInputCommandInteraction, ChatInputApplicationCommandData, Client, ApplicationCommandOption } from 'discord.js' +import { ChatInputCommandInteraction, ChatInputApplicationCommandData, Client } from 'discord.js' import { createLogger } from '../components/index.js' +import type { Logger as PinoLogger } from 'pino' const logger = createLogger({ component: 'commands' }) @@ -10,7 +11,7 @@ export interface SlashCommand extends ChatInputApplicationCommandData { run: ( client: Client, interaction: ChatInputCommandInteraction, - options?: ApplicationCommandOption[] + logger: PinoLogger ) => void } diff --git a/src/utils/events.ts b/src/utils/events.ts index ef3fc76..9705226 100644 --- a/src/utils/events.ts +++ b/src/utils/events.ts @@ -36,7 +36,7 @@ export type UserMessage = { // Event properties export interface EventProps { client: Client, - log: LogMethod, + logger: LogMethod, msgHist: Queue, channelHistory: Queue, ollama: Ollama, @@ -86,14 +86,14 @@ export function registerEvents( ): void { for (const { key, callback } of events) { client.on(key, (...args) => { - const log = createLogger({ component: 'event', event: key }) + const logger = createLogger({ component: 'event', event: key }) // Handle Errors, call callback, log errors as needed try { - void Promise.resolve(callback({ client, log, msgHist, channelHistory, ollama, defaultModel }, ...args)) - .catch(error => log.error({ err: error }, 'Uncaught event error')) + void Promise.resolve(callback({ client, logger, msgHist, channelHistory, ollama, defaultModel }, ...args)) + .catch(error => logger.error({ err: error }, 'Uncaught event error')) } catch (error) { - log.error({ err: error }, 'Uncaught event error') + logger.error({ err: error }, 'Uncaught event error') } }) } diff --git a/src/utils/handlers/chatHistoryHandler.ts b/src/utils/handlers/chatHistoryHandler.ts index 232e128..432bd8e 100644 --- a/src/utils/handlers/chatHistoryHandler.ts +++ b/src/utils/handlers/chatHistoryHandler.ts @@ -59,7 +59,7 @@ export async function clearChannelInfo(filename: string, channel: TextChannel, u return cleanedHistory } -export async function addToChannelContext(filename: string, channel : TextChannel | ThreadChannel, messages: UserMessage[] = []): Promise { +export async function addToChannelContext(filename: string, channel: TextChannel | ThreadChannel, messages: UserMessage[] = []): Promise { const fullFileName = `data/${filename}-context.json` if (fs.existsSync(fullFileName)) { fs.readFile(fullFileName, 'utf8', (error, data) => { @@ -115,7 +115,7 @@ export async function accessChannelContext(filename: string, callback: (config: * @param user the user's name * @param messages their messages */ -export async function openChannelInfo(this: any, filename: string, channel: TextChannel | ThreadChannel, user: string, messages: UserMessage[] = []): Promise { +export async function openChannelInfo(filename: string, channel: TextChannel | ThreadChannel, user: string, messages: UserMessage[] = []): Promise { const fullFileName = `data/${filename}-${user}.json` if (fs.existsSync(fullFileName)) { fs.readFile(fullFileName, 'utf8', (error, data) => { diff --git a/src/utils/handlers/configHandler.ts b/src/utils/handlers/configHandler.ts index 737a71a..b90ac59 100644 --- a/src/utils/handlers/configHandler.ts +++ b/src/utils/handlers/configHandler.ts @@ -13,7 +13,7 @@ const logger = createLogger({ component: 'config' }) * @param value new value to assign */ // add type of change (server, user) -export function openConfig(this: any, filename: string, key: string, value: any) { +export function openConfig(filename: string, key: string, value: any) { const fullFileName = `data/${filename}` // check if the file exists, if not then make the config file diff --git a/src/utils/messageNormal.ts b/src/utils/messageNormal.ts index 240a350..aca0588 100644 --- a/src/utils/messageNormal.ts +++ b/src/utils/messageNormal.ts @@ -1,9 +1,8 @@ import { Message, SendableChannels } from 'discord.js' import { ChatResponse, Ollama, AbortableAsyncIterator } from 'ollama' +import type { Logger as PinoLogger } from 'pino' import { ChatParams, UserMessage, streamResponse, blockResponse } from './index.js' -import { Queue, createLogger } from '../components/index.js' - -const logger = createLogger({ component: 'message' }) +import { Queue } from '../components/index.js' /** * Method to send replies as normal text on discord like any other user @@ -12,7 +11,7 @@ const logger = createLogger({ component: 'message' }) * @param msgHist message history between user and model */ export async function normalMessage( - this: any, + logger: PinoLogger, message: Message, ollama: Ollama, model: string, @@ -92,6 +91,7 @@ export async function normalMessage( } export async function summarizeContextHistory( + logger: PinoLogger, ollama: Ollama, model: string, msgHist: UserMessage[],