diff --git a/core/plex_scan_manager.py b/core/plex_scan_manager.py index 8a59055f..32d1b4a2 100644 --- a/core/plex_scan_manager.py +++ b/core/plex_scan_manager.py @@ -49,7 +49,7 @@ class PlexScanManager: Args: reason: Optional reason for the scan request (for logging) """ - logger.info(f"🔍 DEBUG: Plex scan requested - reason: {reason}") + logger.info(f"DEBUG: Plex scan requested - reason: {reason}") with self._lock: if self._scan_in_progress: # Plex is currently scanning - mark that we need another scan later @@ -78,8 +78,8 @@ class PlexScanManager: with self._lock: if callback not in self._scan_completion_callbacks: self._scan_completion_callbacks.append(callback) - logger.info(f"🔍 DEBUG: Added scan completion callback: {callback.__name__}") - logger.info(f"🔍 DEBUG: Total callbacks registered: {len(self._scan_completion_callbacks)}") + logger.info(f"DEBUG: Added scan completion callback: {callback.__name__}") + logger.info(f"DEBUG: Total callbacks registered: {len(self._scan_completion_callbacks)}") def remove_scan_completion_callback(self, callback): """ @@ -213,11 +213,11 @@ class PlexScanManager: # Check if Plex is still scanning is_scanning = self.plex_client.is_library_scanning("Music") - logger.info(f"🔍 DEBUG: Plex scan status check - is_scanning: {is_scanning}") + logger.info(f"DEBUG: Plex scan status check - is_scanning: {is_scanning}") if is_scanning: # Still scanning, poll again in 30 seconds - logger.info("🔍 DEBUG: Plex library still scanning, will check again in 30 seconds") + logger.info("DEBUG: Plex library still scanning, will check again in 30 seconds") threading.Timer(30, self._poll_scan_status).start() else: # Scan completed! @@ -260,12 +260,12 @@ class PlexScanManager: with self._lock: callbacks = self._scan_completion_callbacks.copy() # Copy to avoid lock issues - logger.info(f"🔍 DEBUG: Calling {len(callbacks)} scan completion callbacks") + logger.info(f"DEBUG: Calling {len(callbacks)} scan completion callbacks") for callback in callbacks: try: - logger.info(f"🔍 DEBUG: Executing callback: {callback.__name__}") + logger.info(f"DEBUG: Executing callback: {callback.__name__}") callback() - logger.info(f"🔍 DEBUG: Callback {callback.__name__} completed successfully") + logger.info(f"DEBUG: Callback {callback.__name__} completed successfully") except Exception as e: logger.error(f"Error in scan completion callback {callback.__name__}: {e}") diff --git a/utils/__pycache__/logging_config.cpython-310.pyc b/utils/__pycache__/logging_config.cpython-310.pyc index c42ed872..6f4c8041 100644 Binary files a/utils/__pycache__/logging_config.cpython-310.pyc and b/utils/__pycache__/logging_config.cpython-310.pyc differ diff --git a/utils/logging_config.py b/utils/logging_config.py index 9bcf2a70..6457c0dc 100644 --- a/utils/logging_config.py +++ b/utils/logging_config.py @@ -1,10 +1,37 @@ import logging import sys +import re from pathlib import Path from datetime import datetime from typing import Optional -class ColoredFormatter(logging.Formatter): +class SafeFormatter(logging.Formatter): + """Formatter that handles Unicode characters safely on Windows""" + + @staticmethod + def strip_emojis(text): + """Remove emoji characters from text for Windows compatibility""" + # Remove emoji characters but keep other Unicode + emoji_pattern = re.compile("[" + u"\U0001F600-\U0001F64F" # emoticons + u"\U0001F300-\U0001F5FF" # symbols & pictographs + u"\U0001F680-\U0001F6FF" # transport & map symbols + u"\U0001F1E0-\U0001F1FF" # flags (iOS) + u"\U00002702-\U000027B0" + u"\U000024C2-\U0001F251" + "]+", flags=re.UNICODE) + return emoji_pattern.sub('', text) + + def format(self, record): + # Try to format with emojis first, fall back to stripped version + try: + return super().format(record) + except UnicodeEncodeError: + # Strip emojis and try again for Windows compatibility + record.getMessage = lambda: self.strip_emojis(record.msg % record.args if record.args else record.msg) + return super().format(record) + +class ColoredFormatter(SafeFormatter): COLORS = { 'DEBUG': '\033[94m', 'INFO': '\033[92m', @@ -32,6 +59,9 @@ def setup_logging(level: str = "INFO", log_file: Optional[str] = None) -> loggin console_handler = logging.StreamHandler(sys.stdout) console_handler.setLevel(log_level) + # Force UTF-8 encoding for Windows compatibility with Unicode characters + if hasattr(console_handler.stream, 'reconfigure'): + console_handler.stream.reconfigure(encoding='utf-8', errors='replace') console_formatter = ColoredFormatter( fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s', @@ -44,10 +74,10 @@ def setup_logging(level: str = "INFO", log_file: Optional[str] = None) -> loggin log_path = Path(log_file) log_path.parent.mkdir(parents=True, exist_ok=True) - file_handler = logging.FileHandler(log_path) + file_handler = logging.FileHandler(log_path, encoding='utf-8') file_handler.setLevel(log_level) - file_formatter = logging.Formatter( + file_formatter = SafeFormatter( fmt='%(asctime)s - %(name)s - %(levelname)s - %(funcName)s:%(lineno)d - %(message)s', datefmt='%Y-%m-%d %H:%M:%S' )