From c1561b15abc0c12fe0b4a5d989d38ee680d23a53 Mon Sep 17 00:00:00 2001 From: Broque Thomas Date: Mon, 8 Dec 2025 12:53:44 -0800 Subject: [PATCH] Add dynamic log level control via Web UI Introduces API endpoints and UI elements to view and change the application's log level from the web interface. Log level changes are applied immediately and persisted in the database. Updates backend logic, logging utilities, and frontend scripts to support this feature. --- main.py | 17 ++++++++++++++++- utils/logging_config.py | 24 ++++++++++++++++++++++++ web_server.py | 41 +++++++++++++++++++++++++++++++++++++++++ webui/index.html | 13 +++++++++++++ webui/static/script.js | 36 ++++++++++++++++++++++++++++++++++++ 5 files changed, 130 insertions(+), 1 deletion(-) diff --git a/main.py b/main.py index 1ea71bc..c5b286d 100644 --- a/main.py +++ b/main.py @@ -415,8 +415,23 @@ class MainWindow(QMainWindow): event.accept() def main(): + # Check for saved log level preference in database + try: + from database.music_database import MusicDatabase + db = MusicDatabase() + saved_log_level = db.get_preference('log_level') + if saved_log_level: + log_level = saved_log_level + else: + # Fall back to config file + logging_config = config_manager.get_logging_config() + log_level = logging_config.get('level', 'INFO') + except: + # If database isn't available yet, use config file + logging_config = config_manager.get_logging_config() + log_level = logging_config.get('level', 'INFO') + logging_config = config_manager.get_logging_config() - log_level = logging_config.get('level', 'INFO') log_file = logging_config.get('path', 'logs/newmusic.log') setup_logging(level=log_level, log_file=log_file) diff --git a/utils/logging_config.py b/utils/logging_config.py index 6457c0d..49a6a82 100644 --- a/utils/logging_config.py +++ b/utils/logging_config.py @@ -90,4 +90,28 @@ def setup_logging(level: str = "INFO", log_file: Optional[str] = None) -> loggin def get_logger(name: str) -> logging.Logger: return logging.getLogger(f"newmusic.{name}") +def set_log_level(level: str) -> bool: + """Dynamically change the log level for all loggers without restart""" + try: + log_level = getattr(logging, level.upper(), logging.INFO) + + # Get the root "newmusic" logger + root_logger = logging.getLogger("newmusic") + root_logger.setLevel(log_level) + + # Update all handlers + for handler in root_logger.handlers: + handler.setLevel(log_level) + + root_logger.info(f"Log level changed to: {level.upper()}") + return True + except Exception as e: + print(f"Error setting log level: {e}") + return False + +def get_current_log_level() -> str: + """Get the current log level""" + root_logger = logging.getLogger("newmusic") + return logging.getLevelName(root_logger.level) + main_logger = get_logger("main") \ No newline at end of file diff --git a/web_server.py b/web_server.py index b3924d2..1b669d4 100644 --- a/web_server.py +++ b/web_server.py @@ -2086,6 +2086,47 @@ def handle_settings(): except Exception as e: return jsonify({"error": str(e)}), 500 +@app.route('/api/settings/log-level', methods=['GET', 'POST']) +def handle_log_level(): + """Get or set the application log level""" + from utils.logging_config import set_log_level, get_current_log_level + from database.music_database import MusicDatabase + + if request.method == 'POST': + try: + data = request.get_json() + level = data.get('level') + + if not level or level.upper() not in ['DEBUG', 'INFO', 'WARNING', 'ERROR']: + return jsonify({"success": False, "error": "Invalid log level. Must be DEBUG, INFO, WARNING, or ERROR"}), 400 + + # Change the log level dynamically + success = set_log_level(level) + + if success: + # Save to database preferences + db = MusicDatabase() + db.set_preference('log_level', level.upper()) + + logger.info(f"Log level changed to {level.upper()} via Web UI") + add_activity_item("🔍", "Log Level Changed", f"Set to {level.upper()}", "Now") + + return jsonify({"success": True, "level": level.upper()}) + else: + return jsonify({"success": False, "error": "Failed to set log level"}), 500 + + except Exception as e: + logger.error(f"Error setting log level: {e}") + return jsonify({"success": False, "error": str(e)}), 500 + + else: # GET request + try: + current_level = get_current_log_level() + return jsonify({"success": True, "level": current_level}) + except Exception as e: + logger.error(f"Error getting log level: {e}") + return jsonify({"success": False, "error": str(e)}), 500 + @app.route('/api/test-connection', methods=['POST']) def test_connection_endpoint(): data = request.get_json() diff --git a/webui/index.html b/webui/index.html index 1ffe7a6..92dd599 100644 --- a/webui/index.html +++ b/webui/index.html @@ -2411,6 +2411,19 @@ + +
+ + +
+ Controls the level of detail in application logs. DEBUG shows all details, INFO shows general operations, WARNING and ERROR show only issues. +
+
diff --git a/webui/static/script.js b/webui/static/script.js index cd7ce46..9e94778 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -1545,12 +1545,48 @@ async function loadSettingsData() { console.error('Error loading discovery lookback period:', error); } + // Load current log level + try { + const logLevelResponse = await fetch('/api/settings/log-level'); + const logLevelData = await logLevelResponse.json(); + if (logLevelData.success && logLevelData.level) { + document.getElementById('log-level-select').value = logLevelData.level; + } + } catch (error) { + console.error('Error loading log level:', error); + } + } catch (error) { console.error('Error loading settings:', error); showToast('Failed to load settings', 'error'); } } +async function changeLogLevel() { + const selector = document.getElementById('log-level-select'); + const level = selector.value; + + try { + const response = await fetch('/api/settings/log-level', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ level: level }) + }); + + const data = await response.json(); + + if (data.success) { + showToast(`Log level changed to ${level}`, 'success'); + console.log(`Log level changed to: ${level}`); + } else { + showToast(`Failed to change log level: ${data.error}`, 'error'); + } + } catch (error) { + console.error('Error changing log level:', error); + showToast('Failed to change log level', 'error'); + } +} + function updateMediaServerFields() { const serverType = document.getElementById('media-server-type').value; const urlInput = document.getElementById('media-server-url');