Replace more print logs with proper logger usage

This commit is contained in:
Antti Kettunen 2026-04-20 09:21:50 +03:00
parent ac17bc8d87
commit fe7ae29b8a
No known key found for this signature in database
GPG key ID: C6B2A3D250359BD7
8 changed files with 131 additions and 129 deletions

View file

@ -5,6 +5,10 @@ import sqlite3
from typing import Dict, Any, Optional from typing import Dict, Any, Optional
from cryptography.fernet import Fernet, InvalidToken from cryptography.fernet import Fernet, InvalidToken
from pathlib import Path from pathlib import Path
from utils.logging_config import get_logger
logger = get_logger("config")
class ConfigManager: class ConfigManager:
def __init__(self, config_path: str = "config/config.json"): def __init__(self, config_path: str = "config/config.json"):
@ -33,7 +37,7 @@ class ConfigManager:
# Default to project path even if it doesn't exist yet (for creation/fallback) # Default to project path even if it doesn't exist yet (for creation/fallback)
self.config_path = project_path self.config_path = project_path
print(f"ConfigManager initialized with path: {self.config_path}") logger.info(f"ConfigManager initialized with path: {self.config_path}")
self.config_data: Dict[str, Any] = {} self.config_data: Dict[str, Any] = {}
self._fernet: Optional[Fernet] = None self._fernet: Optional[Fernet] = None
@ -45,7 +49,7 @@ class ConfigManager:
else: else:
self.database_path = self.base_dir / "database" / "music_library.db" self.database_path = self.base_dir / "database" / "music_library.db"
print(f"Database path set to: {self.database_path}") logger.info(f"Database path set to: {self.database_path}")
self.load_config(str(self.config_path)) self.load_config(str(self.config_path))
@ -107,7 +111,7 @@ class ConfigManager:
try: try:
import shutil import shutil
shutil.move(str(old_key_file), str(key_file)) shutil.move(str(old_key_file), str(key_file))
print(f"[MIGRATE] Moved encryption key to {key_file}") logger.info(f"Moved encryption key to {key_file}")
except Exception: except Exception:
key_file = old_key_file # Fall back to old location key_file = old_key_file # Fall back to old location
if key_file.exists(): if key_file.exists():
@ -155,8 +159,10 @@ class ConfigManager:
return decrypted return decrypted
except InvalidToken: except InvalidToken:
# Key mismatch — encrypted with a different key (key file deleted/replaced) # Key mismatch — encrypted with a different key (key file deleted/replaced)
print(f"[ERROR] Failed to decrypt a config value — encryption key may have changed. " logger.error(
f"Re-enter credentials in Settings or restore the original .encryption_key file.") "Failed to decrypt a config value — encryption key may have changed. "
"Re-enter credentials in Settings or restore the original .encryption_key file."
)
return value return value
except Exception: except Exception:
return value return value
@ -243,11 +249,11 @@ class ConfigManager:
needs_migration = True needs_migration = True
break break
if needs_migration: if needs_migration:
print("[MIGRATE] Encrypting sensitive config values at rest...") logger.info("Encrypting sensitive config values at rest...")
self._save_to_database(self.config_data) self._save_to_database(self.config_data)
print("[OK] Sensitive config values encrypted successfully") logger.info("Sensitive config values encrypted successfully")
except Exception as e: except Exception as e:
print(f"[WARN] Could not migrate encryption: {e}") logger.warning(f"Could not migrate encryption: {e}")
def _ensure_database_exists(self): def _ensure_database_exists(self):
"""Ensure database file and metadata table exist""" """Ensure database file and metadata table exist"""
@ -271,7 +277,7 @@ class ConfigManager:
conn.commit() conn.commit()
conn.close() conn.close()
except Exception as e: except Exception as e:
print(f"Warning: Could not ensure database exists: {e}") logger.warning(f"Could not ensure database exists: {e}")
def _load_from_database(self) -> Optional[Dict[str, Any]]: def _load_from_database(self) -> Optional[Dict[str, Any]]:
"""Load configuration from database, decrypting sensitive values.""" """Load configuration from database, decrypting sensitive values."""
@ -289,13 +295,13 @@ class ConfigManager:
config_data = json.loads(row[0]) config_data = json.loads(row[0])
# Decrypt sensitive values (gracefully handles plaintext migration) # Decrypt sensitive values (gracefully handles plaintext migration)
config_data = self._decrypt_sensitive(config_data) config_data = self._decrypt_sensitive(config_data)
print("[OK] Configuration loaded from database") logger.info("Configuration loaded from database")
return config_data return config_data
else: else:
return None return None
except Exception as e: except Exception as e:
print(f"Warning: Could not load config from database: {e}") logger.warning(f"Could not load config from database: {e}")
return None return None
finally: finally:
if conn: if conn:
@ -325,7 +331,7 @@ class ConfigManager:
return True return True
except Exception as e: except Exception as e:
print(f"Error: Could not save config to database: {e}") logger.error(f"Could not save config to database: {e}")
return False return False
finally: finally:
if conn: if conn:
@ -337,12 +343,12 @@ class ConfigManager:
if self.config_path.exists(): if self.config_path.exists():
with open(self.config_path, 'r') as f: with open(self.config_path, 'r') as f:
config_data = json.load(f) config_data = json.load(f)
print(f"[OK] Configuration loaded from {self.config_path}") logger.info(f"Configuration loaded from {self.config_path}")
return config_data return config_data
else: else:
return None return None
except Exception as e: except Exception as e:
print(f"Warning: Could not load config from file: {e}") logger.warning(f"Could not load config from file: {e}")
return None return None
def _get_default_config(self) -> Dict[str, Any]: def _get_default_config(self) -> Dict[str, Any]:
@ -506,7 +512,7 @@ class ConfigManager:
2. config.json (migration from file-based config) 2. config.json (migration from file-based config)
3. Defaults (fresh install) 3. Defaults (fresh install)
""" """
print(f"Loading configuration...") logger.info("Loading configuration...")
# Try loading from database first # Try loading from database first
config_data = self._load_from_database() config_data = self._load_from_database()
@ -519,30 +525,30 @@ class ConfigManager:
return return
# Database is empty - try migration from config.json # Database is empty - try migration from config.json
print(f"Configuration not found in database. Attempting migration from: {self.config_path}") logger.info(f"Configuration not found in database. Attempting migration from: {self.config_path}")
config_data = self._load_from_config_file() config_data = self._load_from_config_file()
if config_data: if config_data:
# Migrate from config.json to database # Migrate from config.json to database
print("[MIGRATE] Migrating configuration from config.json to database...") logger.info("Migrating configuration from config.json to database...")
if self._save_to_database(config_data): if self._save_to_database(config_data):
print("[OK] Configuration migrated successfully to database.") logger.info("Configuration migrated successfully to database.")
self.config_data = config_data self.config_data = config_data
return return
else: else:
print("[WARN] Migration failed - using file-based config temporarily.") logger.warning("Migration failed - using file-based config temporarily.")
self.config_data = config_data self.config_data = config_data
return return
# No config.json either - use defaults # No config.json either - use defaults
print("[INFO] No existing configuration found (DB or File) - using defaults") logger.info("No existing configuration found (DB or File) - using defaults")
config_data = self._get_default_config() config_data = self._get_default_config()
# Try to save defaults to database # Try to save defaults to database
if self._save_to_database(config_data): if self._save_to_database(config_data):
print("[OK] Default configuration saved to database") logger.info("Default configuration saved to database")
else: else:
print("[WARN] Could not save defaults to database - using in-memory config") logger.warning("Could not save defaults to database - using in-memory config")
self.config_data = config_data self.config_data = config_data
@ -558,14 +564,14 @@ class ConfigManager:
if not success: if not success:
# Fallback: Try to save to config.json if database fails # Fallback: Try to save to config.json if database fails
print("[WARN] Database save failed - attempting file fallback") logger.warning("Database save failed - attempting file fallback")
try: try:
self.config_path.parent.mkdir(parents=True, exist_ok=True) self.config_path.parent.mkdir(parents=True, exist_ok=True)
with open(self.config_path, 'w') as f: with open(self.config_path, 'w') as f:
json.dump(self.config_data, f, indent=2) json.dump(self.config_data, f, indent=2)
print("[OK] Configuration saved to config.json as fallback") logger.info("Configuration saved to config.json as fallback")
except Exception as e: except Exception as e:
print(f"[ERROR] Failed to save configuration: {e}") logger.error(f"Failed to save configuration: {e}")
def get(self, key: str, default: Any = None) -> Any: def get(self, key: str, default: Any = None) -> Any:
keys = key.split('.') keys = key.split('.')

View file

@ -194,7 +194,7 @@ class AcoustIDClient:
result = client.fingerprint_and_lookup("/path/to/audio.mp3") result = client.fingerprint_and_lookup("/path/to/audio.mp3")
if result: if result:
for mbid in result['recording_mbids']: for mbid in result['recording_mbids']:
print(f"Match: {mbid}") logger.info(f"Match: {mbid}")
""" """
def __init__(self): def __init__(self):

View file

@ -12,6 +12,11 @@ import threading
import time import time
from collections import deque, defaultdict from collections import deque, defaultdict
from utils.logging_config import get_logger
logger = get_logger("api_call_tracker")
# Known rate limits per service (calls/minute) # Known rate limits per service (calls/minute)
RATE_LIMITS = { RATE_LIMITS = {
@ -281,7 +286,7 @@ class ApiCallTracker:
with open(_PERSIST_PATH, 'w') as f: with open(_PERSIST_PATH, 'w') as f:
json.dump({'ts': now, 'history': data, 'events': events}, f) json.dump({'ts': now, 'history': data, 'events': events}, f)
except Exception as e: except Exception as e:
print(f"[ApiCallTracker] Failed to save history: {e}") logger.error(f"[ApiCallTracker] Failed to save history: {e}")
def _load(self): def _load(self):
"""Restore 24h minute history from disk. Called on init.""" """Restore 24h minute history from disk. Called on init."""
@ -305,9 +310,9 @@ class ApiCallTracker:
for e in events: for e in events:
if e.get('ts', 0) >= cutoff: if e.get('ts', 0) >= cutoff:
self._events.append(e) self._events.append(e)
print(f"[ApiCallTracker] Restored history for {len(history)} services, {len(events)} events") logger.info(f"[ApiCallTracker] Restored history for {len(history)} services, {len(events)} events")
except Exception as e: except Exception as e:
print(f"[ApiCallTracker] Failed to load history: {e}") logger.error(f"[ApiCallTracker] Failed to load history: {e}")
# Singleton instance # Singleton instance

View file

@ -421,7 +421,7 @@ class MusicMatchingEngine:
if is_likely_album and 4 <= len(potential_album_part) <= 30: if is_likely_album and 4 <= len(potential_album_part) <= 30:
cleaned_title = re.sub(dash_pattern, '', track_title).strip() cleaned_title = re.sub(dash_pattern, '', track_title).strip()
print(f"Heuristic album detection: '{original_title}''{cleaned_title}' (removed: '{potential_album_part}')") logger.debug(f"Heuristic album detection: '{original_title}''{cleaned_title}' (removed: '{potential_album_part}')")
return cleaned_title, True return cleaned_title, True
return track_title, False return track_title, False
@ -1004,13 +1004,13 @@ class MusicMatchingEngine:
# Debug logging for troubleshooting # Debug logging for troubleshooting
if scored_results and not confident_results: if scored_results and not confident_results:
print(f"DEBUG: Found {len(scored_results)} scored results but none met confidence threshold 0.58") logger.debug(f"Found {len(scored_results)} scored results but none met confidence threshold 0.58")
for i, result in enumerate(sorted_results[:3]): # Show top 3 for i, result in enumerate(sorted_results[:3]): # Show top 3
print(f" {i+1}. {result.confidence:.3f} - {getattr(result, 'version_type', 'unknown')} - {result.filename[:60]}...") logger.debug(f" {i+1}. {result.confidence:.3f} - {getattr(result, 'version_type', 'unknown')} - {result.filename[:60]}...")
elif confident_results: elif confident_results:
print(f"DEBUG: {len(confident_results)} results passed confidence threshold 0.58") logger.debug(f"{len(confident_results)} results passed confidence threshold 0.58")
for i, result in enumerate(confident_results[:3]): # Show top 3 for i, result in enumerate(confident_results[:3]): # Show top 3
print(f" {i+1}. {result.confidence:.3f} - {getattr(result, 'version_type', 'unknown')} - {result.filename[:60]}...") logger.debug(f" {i+1}. {result.confidence:.3f} - {getattr(result, 'version_type', 'unknown')} - {result.filename[:60]}...")
return confident_results return confident_results

View file

@ -1045,7 +1045,7 @@ def check_album_completion(
if total_tracks == 0 and album_id: if total_tracks == 0 and album_id:
logger.debug("No track count found for '%s' (%s)", album_name, album_id) logger.debug("No track count found for '%s' (%s)", album_name, album_id)
print(f"Checking album: '{album_name}' ({total_tracks} tracks)") logger.debug(f"Checking album: '{album_name}' ({total_tracks} tracks)")
formats = [] formats = []
# Check if album exists in database with completeness info # Check if album exists in database with completeness info
@ -1060,7 +1060,7 @@ def check_album_completion(
server_source=active_server server_source=active_server
) )
except Exception as db_error: except Exception as db_error:
print(f"Database error for album '{album_name}': {db_error}") logger.error(f"Database error for album '{album_name}': {db_error}")
return { return {
"id": album_id, "id": album_id,
"name": album_name, "name": album_name,
@ -1088,7 +1088,7 @@ def check_album_completion(
else: else:
status = "missing" status = "missing"
print(f" Result: {owned_tracks}/{expected_tracks or total_tracks} tracks ({completion_percentage:.1f}%) - {status}") logger.debug(f" Result: {owned_tracks}/{expected_tracks or total_tracks} tracks ({completion_percentage:.1f}%) - {status}")
return { return {
"id": album_id, "id": album_id,
@ -1103,7 +1103,7 @@ def check_album_completion(
} }
except Exception as e: except Exception as e:
print(f"Error checking album completion for '{album_data.get('name', 'Unknown')}': {e}") logger.error(f"Error checking album completion for '{album_data.get('name', 'Unknown')}': {e}")
return { return {
"id": album_data.get('id', ''), "id": album_data.get('id', ''),
"name": album_data.get('name', 'Unknown'), "name": album_data.get('name', 'Unknown'),
@ -1137,7 +1137,7 @@ def check_single_completion(
if total_tracks == 0: if total_tracks == 0:
total_tracks = _resolve_completion_track_total(single_data, source_chain) or 1 total_tracks = _resolve_completion_track_total(single_data, source_chain) or 1
print(f"Checking {album_type}: '{single_name}' ({total_tracks} tracks)") logger.debug(f"Checking {album_type}: '{single_name}' ({total_tracks} tracks)")
if album_type == 'ep' or total_tracks > 1: if album_type == 'ep' or total_tracks > 1:
try: try:
@ -1151,7 +1151,7 @@ def check_single_completion(
server_source=active_server server_source=active_server
) )
except Exception as db_error: except Exception as db_error:
print(f"Database error for EP '{single_name}': {db_error}") logger.error(f"Database error for EP '{single_name}': {db_error}")
owned_tracks, expected_tracks, confidence = 0, total_tracks, 0.0 owned_tracks, expected_tracks, confidence = 0, total_tracks, 0.0
db_album = None db_album = None
@ -1167,7 +1167,7 @@ def check_single_completion(
else: else:
status = "missing" status = "missing"
print(f" EP Result: {owned_tracks}/{expected_tracks or total_tracks} tracks ({completion_percentage:.1f}%) - {status}") logger.debug(f" EP Result: {owned_tracks}/{expected_tracks or total_tracks} tracks ({completion_percentage:.1f}%) - {status}")
return { return {
"id": single_id, "id": single_id,
@ -1192,7 +1192,7 @@ def check_single_completion(
server_source=active_server server_source=active_server
) )
except Exception as db_error: except Exception as db_error:
print(f"Database error for single '{single_name}': {db_error}") logger.error(f"Database error for single '{single_name}': {db_error}")
db_track, confidence = None, 0.0 db_track, confidence = None, 0.0
owned_tracks = 1 if db_track else 0 owned_tracks = 1 if db_track else 0
@ -1208,7 +1208,7 @@ def check_single_completion(
elif ext: elif ext:
formats = [ext] formats = [ext]
print(f" Single Result: {owned_tracks}/1 tracks ({completion_percentage:.1f}%) - {status}") logger.debug(f" Single Result: {owned_tracks}/1 tracks ({completion_percentage:.1f}%) - {status}")
return { return {
"id": single_id, "id": single_id,
@ -1224,7 +1224,7 @@ def check_single_completion(
} }
except Exception as e: except Exception as e:
print(f"Error checking single/EP completion for '{single_data.get('name', 'Unknown')}': {e}") logger.error(f"Error checking single/EP completion for '{single_data.get('name', 'Unknown')}': {e}")
return { return {
"id": single_data.get('id', ''), "id": single_data.get('id', ''),
"name": single_data.get('name', 'Unknown'), "name": single_data.get('name', 'Unknown'),

View file

@ -1,12 +1,18 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""Reports basic system info — useful for debugging Docker setups.""" """Reports basic system info — useful for debugging Docker setups."""
import logging
import os import os
import platform import platform
import shutil import shutil
print(f"Platform: {platform.system()} {platform.release()}") if not logging.getLogger().handlers:
print(f"Python: {platform.python_version()}") logging.basicConfig(level=logging.INFO, format="%(message)s")
print(f"Working Dir: {os.getcwd()}")
logger = logging.getLogger("system_info")
logger.info(f"Platform: {platform.system()} {platform.release()}")
logger.info(f"Python: {platform.python_version()}")
logger.info(f"Working Dir: {os.getcwd()}")
# Disk usage for common SoulSync paths # Disk usage for common SoulSync paths
for path in ['/app/downloads', '/app/Transfer', '/app/data', './downloads', './Transfer']: for path in ['/app/downloads', '/app/Transfer', '/app/data', './downloads', './Transfer']:
@ -14,4 +20,4 @@ for path in ['/app/downloads', '/app/Transfer', '/app/data', './downloads', './T
usage = shutil.disk_usage(path) usage = shutil.disk_usage(path)
free_gb = usage.free / (1024**3) free_gb = usage.free / (1024**3)
total_gb = usage.total / (1024**3) total_gb = usage.total / (1024**3)
print(f"Disk {path}: {free_gb:.1f} GB free / {total_gb:.1f} GB total") logger.info(f"Disk {path}: {free_gb:.1f} GB free / {total_gb:.1f} GB total")

View file

@ -7,14 +7,12 @@ Run this script to identify issues with iTunes data population:
- Discovery pool tracks by source - Discovery pool tracks by source
- Recent albums by source - Recent albums by source
- Curated playlists status - Curated playlists status
Usage:
python tools/diagnose_itunes_discover.py
""" """
import sys
import os
import json import json
import logging
import os
import sys
# Add parent directory to path for imports # Add parent directory to path for imports
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
@ -22,36 +20,42 @@ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from database.music_database import MusicDatabase from database.music_database import MusicDatabase
if not logging.getLogger().handlers:
logging.basicConfig(level=logging.INFO, format="%(message)s")
logger = logging.getLogger("diagnose_itunes_discover")
def _section(title: str) -> None:
logger.info("")
logger.info(title)
logger.info("-" * 40)
def diagnose_itunes_discover(): def diagnose_itunes_discover():
"""Run diagnostic checks for iTunes discover data.""" """Run diagnostic checks for iTunes discover data."""
print("=" * 60) logger.info("=" * 60)
print("iTunes Discover Page Diagnostic Report") logger.info("iTunes Discover Page Diagnostic Report")
print("=" * 60) logger.info("=" * 60)
db = MusicDatabase() db = MusicDatabase()
# 1. Check Similar Artists # 1. Check Similar Artists
print("\n[1] SIMILAR ARTISTS") _section("[1] SIMILAR ARTISTS")
print("-" * 40)
try: try:
with db._get_connection() as conn: with db._get_connection() as conn:
cursor = conn.cursor() cursor = conn.cursor()
# Total similar artists
cursor.execute("SELECT COUNT(*) as total FROM similar_artists") cursor.execute("SELECT COUNT(*) as total FROM similar_artists")
total = cursor.fetchone()['total'] total = cursor.fetchone()['total']
# With iTunes IDs
cursor.execute("SELECT COUNT(*) as count FROM similar_artists WHERE similar_artist_itunes_id IS NOT NULL") cursor.execute("SELECT COUNT(*) as count FROM similar_artists WHERE similar_artist_itunes_id IS NOT NULL")
with_itunes = cursor.fetchone()['count'] with_itunes = cursor.fetchone()['count']
# With Spotify IDs
cursor.execute("SELECT COUNT(*) as count FROM similar_artists WHERE similar_artist_spotify_id IS NOT NULL") cursor.execute("SELECT COUNT(*) as count FROM similar_artists WHERE similar_artist_spotify_id IS NOT NULL")
with_spotify = cursor.fetchone()['count'] with_spotify = cursor.fetchone()['count']
# With both
cursor.execute(""" cursor.execute("""
SELECT COUNT(*) as count FROM similar_artists SELECT COUNT(*) as count FROM similar_artists
WHERE similar_artist_itunes_id IS NOT NULL WHERE similar_artist_itunes_id IS NOT NULL
@ -59,34 +63,29 @@ def diagnose_itunes_discover():
""") """)
with_both = cursor.fetchone()['count'] with_both = cursor.fetchone()['count']
print(f" Total similar artists: {total}") logger.info(f" Total similar artists: {total}")
print(f" With iTunes ID: {with_itunes} ({100*with_itunes/total:.1f}%)" if total > 0 else " With iTunes ID: 0") logger.info(f" With iTunes ID: {with_itunes} ({100 * with_itunes / total:.1f}%)" if total > 0 else " With iTunes ID: 0")
print(f" With Spotify ID: {with_spotify} ({100*with_spotify/total:.1f}%)" if total > 0 else " With Spotify ID: 0") logger.info(f" With Spotify ID: {with_spotify} ({100 * with_spotify / total:.1f}%)" if total > 0 else " With Spotify ID: 0")
print(f" With BOTH IDs: {with_both} ({100*with_both/total:.1f}%)" if total > 0 else " With BOTH IDs: 0") logger.info(f" With BOTH IDs: {with_both} ({100 * with_both / total:.1f}%)" if total > 0 else " With BOTH IDs: 0")
if with_itunes == 0 and total > 0: if with_itunes == 0 and total > 0:
print(" [CRITICAL] No similar artists have iTunes IDs - Hero section will be empty!") logger.critical("No similar artists have iTunes IDs - Hero section will be empty!")
elif with_itunes < total * 0.5: elif with_itunes < total * 0.5:
print(" [WARNING] Less than 50% of similar artists have iTunes IDs") logger.warning("Less than 50% of similar artists have iTunes IDs")
else: else:
print(" [OK] iTunes coverage is adequate") logger.info("iTunes coverage is adequate")
except Exception as e: except Exception as e:
print(f" [ERROR] Could not check similar artists: {e}") logger.error(f"Could not check similar artists: {e}")
# 2. Check Discovery Pool # 2. Check Discovery Pool
print("\n[2] DISCOVERY POOL") _section("[2] DISCOVERY POOL")
print("-" * 40)
try: try:
with db._get_connection() as conn: with db._get_connection() as conn:
cursor = conn.cursor() cursor = conn.cursor()
# Total tracks
cursor.execute("SELECT COUNT(*) as total FROM discovery_pool") cursor.execute("SELECT COUNT(*) as total FROM discovery_pool")
total = cursor.fetchone()['total'] total = cursor.fetchone()['total']
# By source
cursor.execute(""" cursor.execute("""
SELECT source, COUNT(*) as count SELECT source, COUNT(*) as count
FROM discovery_pool FROM discovery_pool
@ -94,33 +93,28 @@ def diagnose_itunes_discover():
""") """)
source_counts = {row['source']: row['count'] for row in cursor.fetchall()} source_counts = {row['source']: row['count'] for row in cursor.fetchall()}
print(f" Total tracks: {total}") logger.info(f" Total tracks: {total}")
print(f" Spotify tracks: {source_counts.get('spotify', 0)}") logger.info(f" Spotify tracks: {source_counts.get('spotify', 0)}")
print(f" iTunes tracks: {source_counts.get('itunes', 0)}") logger.info(f" iTunes tracks: {source_counts.get('itunes', 0)}")
if source_counts.get('itunes', 0) == 0 and total > 0: if source_counts.get('itunes', 0) == 0 and total > 0:
print(" [CRITICAL] No iTunes tracks in discovery pool - Fresh Tape/Archives will be empty!") logger.critical("No iTunes tracks in discovery pool - Fresh Tape/Archives will be empty!")
elif source_counts.get('itunes', 0) < total * 0.3: elif source_counts.get('itunes', 0) < total * 0.3:
print(" [WARNING] Low iTunes track count in discovery pool") logger.warning("Low iTunes track count in discovery pool")
else: else:
print(" [OK] iTunes tracks present") logger.info("iTunes tracks present")
except Exception as e: except Exception as e:
print(f" [ERROR] Could not check discovery pool: {e}") logger.error(f"Could not check discovery pool: {e}")
# 3. Check Recent Albums # 3. Check Recent Albums
print("\n[3] RECENT ALBUMS CACHE") _section("[3] RECENT ALBUMS CACHE")
print("-" * 40)
try: try:
with db._get_connection() as conn: with db._get_connection() as conn:
cursor = conn.cursor() cursor = conn.cursor()
# Total albums
cursor.execute("SELECT COUNT(*) as total FROM discovery_recent_albums") cursor.execute("SELECT COUNT(*) as total FROM discovery_recent_albums")
total = cursor.fetchone()['total'] total = cursor.fetchone()['total']
# By source
cursor.execute(""" cursor.execute("""
SELECT source, COUNT(*) as count SELECT source, COUNT(*) as count
FROM discovery_recent_albums FROM discovery_recent_albums
@ -128,24 +122,21 @@ def diagnose_itunes_discover():
""") """)
source_counts = {row['source']: row['count'] for row in cursor.fetchall()} source_counts = {row['source']: row['count'] for row in cursor.fetchall()}
print(f" Total recent albums: {total}") logger.info(f" Total recent albums: {total}")
print(f" Spotify albums: {source_counts.get('spotify', 0)}") logger.info(f" Spotify albums: {source_counts.get('spotify', 0)}")
print(f" iTunes albums: {source_counts.get('itunes', 0)}") logger.info(f" iTunes albums: {source_counts.get('itunes', 0)}")
if source_counts.get('itunes', 0) == 0 and total > 0: if source_counts.get('itunes', 0) == 0 and total > 0:
print(" [CRITICAL] No iTunes albums cached - Recent Releases section will be empty!") logger.critical("No iTunes albums cached - Recent Releases section will be empty!")
elif source_counts.get('itunes', 0) < 5: elif source_counts.get('itunes', 0) < 5:
print(" [WARNING] Very few iTunes albums cached") logger.warning("Very few iTunes albums cached")
else: else:
print(" [OK] iTunes albums cached") logger.info("iTunes albums cached")
except Exception as e: except Exception as e:
print(f" [ERROR] Could not check recent albums: {e}") logger.error(f"Could not check recent albums: {e}")
# 4. Check Curated Playlists # 4. Check Curated Playlists
print("\n[4] CURATED PLAYLISTS") _section("[4] CURATED PLAYLISTS")
print("-" * 40)
try: try:
with db._get_connection() as conn: with db._get_connection() as conn:
cursor = conn.cursor() cursor = conn.cursor()
@ -156,7 +147,7 @@ def diagnose_itunes_discover():
'release_radar_itunes', 'release_radar_itunes',
'discovery_weekly', 'discovery_weekly',
'discovery_weekly_spotify', 'discovery_weekly_spotify',
'discovery_weekly_itunes' 'discovery_weekly_itunes',
] ]
for playlist_type in playlists_to_check: for playlist_type in playlists_to_check:
@ -174,9 +165,8 @@ def diagnose_itunes_discover():
else: else:
status = "[NOT FOUND]" status = "[NOT FOUND]"
print(f" {playlist_type}: {status}") logger.info(f" {playlist_type}: {status}")
# Check iTunes-specific playlists
cursor.execute(""" cursor.execute("""
SELECT track_ids_json FROM discovery_curated_playlists SELECT track_ids_json FROM discovery_curated_playlists
WHERE playlist_type = 'release_radar_itunes' WHERE playlist_type = 'release_radar_itunes'
@ -190,49 +180,43 @@ def diagnose_itunes_discover():
itunes_dw = cursor.fetchone() itunes_dw = cursor.fetchone()
if not itunes_rr or len(json.loads(itunes_rr['track_ids_json'])) == 0: if not itunes_rr or len(json.loads(itunes_rr['track_ids_json'])) == 0:
print("\n [CRITICAL] release_radar_itunes is empty or missing!") logger.critical("release_radar_itunes is empty or missing!")
if not itunes_dw or len(json.loads(itunes_dw['track_ids_json'])) == 0: if not itunes_dw or len(json.loads(itunes_dw['track_ids_json'])) == 0:
print(" [CRITICAL] discovery_weekly_itunes is empty or missing!") logger.critical("discovery_weekly_itunes is empty or missing!")
except Exception as e: except Exception as e:
print(f" [ERROR] Could not check curated playlists: {e}") logger.error(f"Could not check curated playlists: {e}")
# 5. Check Watchlist Artists # 5. Check Watchlist Artists
print("\n[5] WATCHLIST ARTISTS") _section("[5] WATCHLIST ARTISTS")
print("-" * 40)
try: try:
with db._get_connection() as conn: with db._get_connection() as conn:
cursor = conn.cursor() cursor = conn.cursor()
# Total artists
cursor.execute("SELECT COUNT(*) as total FROM watchlist_artists") cursor.execute("SELECT COUNT(*) as total FROM watchlist_artists")
total = cursor.fetchone()['total'] total = cursor.fetchone()['total']
# With iTunes IDs
cursor.execute("SELECT COUNT(*) as count FROM watchlist_artists WHERE itunes_artist_id IS NOT NULL") cursor.execute("SELECT COUNT(*) as count FROM watchlist_artists WHERE itunes_artist_id IS NOT NULL")
with_itunes = cursor.fetchone()['count'] with_itunes = cursor.fetchone()['count']
# With Spotify IDs
cursor.execute("SELECT COUNT(*) as count FROM watchlist_artists WHERE spotify_artist_id IS NOT NULL") cursor.execute("SELECT COUNT(*) as count FROM watchlist_artists WHERE spotify_artist_id IS NOT NULL")
with_spotify = cursor.fetchone()['count'] with_spotify = cursor.fetchone()['count']
print(f" Total watchlist artists: {total}") logger.info(f" Total watchlist artists: {total}")
print(f" With iTunes ID: {with_itunes} ({100*with_itunes/total:.1f}%)" if total > 0 else " With iTunes ID: 0") logger.info(f" With iTunes ID: {with_itunes} ({100 * with_itunes / total:.1f}%)" if total > 0 else " With iTunes ID: 0")
print(f" With Spotify ID: {with_spotify} ({100*with_spotify/total:.1f}%)" if total > 0 else " With Spotify ID: 0") logger.info(f" With Spotify ID: {with_spotify} ({100 * with_spotify / total:.1f}%)" if total > 0 else " With Spotify ID: 0")
if with_itunes == 0 and total > 0: if with_itunes == 0 and total > 0:
print(" [WARNING] No watchlist artists have iTunes IDs - source artist data limited") logger.warning("No watchlist artists have iTunes IDs - source artist data limited")
except Exception as e: except Exception as e:
print(f" [ERROR] Could not check watchlist artists: {e}") logger.error(f"Could not check watchlist artists: {e}")
# Summary logger.info("")
print("\n" + "=" * 60) logger.info("=" * 60)
print("SUMMARY & RECOMMENDED ACTIONS") logger.info("SUMMARY & RECOMMENDED ACTIONS")
print("=" * 60) logger.info("=" * 60)
print(""" logger.info(
If you see [CRITICAL] or [WARNING] messages above, follow these steps: """
If you see critical or warning messages above, follow these steps:
QUICK FIX - Force Refresh Discover Data: QUICK FIX - Force Refresh Discover Data:
----------------------------------------- -----------------------------------------
@ -263,7 +247,8 @@ ROOT CAUSE NOTES:
The discover page will now fall back to watchlist artists if similar The discover page will now fall back to watchlist artists if similar
artists are not available, so basic functionality should still work. artists are not available, so basic functionality should still work.
""") """.strip()
)
if __name__ == '__main__': if __name__ == '__main__':

View file

@ -109,7 +109,7 @@ def set_log_level(level: str) -> bool:
root_logger.info(f"Log level changed to: {level.upper()}") root_logger.info(f"Log level changed to: {level.upper()}")
return True return True
except Exception as e: except Exception as e:
print(f"Error setting log level: {e}") logging.getLogger("newmusic").error(f"Error setting log level: {e}")
return False return False
def get_current_log_level() -> str: def get_current_log_level() -> str:
@ -117,4 +117,4 @@ def get_current_log_level() -> str:
root_logger = logging.getLogger("newmusic") root_logger = logging.getLogger("newmusic")
return logging.getLevelName(root_logger.level) return logging.getLevelName(root_logger.level)
main_logger = get_logger("main") main_logger = get_logger("main")