From 827253a92b2f9562a0d249a59fdc62531111556a Mon Sep 17 00:00:00 2001 From: Saksham Date: Sun, 31 May 2026 19:42:54 +0530 Subject: [PATCH 1/2] feat: implement structured logging system for prefix & slash commands (#30) --- bot.py | 30 +++++++++++++++++++------ utils/logger.py | 60 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 7 deletions(-) create mode 100644 utils/logger.py diff --git a/bot.py b/bot.py index dbe42e9..8d7f4a6 100644 --- a/bot.py +++ b/bot.py @@ -19,12 +19,9 @@ # Load environment variables load_dotenv() -# Configure logging -logging.basicConfig( - level=getattr(logging, os.getenv("LOG_LEVEL", "INFO")), - format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", - handlers=[logging.FileHandler("bot.log"), logging.StreamHandler()], -) +from utils.logger import setup_logging + +setup_logging() logger = logging.getLogger(__name__) @@ -65,7 +62,8 @@ def __init__(self, config: Config): async def setup_hook(self) -> None: """Setup hook called before the bot starts.""" - # Initialize CodeBuddy database + self.tree.on_app_command_completion = self.on_app_command_completion + try: from utils.codebuddy_database import init_db @@ -176,6 +174,18 @@ async def on_ready(self): activity=discord.Game(name="?helpmenu /help | Made by YC45") ) + async def on_command_completion(self, ctx: commands.Context): + """Handle prefix command completion.""" + from utils.logger import log_command_usage + log_command_usage(ctx, command_type="PREFIX", success=True) + + async def on_app_command_completion( + self, interaction: discord.Interaction, command: app_commands.Command + ): + """Handle slash command completion.""" + from utils.logger import log_command_usage + log_command_usage(interaction, command_type="SLASH", success=True) + async def on_command_error( self, ctx: commands.Context, error: commands.CommandError ): @@ -184,6 +194,9 @@ async def on_command_error( if isinstance(error, commands.CommandNotFound): return + from utils.logger import log_command_usage + log_command_usage(ctx, command_type="PREFIX", success=False, error=error) + async def _safe_ctx_send(message: str) -> None: """Send a message without crashing on expired slash interactions. @@ -235,6 +248,9 @@ async def on_app_command_error( self, interaction: discord.Interaction, error: app_commands.AppCommandError ): """Handle slash command errors.""" + from utils.logger import log_command_usage + log_command_usage(interaction, command_type="SLASH", success=False, error=error) + # Silence unknown slash/app commands. # `app_commands` doesn't expose a stable CommandNotFound across versions, so be conservative. diff --git a/utils/logger.py b/utils/logger.py new file mode 100644 index 0000000..bc781c9 --- /dev/null +++ b/utils/logger.py @@ -0,0 +1,60 @@ +import logging +import os +import discord +from discord.ext import commands + +logger = logging.getLogger("EigenBot") + +def setup_logging(): + log_level_str = os.getenv("LOG_LEVEL", "INFO") + log_level = getattr(logging, log_level_str.upper(), logging.INFO) + + file_handler = logging.FileHandler("bot.log", encoding="utf-8") + stream_handler = logging.StreamHandler() + + formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") + file_handler.setFormatter(formatter) + stream_handler.setFormatter(formatter) + + root_logger = logging.getLogger() + root_logger.setLevel(log_level) + root_logger.handlers.clear() + root_logger.addHandler(file_handler) + root_logger.addHandler(stream_handler) + + logger.info("Logging system configured successfully.") + +def log_command_usage(ctx_or_interaction, command_type: str, success: bool, error: Exception = None): + """Formats and logs command usage consistently.""" + try: + if isinstance(ctx_or_interaction, commands.Context): + user_id = ctx_or_interaction.author.id + username = ctx_or_interaction.author.name + if ctx_or_interaction.command: + command_name = ctx_or_interaction.command.qualified_name + else: + command_name = "Unknown" + elif isinstance(ctx_or_interaction, discord.Interaction): + user_id = ctx_or_interaction.user.id + username = ctx_or_interaction.user.name + if ctx_or_interaction.command: + command_name = ctx_or_interaction.command.qualified_name + elif ctx_or_interaction.data: + command_name = ctx_or_interaction.data.get("name", "Unknown") + else: + command_name = "Unknown" + else: + return + + status = "SUCCESS" if success else f"FAILED ({type(error).__name__}: {error})" + message = ( + f"[{command_type}] User: {username} ({user_id}) | " + f"Command: {command_name} | Status: {status}" + ) + + if success: + logger.info(message) + else: + logger.error(message) + except Exception as log_err: + logger.error(f"Failed to write command log: {log_err}") From f0ec1683053ca9ca34074796a50ff1a9ff7f4274 Mon Sep 17 00:00:00 2001 From: Saksham Date: Mon, 1 Jun 2026 19:05:39 +0530 Subject: [PATCH 2/2] fix: place bot.log inside logs/ directory for Docker persistence --- utils/logger.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/utils/logger.py b/utils/logger.py index bc781c9..8447fdf 100644 --- a/utils/logger.py +++ b/utils/logger.py @@ -9,7 +9,8 @@ def setup_logging(): log_level_str = os.getenv("LOG_LEVEL", "INFO") log_level = getattr(logging, log_level_str.upper(), logging.INFO) - file_handler = logging.FileHandler("bot.log", encoding="utf-8") + os.makedirs("logs", exist_ok=True) + file_handler = logging.FileHandler(os.path.join("logs", "bot.log"), encoding="utf-8") stream_handler = logging.StreamHandler() formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")