Skip to content

Commit ca99034

Browse files
authored
Merge pull request #20 from richardkoehler/feature/implement-logging
feat: implement logging
2 parents 58b432b + a7fd775 commit ca99034

2 files changed

Lines changed: 115 additions & 38 deletions

File tree

src/clinical_dbs_annotator/__main__.py

Lines changed: 14 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@
55
and main window creation.
66
"""
77

8-
import os
8+
import logging
99
import sys
10-
import tempfile
11-
import traceback
1210

1311
from PySide6.QtWidgets import QApplication
1412

13+
from .logging_config import setup_logging
1514
from .utils import get_theme_manager
1615
from .views import WizardWindow
1716

@@ -23,46 +22,23 @@ def main() -> int:
2322
Returns:
2423
Exit code (0 for success)
2524
"""
26-
try:
27-
# Check for required dependencies
28-
try:
29-
import pytz # noqa: F401
30-
except ImportError:
31-
print("Error: pytz is required. Install with: pip install pytz")
32-
return 1
33-
34-
# Create application
35-
app = QApplication(sys.argv)
25+
app = QApplication(sys.argv)
3626

37-
# Set application metadata
38-
app.setApplicationName("Clinical DBS Annotator")
39-
app.setOrganizationName("BML")
27+
app.setApplicationName("Clinical DBS Annotator")
28+
app.setOrganizationName("BML")
4029

41-
# Load and apply theme
42-
theme_manager = get_theme_manager()
43-
try:
44-
theme_manager.apply_theme(theme_manager.get_current_theme(), app)
45-
except Exception as e:
46-
print(f"Warning: Could not load theme: {e}")
47-
print("Continuing with default styling...")
30+
setup_logging(app)
4831

49-
# Create and show main window
50-
window = WizardWindow(app)
51-
window.show()
32+
theme_manager = get_theme_manager()
33+
try:
34+
theme_manager.apply_theme(theme_manager.get_current_theme(), app)
35+
except Exception as e:
36+
logging.warning("Could not load theme: %s", e)
5237

53-
# Run application
54-
return app.exec()
38+
window = WizardWindow(app)
39+
window.show()
5540

56-
except Exception:
57-
print("FATAL ERROR:")
58-
traceback.print_exc()
59-
try:
60-
log_path = os.path.join(tempfile.gettempdir(), "ClinicalDBSAnnot_crash.log")
61-
with open(log_path, "w", encoding="utf-8") as f:
62-
f.write(traceback.format_exc())
63-
except Exception:
64-
pass
65-
return 1
41+
return app.exec()
6642

6743

6844
if __name__ == "__main__":
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import logging
2+
import platform
3+
import sys
4+
import threading
5+
from logging.handlers import RotatingFileHandler
6+
from pathlib import Path
7+
8+
from PySide6.QtCore import (
9+
QMessageLogContext,
10+
QStandardPaths,
11+
QtMsgType,
12+
qInstallMessageHandler,
13+
)
14+
from PySide6.QtWidgets import QApplication
15+
16+
from . import __version__
17+
18+
_configured = False
19+
_log_file_path: Path | None = None
20+
21+
22+
def setup_logging(_app: QApplication) -> Path:
23+
global _configured, _log_file_path
24+
if _configured:
25+
assert _log_file_path is not None
26+
return _log_file_path
27+
28+
base = Path(
29+
QStandardPaths.writableLocation(
30+
QStandardPaths.StandardLocation.AppLocalDataLocation
31+
)
32+
)
33+
log_dir = base / "logs"
34+
log_dir.mkdir(parents=True, exist_ok=True)
35+
log_path = log_dir / "clinical-dbs-annotator.log"
36+
37+
fmt = logging.Formatter("%(asctime)s ¦ %(levelname)s ¦ %(name)s ¦ %(message)s")
38+
root = logging.getLogger()
39+
root.setLevel(logging.DEBUG)
40+
for h in root.handlers[:]:
41+
root.removeHandler(h)
42+
43+
fh = RotatingFileHandler(
44+
log_path, maxBytes=5_000_000, backupCount=3, encoding="utf-8"
45+
)
46+
fh.setLevel(logging.INFO)
47+
fh.setFormatter(fmt)
48+
root.addHandler(fh)
49+
50+
if not getattr(sys, "frozen", False):
51+
sh = logging.StreamHandler(sys.stderr)
52+
sh.setLevel(logging.INFO)
53+
sh.setFormatter(fmt)
54+
root.addHandler(sh)
55+
56+
def exc_hook(exc_type, exc, tb):
57+
logging.getLogger("uncaught").critical(
58+
"Uncaught exception",
59+
exc_info=(exc_type, exc, tb),
60+
)
61+
62+
sys.excepthook = exc_hook
63+
64+
def thread_exc_hook(args: threading.ExceptHookArgs) -> None:
65+
logging.getLogger("uncaught").critical(
66+
"Uncaught thread exception",
67+
exc_info=(args.exc_type, args.exc_value, args.exc_traceback),
68+
)
69+
70+
threading.excepthook = thread_exc_hook
71+
72+
def qt_handler(mode: QtMsgType, context: QMessageLogContext, message: str) -> None:
73+
if mode == QtMsgType.QtDebugMsg:
74+
level = logging.DEBUG
75+
elif mode == QtMsgType.QtInfoMsg:
76+
level = logging.INFO
77+
elif mode == QtMsgType.QtWarningMsg:
78+
level = logging.WARNING
79+
elif mode == QtMsgType.QtCriticalMsg:
80+
level = logging.ERROR
81+
else:
82+
level = logging.CRITICAL
83+
suffix = ""
84+
if context.file:
85+
suffix = f" ({context.file}:{context.line})"
86+
logging.getLogger("qt").log(level, "%s%s", message, suffix)
87+
88+
qInstallMessageHandler(qt_handler)
89+
90+
logging.getLogger("clinical_dbs_annotator").info(
91+
"Started v%s Python %s | %s | log=%s",
92+
__version__,
93+
platform.python_version(),
94+
platform.platform(),
95+
log_path.resolve(),
96+
)
97+
98+
resolved = log_path.resolve()
99+
_configured = True
100+
_log_file_path = resolved
101+
return resolved

0 commit comments

Comments
 (0)