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

@@ -10,6 +10,10 @@ from PIL import Image
import re
import globals
from utils.logger import get_logger
logger = get_logger('vision')
# No need for switch_model anymore - llama-swap handles this automatically
@@ -47,7 +51,7 @@ async def extract_tenor_gif_url(tenor_url):
match = re.search(r'tenor\.com/(\d+)\.gif', tenor_url)
if not match:
print(f"⚠️ Could not extract Tenor GIF ID from: {tenor_url}")
logger.warning(f"Could not extract Tenor GIF ID from: {tenor_url}")
return None
gif_id = match.group(1)
@@ -60,7 +64,7 @@ async def extract_tenor_gif_url(tenor_url):
async with aiohttp.ClientSession() as session:
async with session.head(media_url) as resp:
if resp.status == 200:
print(f"Found Tenor GIF: {media_url}")
logger.debug(f"Found Tenor GIF: {media_url}")
return media_url
# If that didn't work, try alternative formats
@@ -69,14 +73,14 @@ async def extract_tenor_gif_url(tenor_url):
async with aiohttp.ClientSession() as session:
async with session.head(alt_url) as resp:
if resp.status == 200:
print(f"Found Tenor GIF (alternative): {alt_url}")
logger.debug(f"Found Tenor GIF (alternative): {alt_url}")
return alt_url
print(f"⚠️ Could not find working Tenor media URL for ID: {gif_id}")
logger.warning(f"Could not find working Tenor media URL for ID: {gif_id}")
return None
except Exception as e:
print(f"⚠️ Error extracting Tenor GIF URL: {e}")
logger.error(f"Error extracting Tenor GIF URL: {e}")
return None
@@ -114,7 +118,7 @@ async def convert_gif_to_mp4(gif_bytes):
with open(temp_mp4_path, 'rb') as f:
mp4_bytes = f.read()
print(f"Converted GIF to MP4 ({len(gif_bytes)} bytes → {len(mp4_bytes)} bytes)")
logger.info(f"Converted GIF to MP4 ({len(gif_bytes)} bytes → {len(mp4_bytes)} bytes)")
return mp4_bytes
finally:
@@ -125,10 +129,10 @@ async def convert_gif_to_mp4(gif_bytes):
os.remove(temp_mp4_path)
except subprocess.CalledProcessError as e:
print(f"⚠️ ffmpeg error converting GIF to MP4: {e.stderr.decode()}")
logger.error(f"ffmpeg error converting GIF to MP4: {e.stderr.decode()}")
return None
except Exception as e:
print(f"⚠️ Error converting GIF to MP4: {e}")
logger.error(f"Error converting GIF to MP4: {e}")
import traceback
traceback.print_exc()
return None
@@ -165,7 +169,7 @@ async def extract_video_frames(video_bytes, num_frames=4):
if frames:
return frames
except Exception as e:
print(f"Not a GIF, trying video extraction: {e}")
logger.debug(f"Not a GIF, trying video extraction: {e}")
# For video files (MP4, WebM, etc.), use ffmpeg
import subprocess
@@ -222,7 +226,7 @@ async def extract_video_frames(video_bytes, num_frames=4):
os.remove(temp_video_path)
except Exception as e:
print(f"⚠️ Error extracting frames: {e}")
logger.error(f"Error extracting frames: {e}")
import traceback
traceback.print_exc()
@@ -271,10 +275,10 @@ async def analyze_image_with_vision(base64_img):
return data.get("choices", [{}])[0].get("message", {}).get("content", "No description.")
else:
error_text = await response.text()
print(f"Vision API error: {response.status} - {error_text}")
logger.error(f"Vision API error: {response.status} - {error_text}")
return f"Error analyzing image: {response.status}"
except Exception as e:
print(f"⚠️ Error in analyze_image_with_vision: {e}")
logger.error(f"Error in analyze_image_with_vision: {e}")
return f"Error analyzing image: {str(e)}"
@@ -333,10 +337,10 @@ async def analyze_video_with_vision(video_frames, media_type="video"):
return data.get("choices", [{}])[0].get("message", {}).get("content", "No description.")
else:
error_text = await response.text()
print(f"Vision API error: {response.status} - {error_text}")
logger.error(f"Vision API error: {response.status} - {error_text}")
return f"Error analyzing video: {response.status}"
except Exception as e:
print(f"⚠️ Error in analyze_video_with_vision: {e}")
logger.error(f"Error in analyze_video_with_vision: {e}")
return f"Error analyzing video: {str(e)}"