From 9ddc1f5b990d5db27f8ed4875ebba17f95433a9d Mon Sep 17 00:00:00 2001 From: foefl Date: Fri, 29 May 2026 10:44:50 +0200 Subject: [PATCH] additional logging and error handling --- src/wce_crm/backend/backend.py | 9 +++++ src/wce_crm/gui.py | 61 +++++++++++++++++++++++++++++----- src/wce_crm/logging.py | 12 +++++-- 3 files changed, 72 insertions(+), 10 deletions(-) diff --git a/src/wce_crm/backend/backend.py b/src/wce_crm/backend/backend.py index a2c5605..a6e669e 100644 --- a/src/wce_crm/backend/backend.py +++ b/src/wce_crm/backend/backend.py @@ -8,6 +8,7 @@ import polars as pl import sqlalchemy as sql from wce_crm import db +from wce_crm.logging import logger_back as logger class CompanyInfo(TypedDict): @@ -83,6 +84,7 @@ def _transform_for_gui_output( def comp_search_choices() -> tuple[tuple[str, int], ...]: # TODO no reload functionality + logger.debug("[Call backend] comp_search_choices") q = db.DF_CRM_MASTER.lazy() counter = pl.int_range(0, pl.len()).over(pl.col.ma_unternehmensname) q = q.with_columns( @@ -98,6 +100,7 @@ def comp_search_choices() -> tuple[tuple[str, int], ...]: def comp_search_get_info( ma_id: int, ) -> CompanyInfo: + logger.debug("[Call backend] comp_search_get_info") df = db.DF_CRM_MASTER.filter(pl.col.ma_id == ma_id) if df.height > 1 or df.height == 0: raise ValueError(f"Größe des zurückgelieferten Datenpakets ungültig: {df.height}") @@ -111,6 +114,7 @@ def contact_person_search_choices( use_both_names: bool, ) -> tuple[tuple[str, int], ...]: # TODO no reload functionality + logger.debug("[Call backend] contact_person_search_choices") q = db.DF_CONTACT_PERSON.lazy() if ma_id is not None: q = q.filter(pl.col.ma_id == ma_id) @@ -136,6 +140,7 @@ def contact_person_search_choices( def contact_person_search_get_info( an_id: int, ) -> ContactPersonInfo: + logger.debug("[Call backend] contact_person_search_get_info") df = db.DF_CONTACT_PERSON.filter(pl.col.an_id == an_id) if df.height > 1 or df.height == 0: raise ValueError(f"Größe des zurückgelieferten Datenpakets ungültig: {df.height}") @@ -147,6 +152,7 @@ def contact_person_search_get_info( def insert_initial_recording( data: dict[str, Any], ) -> None: + logger.debug("[Call backend] insert_initial_recording") stmt = db.grunderfassung_unternehmen.insert().values(data) with db.ENGINE.begin() as conn: conn.execute(stmt) @@ -156,6 +162,7 @@ def update_initial_recording( id_: int, data: dict[str, Any], ) -> None: + logger.debug("[Call backend] update_initial_recording") stmt = ( db.grunderfassung_unternehmen.update() .where(db.grunderfassung_unternehmen.c.erfassung_id == id_) @@ -168,6 +175,7 @@ def update_initial_recording( def get_initial_recording( id_: int, ) -> dict[str, Any]: + logger.debug("[Call backend] get_initial_recording") stmt = db.grunderfassung_unternehmen.select().where( db.grunderfassung_unternehmen.c.erfassung_id == id_ ) @@ -190,6 +198,7 @@ class FrontpageCompany: def get_company_list() -> list[FrontpageCompany]: + logger.debug("[Call backend] get_company_list") stmt = sql.select( db.grunderfassung_unternehmen.c.erfassung_id, db.grunderfassung_unternehmen.c.Partnersuche__un_suche, diff --git a/src/wce_crm/gui.py b/src/wce_crm/gui.py index 0cae7d7..f537d21 100644 --- a/src/wce_crm/gui.py +++ b/src/wce_crm/gui.py @@ -8,6 +8,7 @@ import json import pickle import re import sys +import traceback import uuid from collections import defaultdict from collections.abc import Container, Iterable, Sequence @@ -33,7 +34,9 @@ from PySide6.QtCore import ( QObject, Qt, QTimer, + QtMsgType, Signal, + qInstallMessageHandler, ) from PySide6.QtGui import QAction from PySide6.QtWidgets import ( @@ -1073,11 +1076,11 @@ def set_form_data( value = data else: if key not in data: - logger_gui.debug("---- Key not in data: %s") - logger_gui.debug("-------- Data: %s", pformat(data)) + logger_gui.error("Key not in data: %s", key) + logger_gui.error("Data:\n%s", pformat(data)) value = data[key] - logger_gui.debug("---- Key set: %s", key) + logger_gui.debug("Key set: %s", key) set_widget_value(widget, value) @@ -3634,9 +3637,51 @@ class MainWindow(QMainWindow): help_menu.addAction(about_action) +# ** global exception handling +def global_exception_handler(exc_type, exc_value, exc_traceback): + """catches all unhandled errors""" + + # format error + error_msg = "".join(traceback.format_exception(exc_type, exc_value, exc_traceback)) + logger_gui.critical(f"UNEXPECTED ERROR:\n{error_msg}") + + # message to user + # check if QApplication exists otherwise crashing of crash handler possible) + if QApplication.instance(): + msg_box = QMessageBox() + msg_box.setIcon(QMessageBox.Icon.Critical) + msg_box.setWindowTitle("Kritischer Fehler") + msg_box.setText( + "Ein unerwarteter Fehler ist aufgetreten. Die Details wurden protokolliert." + ) + # details for user or screenshots + msg_box.setDetailedText(error_msg) + msg_box.exec() + + sys.exit(1) + + +def qt_message_handler(mode, context, message): + """forwards internal Qt C++ warnings to Python loggers""" + if mode == QtMsgType.QtInfoMsg: + logger_gui.info(message) + elif mode == QtMsgType.QtWarningMsg: + logger_gui.warning(message) + elif mode == QtMsgType.QtCriticalMsg: + logger_gui.error(message) + elif mode == QtMsgType.QtFatalMsg: + logger_gui.critical(message) + + if __name__ == "__main__": - app = QApplication(sys.argv) - app.setStyleSheet(QSS) - window = MainWindow() - window.show() - sys.exit(app.exec()) + sys.excepthook = global_exception_handler + qInstallMessageHandler(qt_message_handler) + + try: + app = QApplication(sys.argv) + app.setStyleSheet(QSS) + window = MainWindow() + window.show() + sys.exit(app.exec()) + except Exception as err: + logger_gui.critical("Fehler beim Starten der Anwendung:\n%s", str(err), exc_info=True) diff --git a/src/wce_crm/logging.py b/src/wce_crm/logging.py index 5f6e855..c8afd46 100644 --- a/src/wce_crm/logging.py +++ b/src/wce_crm/logging.py @@ -1,6 +1,6 @@ import logging -from dopt_basics.logging import BASE_LOGGER, setup_logging +from dopt_basics.logging import BASE_LOGGER, LoggingConfig, setup_logging from wce_crm.constants import Config @@ -14,12 +14,16 @@ if Config.DEVELOPMENT_STATE: if not Config.PATH_LOGGING.exists(): Config.PATH_LOGGING.mkdir() -setup_logging( +LOGGING_CFG: LoggingConfig = LoggingConfig( enable_stderr=enable_stderr, enable_file=enable_file, logging_dir=Config.PATH_LOGGING, log_filename=Config.LOG_FILENAME, + file_max_bytes=10_485_760, + file_backup_count=2, ) + +setup_logging(LOGGING_CFG) logger_base = BASE_LOGGER.getChild("wce_crm") # ** GUI @@ -31,3 +35,7 @@ logger_get_data = logger_gui.getChild("get_data") logger_get_data.setLevel(logging.DEBUG) logger_auto_form = logger_gui.getChild("get_data_auto_form") logger_auto_form.setLevel(logging.DEBUG) + +# ** Backend +logger_back = logger_base.getChild("backend") +logger_back.setLevel(logging.DEBUG)