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,10 @@ import aiohttp
import random
from typing import Optional, List, Dict
import asyncio
from utils.logger import get_logger
logger = get_logger('media')
class DanbooruClient:
"""Client for interacting with Danbooru API"""
@@ -74,23 +78,23 @@ class DanbooruClient:
try:
url = f"{self.BASE_URL}/posts.json"
print(f"🎨 Danbooru request: {url} with params: {params}")
logger.debug(f"Danbooru request: {url} with params: {params}")
async with self.session.get(url, params=params, timeout=10) as response:
if response.status == 200:
posts = await response.json()
print(f"🎨 Danbooru: Found {len(posts)} posts (page {page})")
logger.debug(f"Danbooru: Found {len(posts)} posts (page {page})")
return posts
else:
error_text = await response.text()
print(f"⚠️ Danbooru API error: {response.status}")
print(f"⚠️ Request URL: {response.url}")
print(f"⚠️ Error details: {error_text[:500]}")
logger.error(f"Danbooru API error: {response.status}")
logger.error(f"Request URL: {response.url}")
logger.error(f"Error details: {error_text[:500]}")
return []
except asyncio.TimeoutError:
print(f"⚠️ Danbooru API timeout")
logger.error(f"Danbooru API timeout")
return []
except Exception as e:
print(f"⚠️ Danbooru API error: {e}")
logger.error(f"Danbooru API error: {e}")
return []
async def get_random_miku_image(
@@ -128,7 +132,7 @@ class DanbooruClient:
)
if not posts:
print("⚠️ No posts found, trying without mood tags")
logger.warning("No posts found, trying without mood tags")
# Fallback: try without mood tags
posts = await self.search_miku_images(
rating=["g", "s"],
@@ -146,13 +150,13 @@ class DanbooruClient:
]
if not valid_posts:
print("⚠️ No valid posts with sufficient resolution")
logger.warning("No valid posts with sufficient resolution")
return None
# Pick a random one
selected = random.choice(valid_posts)
print(f"🎨 Selected Danbooru post #{selected.get('id')} - {selected.get('tag_string_character', 'unknown character')}")
logger.info(f"Selected Danbooru post #{selected.get('id')} - {selected.get('tag_string_character', 'unknown character')}")
return selected