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:
286
bot/utils/log_config.py
Normal file
286
bot/utils/log_config.py
Normal file
@@ -0,0 +1,286 @@
|
||||
"""
|
||||
Log Configuration Manager
|
||||
|
||||
Handles runtime configuration updates for the logging system.
|
||||
Provides API for the web UI to update log settings without restarting the bot.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional
|
||||
import json
|
||||
|
||||
try:
|
||||
from utils.logger import get_logger
|
||||
logger = get_logger('core')
|
||||
except Exception:
|
||||
logger = None
|
||||
|
||||
|
||||
CONFIG_FILE = Path('/app/memory/log_settings.json')
|
||||
|
||||
|
||||
def load_config() -> Dict:
|
||||
"""Load log configuration from file."""
|
||||
from utils.logger import get_log_config
|
||||
return get_log_config()
|
||||
|
||||
|
||||
def save_config(config: Dict) -> bool:
|
||||
"""
|
||||
Save log configuration to file.
|
||||
|
||||
Args:
|
||||
config: Configuration dictionary
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
from utils.logger import save_config
|
||||
save_config(config)
|
||||
return True
|
||||
except Exception as e:
|
||||
if logger:
|
||||
logger.error(f"Failed to save log config: {e}")
|
||||
print(f"Failed to save log config: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def update_component(component: str, enabled: bool = None, enabled_levels: List[str] = None) -> bool:
|
||||
"""
|
||||
Update a single component's configuration.
|
||||
|
||||
Args:
|
||||
component: Component name
|
||||
enabled: Enable/disable the component
|
||||
enabled_levels: List of log levels to enable (DEBUG, INFO, WARNING, ERROR, CRITICAL, API)
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
config = load_config()
|
||||
|
||||
if component not in config['components']:
|
||||
return False
|
||||
|
||||
if enabled is not None:
|
||||
config['components'][component]['enabled'] = enabled
|
||||
|
||||
if enabled_levels is not None:
|
||||
valid_levels = ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL', 'API']
|
||||
# Validate all levels
|
||||
for level in enabled_levels:
|
||||
if level.upper() not in valid_levels:
|
||||
return False
|
||||
config['components'][component]['enabled_levels'] = [l.upper() for l in enabled_levels]
|
||||
|
||||
return save_config(config)
|
||||
except Exception as e:
|
||||
if logger:
|
||||
logger.error(f"Failed to update component {component}: {e}")
|
||||
print(f"Failed to update component {component}: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def update_global_level(level: str, enabled: bool) -> bool:
|
||||
"""
|
||||
Enable or disable a specific log level across all components.
|
||||
|
||||
Args:
|
||||
level: Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL, API)
|
||||
enabled: True to enable, False to disable
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
level = level.upper()
|
||||
valid_levels = ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL', 'API']
|
||||
|
||||
if level not in valid_levels:
|
||||
return False
|
||||
|
||||
config = load_config()
|
||||
|
||||
# Update all components
|
||||
for component_name in config['components'].keys():
|
||||
current_levels = config['components'][component_name].get('enabled_levels', [])
|
||||
|
||||
if enabled:
|
||||
# Add level if not present
|
||||
if level not in current_levels:
|
||||
current_levels.append(level)
|
||||
else:
|
||||
# Remove level if present
|
||||
if level in current_levels:
|
||||
current_levels.remove(level)
|
||||
|
||||
config['components'][component_name]['enabled_levels'] = current_levels
|
||||
|
||||
return save_config(config)
|
||||
except Exception as e:
|
||||
if logger:
|
||||
logger.error(f"Failed to update global level {level}: {e}")
|
||||
print(f"Failed to update global level {level}: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def update_timestamp_format(format_type: str) -> bool:
|
||||
"""
|
||||
Update timestamp format for all log outputs.
|
||||
|
||||
Args:
|
||||
format_type: Format type - 'off', 'time', 'date', or 'datetime'
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
valid_formats = ['off', 'time', 'date', 'datetime']
|
||||
|
||||
if format_type not in valid_formats:
|
||||
return False
|
||||
|
||||
config = load_config()
|
||||
|
||||
if 'formatting' not in config:
|
||||
config['formatting'] = {}
|
||||
|
||||
config['formatting']['timestamp_format'] = format_type
|
||||
|
||||
return save_config(config)
|
||||
except Exception as e:
|
||||
if logger:
|
||||
logger.error(f"Failed to update timestamp format: {e}")
|
||||
print(f"Failed to update timestamp format: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def update_api_filters(
|
||||
exclude_paths: List[str] = None,
|
||||
exclude_status: List[int] = None,
|
||||
include_slow_requests: bool = None,
|
||||
slow_threshold_ms: int = None
|
||||
) -> bool:
|
||||
"""
|
||||
Update API request filtering configuration.
|
||||
|
||||
Args:
|
||||
exclude_paths: List of path patterns to exclude (e.g., ['/health', '/static/*'])
|
||||
exclude_status: List of HTTP status codes to exclude (e.g., [200, 304])
|
||||
include_slow_requests: Whether to log slow requests
|
||||
slow_threshold_ms: Threshold for slow requests in milliseconds
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
config = load_config()
|
||||
|
||||
if 'api.requests' not in config['components']:
|
||||
return False
|
||||
|
||||
filters = config['components']['api.requests'].get('filters', {})
|
||||
|
||||
if exclude_paths is not None:
|
||||
filters['exclude_paths'] = exclude_paths
|
||||
|
||||
if exclude_status is not None:
|
||||
filters['exclude_status'] = exclude_status
|
||||
|
||||
if include_slow_requests is not None:
|
||||
filters['include_slow_requests'] = include_slow_requests
|
||||
|
||||
if slow_threshold_ms is not None:
|
||||
filters['slow_threshold_ms'] = slow_threshold_ms
|
||||
|
||||
config['components']['api.requests']['filters'] = filters
|
||||
|
||||
return save_config(config)
|
||||
except Exception as e:
|
||||
if logger:
|
||||
logger.error(f"Failed to update API filters: {e}")
|
||||
print(f"Failed to update API filters: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def reset_to_defaults() -> bool:
|
||||
"""
|
||||
Reset configuration to defaults.
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
from utils.logger import get_default_config, save_config
|
||||
default_config = get_default_config()
|
||||
save_config(default_config)
|
||||
return True
|
||||
except Exception as e:
|
||||
if logger:
|
||||
logger.error(f"Failed to reset config: {e}")
|
||||
print(f"Failed to reset config: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def get_component_config(component: str) -> Optional[Dict]:
|
||||
"""
|
||||
Get configuration for a specific component.
|
||||
|
||||
Args:
|
||||
component: Component name
|
||||
|
||||
Returns:
|
||||
Component configuration dictionary or None
|
||||
"""
|
||||
try:
|
||||
config = load_config()
|
||||
return config['components'].get(component)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def is_component_enabled(component: str) -> bool:
|
||||
"""
|
||||
Check if a component is enabled.
|
||||
|
||||
Args:
|
||||
component: Component name
|
||||
|
||||
Returns:
|
||||
True if enabled, False otherwise
|
||||
"""
|
||||
component_config = get_component_config(component)
|
||||
if component_config is None:
|
||||
return True # Default to enabled
|
||||
return component_config.get('enabled', True)
|
||||
|
||||
|
||||
def get_component_level(component: str) -> str:
|
||||
"""
|
||||
Get log level for a component.
|
||||
|
||||
Args:
|
||||
component: Component name
|
||||
|
||||
Returns:
|
||||
Log level string (e.g., 'INFO', 'DEBUG')
|
||||
"""
|
||||
component_config = get_component_config(component)
|
||||
if component_config is None:
|
||||
return 'INFO' # Default level
|
||||
return component_config.get('level', 'INFO')
|
||||
|
||||
|
||||
def reload_all_loggers():
|
||||
"""Reload all logger configurations."""
|
||||
try:
|
||||
from utils.logger import reload_config
|
||||
reload_config()
|
||||
return True
|
||||
except Exception as e:
|
||||
if logger:
|
||||
logger.error(f"Failed to reload loggers: {e}")
|
||||
print(f"Failed to reload loggers: {e}")
|
||||
return False
|
||||
Reference in New Issue
Block a user