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

@@ -13,6 +13,9 @@ import globals
from server_manager import server_manager
from utils.llm import query_llama
from utils.dm_interaction_analyzer import dm_analyzer
from utils.logger import get_logger
logger = get_logger('scheduled')
BEDTIME_TRACKING_FILE = "last_bedtime_targets.json"
@@ -20,7 +23,7 @@ async def send_monday_video_for_server(guild_id: int):
"""Send Monday video for a specific server"""
server_config = server_manager.get_server_config(guild_id)
if not server_config:
print(f"⚠️ No config found for server {guild_id}")
logger.warning(f"No config found for server {guild_id}")
return
# No need to switch model - llama-swap handles this automatically
@@ -37,7 +40,7 @@ async def send_monday_video_for_server(guild_id: int):
for channel_id in target_channel_ids:
channel = globals.client.get_channel(channel_id)
if channel is None:
print(f"Could not find channel with ID {channel_id} in server {server_config.guild_name}")
logger.error(f"Could not find channel with ID {channel_id} in server {server_config.guild_name}")
continue
try:
@@ -45,9 +48,9 @@ async def send_monday_video_for_server(guild_id: int):
# Send video link
await channel.send(f"[Happy Miku Monday!]({video_url})")
print(f"Sent Monday video to channel ID {channel_id} in server {server_config.guild_name}")
logger.info(f"Sent Monday video to channel ID {channel_id} in server {server_config.guild_name}")
except Exception as e:
print(f"⚠️ Failed to send video to channel ID {channel_id} in server {server_config.guild_name}: {e}")
logger.error(f"Failed to send video to channel ID {channel_id} in server {server_config.guild_name}: {e}")
async def send_monday_video():
"""Legacy function - now sends to all servers"""
@@ -61,7 +64,7 @@ def load_last_bedtime_targets():
with open(BEDTIME_TRACKING_FILE, "r") as f:
return json.load(f)
except Exception as e:
print(f"⚠️ Failed to load bedtime tracking file: {e}")
logger.error(f"Failed to load bedtime tracking file: {e}")
return {}
_last_bedtime_targets = load_last_bedtime_targets()
@@ -71,13 +74,13 @@ def save_last_bedtime_targets(data):
with open(BEDTIME_TRACKING_FILE, "w") as f:
json.dump(data, f)
except Exception as e:
print(f"⚠️ Failed to save bedtime tracking file: {e}")
logger.error(f"Failed to save bedtime tracking file: {e}")
async def send_bedtime_reminder_for_server(guild_id: int, client=None):
"""Send bedtime reminder for a specific server"""
server_config = server_manager.get_server_config(guild_id)
if not server_config:
print(f"⚠️ No config found for server {guild_id}")
logger.warning(f"No config found for server {guild_id}")
return
# Use provided client or fall back to globals.client
@@ -85,7 +88,7 @@ async def send_bedtime_reminder_for_server(guild_id: int, client=None):
client = globals.client
if client is None:
print(f"⚠️ No Discord client available for bedtime reminder in server {guild_id}")
logger.error(f"No Discord client available for bedtime reminder in server {guild_id}")
return
# No need to switch model - llama-swap handles this automatically
@@ -94,7 +97,7 @@ async def send_bedtime_reminder_for_server(guild_id: int, client=None):
for channel_id in server_config.bedtime_channel_ids:
channel = client.get_channel(channel_id)
if not channel:
print(f"⚠️ Channel ID {channel_id} not found in server {server_config.guild_name}")
logger.warning(f"Channel ID {channel_id} not found in server {server_config.guild_name}")
continue
guild = channel.guild
@@ -112,7 +115,8 @@ async def send_bedtime_reminder_for_server(guild_id: int, client=None):
online_members.append(specific_user)
if not online_members:
print(f"😴 No online members to ping in {guild.name}")
# TODO: Handle this in a different way in the future
logger.debug(f"No online members to ping in {guild.name}")
continue
# Avoid repeating the same person unless they're the only one
@@ -162,9 +166,9 @@ async def send_bedtime_reminder_for_server(guild_id: int, client=None):
try:
await channel.send(f"{chosen_one.mention} {bedtime_message}")
print(f"🌙 Sent bedtime reminder to {chosen_one.display_name} in server {server_config.guild_name}")
logger.info(f"Sent bedtime reminder to {chosen_one.display_name} in server {server_config.guild_name}")
except Exception as e:
print(f"⚠️ Failed to send bedtime reminder in server {server_config.guild_name}: {e}")
logger.error(f"Failed to send bedtime reminder in server {server_config.guild_name}: {e}")
async def send_bedtime_reminder():
"""Legacy function - now sends to all servers"""
@@ -176,7 +180,7 @@ def schedule_random_bedtime():
for guild_id in server_manager.servers:
# Schedule bedtime for each server using the async function
# This will be called from the server manager's event loop
print(f"Scheduling bedtime for server {guild_id}")
logger.info(f"Scheduling bedtime for server {guild_id}")
# Note: This function is now called from the server manager's context
# which properly handles the async operations
@@ -188,8 +192,8 @@ async def send_bedtime_now():
async def run_daily_dm_analysis():
"""Run daily DM interaction analysis - reports one user per day"""
if dm_analyzer is None:
print("⚠️ DM Analyzer not initialized, skipping daily analysis")
logger.warning("DM Analyzer not initialized, skipping daily analysis")
return
print("📊 Running daily DM interaction analysis...")
logger.info("Running daily DM interaction analysis...")
await dm_analyzer.run_daily_analysis()