1+ import faulthandler
12import logging
23import platform
34import sys
1718
1819_configured = False
1920_log_file_path : Path | None = None
21+ _crash_log_file = None
22+
23+
24+ def _install_exception_hooks () -> None :
25+ def exc_hook (exc_type , exc , tb ):
26+ logging .getLogger ("uncaught" ).critical (
27+ "Uncaught exception" ,
28+ exc_info = (exc_type , exc , tb ),
29+ )
30+
31+ sys .excepthook = exc_hook
32+
33+ def thread_exc_hook (args : threading .ExceptHookArgs ) -> None :
34+ logging .getLogger ("uncaught" ).critical (
35+ "Uncaught thread exception" ,
36+ exc_info = (args .exc_type , args .exc_value , args .exc_traceback ),
37+ )
38+
39+ threading .excepthook = thread_exc_hook
40+
41+ def unraisable_hook (args : sys .UnraisableHookArgs ) -> None :
42+ logging .getLogger ("uncaught" ).error (
43+ "Unraisable exception in %r" ,
44+ args .object ,
45+ exc_info = (args .exc_type , args .exc_value , args .exc_traceback ),
46+ )
47+
48+ sys .unraisablehook = unraisable_hook
49+
50+
51+ def setup_bootstrap_logging () -> None :
52+ root = logging .getLogger ()
53+ if root .handlers :
54+ _install_exception_hooks ()
55+ return
56+
57+ fmt = logging .Formatter ("%(asctime)s ¦ %(levelname)s ¦ %(name)s ¦ %(message)s" )
58+ sh = logging .StreamHandler (sys .stderr )
59+ sh .setLevel (logging .INFO )
60+ sh .setFormatter (fmt )
61+ root .setLevel (logging .DEBUG )
62+ root .addHandler (sh )
63+ _install_exception_hooks ()
2064
2165
2266def setup_logging (_app : QApplication ) -> Path :
23- global _configured , _log_file_path
67+ global _configured , _log_file_path , _crash_log_file
2468 if _configured :
2569 assert _log_file_path is not None
2670 return _log_file_path
@@ -53,21 +97,7 @@ def setup_logging(_app: QApplication) -> Path:
5397 sh .setFormatter (fmt )
5498 root .addHandler (sh )
5599
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
100+ _install_exception_hooks ()
71101
72102 def qt_handler (mode : QtMsgType , context : QMessageLogContext , message : str ) -> None :
73103 if mode == QtMsgType .QtDebugMsg :
@@ -86,6 +116,19 @@ def qt_handler(mode: QtMsgType, context: QMessageLogContext, message: str) -> No
86116 logging .getLogger ("qt" ).log (level , "%s%s" , message , suffix )
87117
88118 qInstallMessageHandler (qt_handler )
119+ _app .aboutToQuit .connect (
120+ lambda : logging .getLogger ("clinical_dbs_annotator" ).info ("Application shutdown" )
121+ )
122+
123+ crash_log_path = log_dir / "clinical-dbs-annotator-crash.log"
124+ try :
125+ _crash_log_file = open (crash_log_path , "a" , encoding = "utf-8" )
126+ faulthandler .enable (file = _crash_log_file , all_threads = True )
127+ except Exception :
128+ logging .getLogger ("clinical_dbs_annotator" ).exception (
129+ "Failed to enable faulthandler with crash log %s" ,
130+ crash_log_path ,
131+ )
89132
90133 logging .getLogger ("clinical_dbs_annotator" ).info (
91134 "Started v%s Python %s | %s | log=%s" ,
0 commit comments