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

@@ -14,6 +14,9 @@ import time
from typing import Optional, Tuple
import globals
from utils.llm import query_llama
from utils.logger import get_logger
logger = get_logger('media')
# Image generation detection patterns
IMAGE_REQUEST_PATTERNS = [
@@ -133,11 +136,11 @@ def find_latest_generated_image(prompt_id: str, expected_filename: str = None) -
recent_threshold = time.time() - 600 # 10 minutes
for file_path in all_files:
if os.path.getmtime(file_path) > recent_threshold:
print(f"🎨 Found recent image: {file_path}")
logger.debug(f"Found recent image: {file_path}")
return file_path
except Exception as e:
print(f"⚠️ Error searching in {output_dir}: {e}")
logger.error(f"Error searching in {output_dir}: {e}")
continue
return None
@@ -156,7 +159,7 @@ async def generate_image_with_comfyui(prompt: str) -> Optional[str]:
# Load the workflow template
workflow_path = "Miku_BasicWorkflow.json"
if not os.path.exists(workflow_path):
print(f"Workflow template not found: {workflow_path}")
logger.error(f"Workflow template not found: {workflow_path}")
return None
with open(workflow_path, 'r') as f:
@@ -186,29 +189,29 @@ async def generate_image_with_comfyui(prompt: str) -> Optional[str]:
async with test_session.get(f"{url}/system_stats", timeout=timeout) as test_response:
if test_response.status == 200:
comfyui_url = url
print(f"ComfyUI found at: {url}")
logger.debug(f"ComfyUI found at: {url}")
break
except:
continue
if not comfyui_url:
print(f"ComfyUI not reachable at any of: {comfyui_urls}")
logger.error(f"ComfyUI not reachable at any of: {comfyui_urls}")
return None
async with aiohttp.ClientSession() as session:
# Submit the generation request
async with session.post(f"{comfyui_url}/prompt", json=payload) as response:
if response.status != 200:
print(f"ComfyUI request failed: {response.status}")
logger.error(f"ComfyUI request failed: {response.status}")
return None
result = await response.json()
prompt_id = result.get("prompt_id")
if not prompt_id:
print("No prompt_id received from ComfyUI")
logger.error("No prompt_id received from ComfyUI")
return None
print(f"🎨 ComfyUI generation started with prompt_id: {prompt_id}")
logger.info(f"ComfyUI generation started with prompt_id: {prompt_id}")
# Poll for completion (timeout after 5 minutes)
timeout = 300 # 5 minutes
@@ -242,20 +245,20 @@ async def generate_image_with_comfyui(prompt: str) -> Optional[str]:
# Verify the file exists before returning
if os.path.exists(image_path):
print(f"Image generated successfully: {image_path}")
logger.info(f"Image generated successfully: {image_path}")
return image_path
else:
# Try alternative paths in case of different mounting
alt_path = os.path.join("/app/ComfyUI/output", filename)
if os.path.exists(alt_path):
print(f"Image generated successfully: {alt_path}")
logger.info(f"Image generated successfully: {alt_path}")
return alt_path
else:
print(f"⚠️ Generated image not found at expected paths: {image_path} or {alt_path}")
logger.warning(f"Generated image not found at expected paths: {image_path} or {alt_path}")
continue
# If we couldn't find the image via API, try the fallback method
print("🔍 Image not found via API, trying fallback method...")
logger.debug("Image not found via API, trying fallback method...")
fallback_image = find_latest_generated_image(prompt_id)
if fallback_image:
return fallback_image
@@ -263,19 +266,19 @@ async def generate_image_with_comfyui(prompt: str) -> Optional[str]:
# Wait before polling again
await asyncio.sleep(2)
print("ComfyUI generation timed out")
logger.error("ComfyUI generation timed out")
# Final fallback: look for the most recent image
print("🔍 Trying final fallback: most recent image...")
logger.debug("Trying final fallback: most recent image...")
fallback_image = find_latest_generated_image(prompt_id)
if fallback_image:
print(f"Found image via fallback method: {fallback_image}")
logger.info(f"Found image via fallback method: {fallback_image}")
return fallback_image
return None
except Exception as e:
print(f"Error in generate_image_with_comfyui: {e}")
logger.error(f"Error in generate_image_with_comfyui: {e}")
return None
async def handle_image_generation_request(message, prompt: str) -> bool:
@@ -307,7 +310,7 @@ async def handle_image_generation_request(message, prompt: str) -> bool:
# Start typing to show we're working
async with message.channel.typing():
# Generate the image
print(f"🎨 Starting image generation for prompt: {prompt}")
logger.info(f"Starting image generation for prompt: {prompt}")
image_path = await generate_image_with_comfyui(prompt)
if image_path and os.path.exists(image_path):
@@ -322,7 +325,7 @@ async def handle_image_generation_request(message, prompt: str) -> bool:
await message.channel.send(completion_response, file=file)
print(f"Image sent successfully to {message.author.display_name}")
logger.info(f"Image sent successfully to {message.author.display_name}")
# Log to DM history if it's a DM
if is_dm:
@@ -336,11 +339,11 @@ async def handle_image_generation_request(message, prompt: str) -> bool:
error_response = await query_llama(error_prompt, user_id=user_id, guild_id=guild_id, response_type=response_type)
await message.channel.send(error_response)
print(f"Image generation failed for prompt: {prompt}")
logger.error(f"Image generation failed for prompt: {prompt}")
return False
except Exception as e:
print(f"Error in handle_image_generation_request: {e}")
logger.error(f"Error in handle_image_generation_request: {e}")
# Send error message
try: