refactor(bot): bot utilities into separate modules

Moved logging, signal handling, and uptime calculation logic from bot.py into dedicated utils modules for better organization and reusability. Updated imports and usage in bot.py and utils/__init__.py accordingly.
This commit is contained in:
neoarz
2025-09-28 16:20:28 -04:00
parent 0a262c522e
commit 4948d2edf7
5 changed files with 96 additions and 73 deletions

76
bot.py
View File

@@ -15,7 +15,7 @@ from discord.ext.commands import Context
from dotenv import load_dotenv from dotenv import load_dotenv
from database import DatabaseManager from database import DatabaseManager
from utils.ascii_art import ascii from utils import ascii, setup_logger, get_uptime, setup_signal_handlers
load_dotenv() load_dotenv()
@@ -50,54 +50,7 @@ intents.messages = True
class LoggingFormatter(logging.Formatter): logger = setup_logger()
# Colors
black = "\x1b[30m"
red = "\x1b[31m"
green = "\x1b[32m"
yellow = "\x1b[33m"
blue = "\x1b[34m"
gray = "\x1b[38m"
# Styles
reset = "\x1b[0m"
bold = "\x1b[1m"
COLORS = {
logging.DEBUG: gray + bold,
logging.INFO: blue + bold,
logging.WARNING: yellow + bold,
logging.ERROR: red,
logging.CRITICAL: red + bold,
}
def format(self, record):
log_color = self.COLORS[record.levelno]
format = "(black){asctime}(reset) (levelcolor){levelname:<8}(reset) (green){name}(reset) {message}"
format = format.replace("(black)", self.black + self.bold)
format = format.replace("(reset)", self.reset)
format = format.replace("(levelcolor)", log_color)
format = format.replace("(green)", self.green + self.bold)
formatter = logging.Formatter(format, "%Y-%m-%d %H:%M:%S", style="{")
return formatter.format(record)
logger = logging.getLogger("discord_bot")
logger.setLevel(logging.INFO)
console_handler = logging.StreamHandler()
console_handler.setFormatter(LoggingFormatter())
log_file_path = os.getenv("LOG_FILE", "logs/discord.log")
log_dir = os.path.dirname(log_file_path)
if log_dir:
os.makedirs(log_dir, exist_ok=True)
file_handler = logging.FileHandler(filename=log_file_path, encoding="utf-8", mode="w")
file_handler_formatter = logging.Formatter(
"[{asctime}] [{levelname:<8}] {name}: {message}", "%Y-%m-%d %H:%M:%S", style="{"
)
file_handler.setFormatter(file_handler_formatter)
logger.addHandler(console_handler)
logger.addHandler(file_handler)
class DiscordBot(commands.Bot): class DiscordBot(commands.Bot):
@@ -223,20 +176,7 @@ class DiscordBot(commands.Bot):
) )
def get_uptime(self) -> str: def get_uptime(self) -> str:
uptime_seconds = int(time.time() - self.start_time) return get_uptime(self.start_time)
days = uptime_seconds // 86400
hours = (uptime_seconds % 86400) // 3600
minutes = (uptime_seconds % 3600) // 60
seconds = uptime_seconds % 60
if days > 0:
return f"{days}d {hours}h {minutes}m {seconds}s"
elif hours > 0:
return f"{hours}h {minutes}m {seconds}s"
elif minutes > 0:
return f"{minutes}m {seconds}s"
else:
return f"{seconds}s"
async def close(self) -> None: async def close(self) -> None:
if self._shutdown: if self._shutdown:
@@ -374,13 +314,6 @@ class DiscordBot(commands.Bot):
raise error raise error
def signal_handler(signum, frame):
logger.info("Shutdown requested. Closing bot...")
if bot.loop and not bot.loop.is_closed():
asyncio.create_task(bot.close())
bot.loop.call_soon_threadsafe(bot.loop.stop)
bot = DiscordBot() bot = DiscordBot()
if __name__ == "__main__": if __name__ == "__main__":
@@ -388,8 +321,7 @@ if __name__ == "__main__":
print(ascii) print(ascii)
signal.signal(signal.SIGINT, signal_handler) setup_signal_handlers(bot)
signal.signal(signal.SIGTERM, signal_handler)
try: try:
bot.run(os.getenv("TOKEN")) bot.run(os.getenv("TOKEN"))

View File

@@ -1,3 +1,9 @@
from .ascii_art import ascii, ascii_plain, gradient_text, gradient_text_selective from .ascii_art import ascii, ascii_plain, gradient_text, gradient_text_selective
from .logging import LoggingFormatter, setup_logger
from .time import get_uptime
from .signal import setup_signal_handlers
__all__ = ['ascii', 'ascii_plain', 'gradient_text', 'gradient_text_selective'] __all__ = [
'ascii', 'ascii_plain', 'gradient_text', 'gradient_text_selective',
'LoggingFormatter', 'setup_logger', 'get_uptime', 'setup_signal_handlers'
]

54
utils/logging.py Normal file
View File

@@ -0,0 +1,54 @@
import logging
import os
class LoggingFormatter(logging.Formatter):
black = "\x1b[30m"
red = "\x1b[31m"
green = "\x1b[32m"
yellow = "\x1b[33m"
blue = "\x1b[34m"
gray = "\x1b[38m"
reset = "\x1b[0m"
bold = "\x1b[1m"
COLORS = {
logging.DEBUG: gray + bold,
logging.INFO: blue + bold,
logging.WARNING: yellow + bold,
logging.ERROR: red,
logging.CRITICAL: red + bold,
}
def format(self, record):
log_color = self.COLORS[record.levelno]
format = "(black){asctime}(reset) (levelcolor){levelname:<8}(reset) (green){name}(reset) {message}"
format = format.replace("(black)", self.black + self.bold)
format = format.replace("(reset)", self.reset)
format = format.replace("(levelcolor)", log_color)
format = format.replace("(green)", self.green + self.bold)
formatter = logging.Formatter(format, "%Y-%m-%d %H:%M:%S", style="{")
return formatter.format(record)
def setup_logger():
logger = logging.getLogger("discord_bot")
logger.setLevel(logging.INFO)
console_handler = logging.StreamHandler()
console_handler.setFormatter(LoggingFormatter())
log_file_path = os.getenv("LOG_FILE", "logs/discord.log")
log_dir = os.path.dirname(log_file_path)
if log_dir:
os.makedirs(log_dir, exist_ok=True)
file_handler = logging.FileHandler(filename=log_file_path, encoding="utf-8", mode="w")
file_handler_formatter = logging.Formatter(
"[{asctime}] [{levelname:<8}] {name}: {message}", "%Y-%m-%d %H:%M:%S", style="{"
)
file_handler.setFormatter(file_handler_formatter)
logger.addHandler(console_handler)
logger.addHandler(file_handler)
return logger

13
utils/signal.py Normal file
View File

@@ -0,0 +1,13 @@
import asyncio
import signal
def setup_signal_handlers(bot):
def signal_handler(signum, frame):
bot.logger.info("Shutdown requested. Closing bot...")
if bot.loop and not bot.loop.is_closed():
asyncio.create_task(bot.close())
bot.loop.call_soon_threadsafe(bot.loop.stop)
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)

18
utils/time.py Normal file
View File

@@ -0,0 +1,18 @@
import time
def get_uptime(start_time: float) -> str:
uptime_seconds = int(time.time() - start_time)
days = uptime_seconds // 86400
hours = (uptime_seconds % 86400) // 3600
minutes = (uptime_seconds % 3600) // 60
seconds = uptime_seconds % 60
if days > 0:
return f"{days}d {hours}h {minutes}m {seconds}s"
elif hours > 0:
return f"{hours}h {minutes}m {seconds}s"
elif minutes > 0:
return f"{minutes}m {seconds}s"
else:
return f"{seconds}s"