feat: sidebar scroll + log persistente (v1.11.1)
- Sidebar diventata troppo lunga (14+ tab): overflow-y auto sulla nav, brand e footer restano sticky. Scrollbar 6px tema-coerente - Nuovo core/applog.py: TimedRotatingFileHandler daily con 7 giorni di retention. NON tocca sys.stdout/stderr (causa segfault WebKit su macOS); cattura tracebacks via sys.excepthook + threading.excepthook - bridge._log mirror su logger persistente (ui.<view>) → tutto quello che vedi nei log delle tab e' anche su file - Bridge: get_log_file_path() + open_log_folder() per la nuova sezione Impostazioni con path visibile e bottone "Apri cartella"
This commit is contained in:
1 parent
cdbb1d85d5
commit
ed1059bf8c
8 files changed
+228
-1
No files matched your search
+134
@@ -0,0 +1,134 @@
|
||||
"""Setup logging persistente su file rotante.
|
||||
|
||||
Chiamato in `main.py` prima di qualsiasi import pesante. Il file di log
|
||||
vive in:
|
||||
- macOS: ~/Library/Application Support/MusicTools/logs/app.log
|
||||
- Windows: %APPDATA%/MusicTools/logs/app.log
|
||||
- Dev: <project_root>/logs/app.log
|
||||
|
||||
Rotazione: 1 file al giorno, mantiene gli ultimi 7. Cattura anche stdout
|
||||
e stderr per non perdere `print()` o traceback che l'app windowed
|
||||
altrimenti scarterebbe.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import logging.handlers
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
_setup_done = False
|
||||
|
||||
|
||||
def _logs_dir() -> Path:
|
||||
"""Directory dei log — parallela a config.json."""
|
||||
if getattr(sys, "frozen", False):
|
||||
if sys.platform == "win32":
|
||||
base = Path(os.environ.get("APPDATA", Path.home()))
|
||||
else:
|
||||
base = Path.home() / "Library" / "Application Support"
|
||||
d = base / "MusicTools" / "logs"
|
||||
else:
|
||||
d = Path(__file__).resolve().parent.parent / "logs"
|
||||
d.mkdir(parents=True, exist_ok=True)
|
||||
return d
|
||||
|
||||
|
||||
def log_file_path() -> Path:
|
||||
return _logs_dir() / "app.log"
|
||||
|
||||
|
||||
def _install_excepthook() -> None:
|
||||
"""Cattura ogni uncaught exception dentro il file di log.
|
||||
NON tocca sys.stdout/stderr (romperebbe WebKit)."""
|
||||
import traceback
|
||||
logger = logging.getLogger("uncaught")
|
||||
_orig = sys.excepthook
|
||||
|
||||
def _hook(exc_type, exc_value, tb):
|
||||
try:
|
||||
logger.error(
|
||||
"Uncaught: %s: %s\n%s",
|
||||
exc_type.__name__,
|
||||
exc_value,
|
||||
"".join(traceback.format_exception(exc_type, exc_value, tb)),
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
_orig(exc_type, exc_value, tb)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
sys.excepthook = _hook
|
||||
|
||||
# Anche per exception in threading (Python 3.8+)
|
||||
try:
|
||||
_orig_thread = threading_excepthook = getattr(__import__("threading"), "excepthook", None)
|
||||
|
||||
def _thread_hook(args):
|
||||
try:
|
||||
logger.error(
|
||||
"Thread uncaught: %s: %s\n%s",
|
||||
args.exc_type.__name__,
|
||||
args.exc_value,
|
||||
"".join(traceback.format_exception(args.exc_type, args.exc_value, args.exc_traceback)),
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
if _orig_thread:
|
||||
try:
|
||||
_orig_thread(args)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if threading_excepthook is not None:
|
||||
__import__("threading").excepthook = _thread_hook
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def setup() -> Path:
|
||||
"""Configura il logger root — NON tocca stdout/stderr per non rompere
|
||||
WebKit (segfault). Cattura traceback via sys.excepthook + logger.
|
||||
"""
|
||||
global _setup_done
|
||||
path = log_file_path()
|
||||
if _setup_done:
|
||||
return path
|
||||
_setup_done = True
|
||||
|
||||
root = logging.getLogger()
|
||||
root.setLevel(logging.INFO)
|
||||
|
||||
fmt = logging.Formatter(
|
||||
fmt="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
||||
datefmt="%Y-%m-%d %H:%M:%S",
|
||||
)
|
||||
|
||||
# File handler con rotazione giornaliera (max 7 file = 7 giorni)
|
||||
fh = logging.handlers.TimedRotatingFileHandler(
|
||||
str(path),
|
||||
when="midnight",
|
||||
backupCount=7,
|
||||
encoding="utf-8",
|
||||
utc=False,
|
||||
)
|
||||
fh.setFormatter(fmt)
|
||||
fh.setLevel(logging.INFO)
|
||||
root.addHandler(fh)
|
||||
|
||||
# Se in dev, mantieni anche console (utile durante lavoro)
|
||||
if not getattr(sys, "frozen", False):
|
||||
ch = logging.StreamHandler(sys.__stderr__)
|
||||
ch.setFormatter(fmt)
|
||||
ch.setLevel(logging.INFO)
|
||||
root.addHandler(ch)
|
||||
|
||||
_install_excepthook()
|
||||
|
||||
root.info(f"MusicTools log start — file: {path}")
|
||||
return path
|
||||
Reference in new issue
Block a user