feat: Implement comprehensive non-hierarchical logging system

- Created new logging infrastructure with per-component filtering
- Added 6 log levels: DEBUG, INFO, API, WARNING, ERROR, CRITICAL
- Implemented non-hierarchical level control (any combination can be enabled)
- Migrated 917 print() statements across 31 files to structured logging
- Created web UI (system.html) for runtime configuration with dark theme
- Added global level controls to enable/disable levels across all components
- Added timestamp format control (off/time/date/datetime options)
- Implemented log rotation (10MB per file, 5 backups)
- Added API endpoints for dynamic log configuration
- Configured HTTP request logging with filtering via api.requests component
- Intercepted APScheduler logs with proper formatting
- Fixed persistence paths to use /app/memory for Docker volume compatibility
- Fixed checkbox display bug in web UI (enabled_levels now properly shown)
- Changed System Settings button to open in same tab instead of new window

Components: bot, api, api.requests, autonomous, persona, vision, llm,
conversation, mood, dm, scheduled, gpu, media, server, commands,
sentiment, core, apscheduler

All settings persist across container restarts via JSON config.
This commit is contained in:
2026-01-10 20:46:19 +02:00
parent ce00f9bd95
commit 32c2a7b930
34 changed files with 2766 additions and 936 deletions

View File

@@ -7,6 +7,9 @@ import asyncio
from discord.ext import tasks
import globals
import datetime
from utils.logger import get_logger
logger = get_logger('mood')
MOOD_EMOJIS = {
"asleep": "💤",
@@ -47,7 +50,7 @@ def load_mood_description(mood_name: str) -> str:
with open(path, "r", encoding="utf-8") as f:
return f.read().strip()
except FileNotFoundError:
print(f"⚠️ Mood file '{mood_name}' not found. Falling back to default.")
logger.warning(f"Mood file '{mood_name}' not found. Falling back to default.")
# Return a default mood description instead of recursive call
return "I'm feeling neutral and balanced today."
@@ -120,17 +123,17 @@ def detect_mood_shift(response_text, server_context=None):
# For server context, check against server's current mood
current_mood = server_context.get('current_mood_name', 'neutral')
if current_mood != "sleepy":
print(f"Mood 'asleep' skipped - server mood isn't 'sleepy', it's '{current_mood}'")
logger.debug(f"Mood 'asleep' skipped - server mood isn't 'sleepy', it's '{current_mood}'")
continue
else:
# For DM context, check against DM mood
if globals.DM_MOOD != "sleepy":
print(f"Mood 'asleep' skipped - DM mood isn't 'sleepy', it's '{globals.DM_MOOD}'")
logger.debug(f"Mood 'asleep' skipped - DM mood isn't 'sleepy', it's '{globals.DM_MOOD}'")
continue
for phrase in phrases:
if phrase.lower() in response_text.lower():
print(f"*️⃣ Mood keyword triggered: {phrase}")
logger.info(f"Mood keyword triggered: {phrase}")
return mood
return None
@@ -155,13 +158,13 @@ async def rotate_dm_mood():
globals.DM_MOOD = new_mood
globals.DM_MOOD_DESCRIPTION = load_mood_description(new_mood)
print(f"🔄 DM mood rotated from {old_mood} to {new_mood}")
logger.info(f"DM mood rotated from {old_mood} to {new_mood}")
# Note: We don't update server nicknames here because servers have their own independent moods.
# DM mood only affects direct messages to users.
except Exception as e:
print(f"Exception in rotate_dm_mood: {e}")
logger.error(f"Exception in rotate_dm_mood: {e}")
async def update_all_server_nicknames():
"""
@@ -171,8 +174,8 @@ async def update_all_server_nicknames():
This function incorrectly used DM mood to update all server nicknames,
breaking the independent per-server mood system.
"""
print("⚠️ WARNING: update_all_server_nicknames() is deprecated and should not be called!")
print("⚠️ Use update_server_nickname(guild_id) for per-server nickname updates instead.")
logger.warning("WARNING: update_all_server_nicknames() is deprecated and should not be called!")
logger.warning("Use update_server_nickname(guild_id) for per-server nickname updates instead.")
# Do nothing - this function should not modify nicknames
async def nickname_mood_emoji(guild_id: int):
@@ -182,11 +185,11 @@ async def nickname_mood_emoji(guild_id: int):
async def update_server_nickname(guild_id: int):
"""Update nickname for a specific server based on its mood"""
try:
print(f"🎭 Starting nickname update for server {guild_id}")
logger.debug(f"Starting nickname update for server {guild_id}")
# Check if bot is ready
if not globals.client.is_ready():
print(f"⚠️ Bot not ready yet, deferring nickname update for server {guild_id}")
logger.warning(f"Bot not ready yet, deferring nickname update for server {guild_id}")
return
# Check if evil mode is active
@@ -196,7 +199,7 @@ async def update_server_nickname(guild_id: int):
from server_manager import server_manager
server_config = server_manager.get_server_config(guild_id)
if not server_config:
print(f"⚠️ No server config found for guild {guild_id}")
logger.warning(f"No server config found for guild {guild_id}")
return
if evil_mode:
@@ -209,29 +212,29 @@ async def update_server_nickname(guild_id: int):
emoji = MOOD_EMOJIS.get(mood, "")
base_name = "Hatsune Miku"
print(f"🔍 Server {guild_id} mood is: {mood} (evil_mode={evil_mode})")
print(f"🔍 Using emoji: {emoji}")
logger.debug(f"Server {guild_id} mood is: {mood} (evil_mode={evil_mode})")
logger.debug(f"Using emoji: {emoji}")
nickname = f"{base_name}{emoji}"
print(f"🔍 New nickname will be: {nickname}")
logger.debug(f"New nickname will be: {nickname}")
guild = globals.client.get_guild(guild_id)
if guild:
print(f"🔍 Found guild: {guild.name}")
logger.debug(f"Found guild: {guild.name}")
me = guild.get_member(globals.BOT_USER.id)
if me is not None:
print(f"🔍 Found bot member: {me.display_name}")
logger.debug(f"Found bot member: {me.display_name}")
try:
await me.edit(nick=nickname)
print(f"💱 Changed nickname to {nickname} in server {guild.name}")
logger.info(f"Changed nickname to {nickname} in server {guild.name}")
except Exception as e:
print(f"⚠️ Failed to update nickname in server {guild.name}: {e}")
logger.warning(f"Failed to update nickname in server {guild.name}: {e}")
else:
print(f"⚠️ Could not find bot member in server {guild.name}")
logger.warning(f"Could not find bot member in server {guild.name}")
else:
print(f"⚠️ Could not find guild {guild_id}")
logger.warning(f"Could not find guild {guild_id}")
except Exception as e:
print(f"⚠️ Error updating server nickname for guild {guild_id}: {e}")
logger.error(f"Error updating server nickname for guild {guild_id}: {e}")
import traceback
traceback.print_exc()
@@ -268,7 +271,7 @@ async def rotate_server_mood(guild_id: int):
# Block transition to asleep unless coming from sleepy
if new_mood_name == "asleep" and old_mood_name != "sleepy":
print(f"Cannot rotate to asleep from {old_mood_name}, must be sleepy first")
logger.warning(f"Cannot rotate to asleep from {old_mood_name}, must be sleepy first")
# Try to get a different mood
attempts = 0
while (new_mood_name == "asleep" or new_mood_name == old_mood_name) and attempts < 5:
@@ -282,7 +285,7 @@ async def rotate_server_mood(guild_id: int):
from utils.autonomous import on_mood_change
on_mood_change(guild_id, new_mood_name)
except Exception as mood_notify_error:
print(f"⚠️ Failed to notify autonomous engine of mood change: {mood_notify_error}")
logger.error(f"Failed to notify autonomous engine of mood change: {mood_notify_error}")
# If transitioning to asleep, set up auto-wake
if new_mood_name == "asleep":
@@ -298,22 +301,22 @@ async def rotate_server_mood(guild_id: int):
from utils.autonomous import on_mood_change
on_mood_change(guild_id, "neutral")
except Exception as mood_notify_error:
print(f"⚠️ Failed to notify autonomous engine of wake-up mood change: {mood_notify_error}")
logger.error(f"Failed to notify autonomous engine of wake-up mood change: {mood_notify_error}")
await update_server_nickname(guild_id)
print(f"🌅 Server {guild_id} woke up from auto-sleep (mood rotation)")
logger.info(f"Server {guild_id} woke up from auto-sleep (mood rotation)")
globals.client.loop.create_task(delayed_wakeup())
print(f"Scheduled auto-wake for server {guild_id} in 1 hour")
logger.info(f"Scheduled auto-wake for server {guild_id} in 1 hour")
# Update nickname for this specific server
await update_server_nickname(guild_id)
print(f"🔄 Rotated mood for server {guild_id} from {old_mood_name} to {new_mood_name}")
logger.info(f"Rotated mood for server {guild_id} from {old_mood_name} to {new_mood_name}")
except Exception as e:
print(f"Exception in rotate_server_mood for server {guild_id}: {e}")
logger.error(f"Exception in rotate_server_mood for server {guild_id}: {e}")
async def clear_angry_mood_after_delay():
"""Clear angry mood after delay (legacy function - now handled per-server)"""
print("⚠️ clear_angry_mood_after_delay called - this function is deprecated")
logger.warning("clear_angry_mood_after_delay called - this function is deprecated")
pass