refactor: move qt logging to gui module

This commit is contained in:
2026-07-06 11:53:49 +02:00
parent ccc2185561
commit 8f0a85316d
4 changed files with 158 additions and 79 deletions
+27 -32
View File
@@ -1,32 +1,14 @@
import logging
import logging.config
import os
from pathlib import Path
import yaml
import os
from PySide6.QtCore import QObject, Signal
class QtLogEmitter(QObject):
message = Signal(str)
class QtLogHandler(logging.Handler):
def __init__(self, emitter: QtLogEmitter):
super().__init__()
self.emitter = emitter
def emit(self, record: logging.LogRecord):
try:
msg = self.format(record)
self.emitter.message.emit(msg)
except Exception:
self.handleError(record)
class IgnoreSuccessfulStatusAccessFilter(logging.Filter):
#Should be disabled if debugging status calls.
#In particular if this is causing server/GUI lag
# Should be disabled if debugging status calls.
# In particular if this is causing server/GUI lag
_SUPPRESSED_SUCCESS_MESSAGES = (
'"GET /status HTTP/1.1" 200',
'"GET /sample/reference_tools HTTP/1.1" 200',
@@ -94,6 +76,7 @@ def get_uvicorn_logging_config() -> dict:
},
}
def get_config_path(env: str = "dev") -> Path:
module_dir = Path(__file__).parent
config_filename = f"logging_{env}.yaml"
@@ -104,8 +87,13 @@ def get_config_path(env: str = "dev") -> Path:
return config_path
#TODO fix logging.
def setup_logger(name="aareDAQ", base_dir: str | None = f'~/tmp/mxlogs/', config_path: str | None = None):
# TODO fix logging.
def setup_logger(
name="aareDAQ",
base_dir: str | None = f"~/tmp/mxlogs/",
config_path: str | None = None,
):
# switch to production mode using: $ APP_ENV=prod python main.py
env = os.getenv("APP_ENV", "dev") # default: dev
@@ -118,14 +106,15 @@ def setup_logger(name="aareDAQ", base_dir: str | None = f'~/tmp/mxlogs/', config
config = yaml.safe_load(f.read())
# Force base directory to be under the user's home dir
effective_base = base_dir + f'{name}' or f'/tmp/logs/mxlogs/{name}'
effective_base = os.path.abspath(os.path.expanduser(os.path.expandvars(effective_base)))
effective_base = base_dir + f"{name}" or f"/tmp/logs/mxlogs/{name}"
effective_base = os.path.abspath(
os.path.expanduser(os.path.expandvars(effective_base))
)
# Ensure directories for file handlers exist
handlers = config.get("handlers", {})
for h in handlers.values():
filename = h.get("filename")
if not filename:
continue
@@ -134,7 +123,7 @@ def setup_logger(name="aareDAQ", base_dir: str | None = f'~/tmp/mxlogs/', config
h["filename"] = abs_filename
log_dir = os.path.dirname(abs_filename)
if log_dir and not os.path.exists(log_dir):
os.makedirs(log_dir, exist_ok=True)
os.makedirs(log_dir, exist_ok=True)
logging.config.dictConfig(config)
logging.getLogger("redis_lock").setLevel(logging.WARNING)
@@ -144,7 +133,11 @@ def setup_logger(name="aareDAQ", base_dir: str | None = f'~/tmp/mxlogs/', config
logging.getLogger("aaregui").setLevel(logging.INFO)
return logging.getLogger(name)
def find_existing_formatter(default_fmt="%(asctime)s [%(levelname)s] %(name)s: %(message)s", default_datefmt=None):
def find_existing_formatter(
default_fmt="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
default_datefmt=None,
):
root = logging.getLogger()
for h in root.handlers:
if h.formatter:
@@ -158,15 +151,18 @@ def find_existing_formatter(default_fmt="%(asctime)s [%(levelname)s] %(name)s: %
return logging.Formatter(default_fmt, datefmt=default_datefmt)
def attach_to_logger(logger_name: str = "", handler: logging.Handler = None):
logging.getLogger(logger_name).addHandler(handler)
if __name__ == "__main__":
os.environ["APP_ENV"] = "dev" # or "prod"
#os.environ["LOG_DIR"] = "/tmp/mxlogs" # optional; matches your setup code
# os.environ["LOG_DIR"] = "/tmp/mxlogs" # optional; matches your setup code
log = setup_logger("aareDAQ", base_dir="/tmp/test_logs") # use the configured logger name
log = setup_logger(
"aareDAQ", base_dir="/tmp/test_logs"
) # use the configured logger name
log.debug("debug smoke test")
log.error("error smoke test")
@@ -182,4 +178,3 @@ if __name__ == "__main__":
log.debug("debug test")
log.error("error test")
print(type(h), getattr(h, "baseFilename", None), h.level)
+20
View File
@@ -0,0 +1,20 @@
import logging
from PySide6.QtCore import QObject, Signal
class QtLogEmitter(QObject):
message = Signal(str)
class QtLogHandler(logging.Handler):
def __init__(self, emitter: QtLogEmitter):
super().__init__()
self.emitter = emitter
def emit(self, record: logging.LogRecord):
try:
msg = self.format(record)
self.emitter.message.emit(msg)
except Exception:
self.handleError(record)
+13 -8
View File
@@ -1,17 +1,18 @@
from PySide6.QtCore import Qt, QTimer, Signal, Slot
from PySide6.QtWidgets import (
QDockWidget,
QPlainTextEdit,
QWidget,
QVBoxLayout,
QFrame,
QHBoxLayout,
QLabel,
QPlainTextEdit,
QPushButton,
QToolButton,
QFrame,
QVBoxLayout,
QWidget,
)
from aare.common.logger_config import QtLogEmitter, QtLogHandler, find_existing_formatter, attach_to_logger
from aare.common.logger_config import attach_to_logger, find_existing_formatter
from aare.gui.log import QtLogEmitter, QtLogHandler
class RuntimeNotificationWidget(QFrame):
@@ -148,7 +149,9 @@ class RuntimeNotificationWidget(QFrame):
timeout = 5000 if auto_clear_ms is None else int(auto_clear_ms)
self._auto_clear_timer.start(timeout)
def show_waiting(self, *, title: str, message: str, auto_clear_ms: int | None = None) -> None:
def show_waiting(
self, *, title: str, message: str, auto_clear_ms: int | None = None
) -> None:
self.show_notification(
title=title,
message=message,
@@ -171,7 +174,9 @@ class RuntimeNotificationWidget(QFrame):
class LogDock(QDockWidget):
def __init__(self, title="Log", parent=None):
super().__init__(title, parent)
self.setAllowedAreas(Qt.BottomDockWidgetArea | Qt.RightDockWidgetArea | Qt.LeftDockWidgetArea)
self.setAllowedAreas(
Qt.BottomDockWidgetArea | Qt.RightDockWidgetArea | Qt.LeftDockWidgetArea
)
self.container = QWidget(self)
@@ -236,4 +241,4 @@ class LogDock(QDockWidget):
self.notification.clear_notification()
def clear(self):
self.view.clear()
self.view.clear()
+98 -39
View File
@@ -5,35 +5,35 @@ import logging
import time
from typing import Dict
from PySide6.QtCore import Qt, Slot, QUrl
from PySide6.QtGui import QGuiApplication, QDesktopServices
from PySide6.QtCore import Qt, QUrl, Slot
from PySide6.QtGui import QDesktopServices, QGuiApplication
from PySide6.QtWidgets import (
QDialog,
QVBoxLayout,
QHBoxLayout,
QLineEdit,
QPushButton,
QTabWidget,
QTableWidget,
QTableWidgetItem,
QTextEdit,
QLabel,
QWidget,
QCheckBox,
QDialog,
QDialogButtonBox,
QFormLayout,
QFrame,
QSpinBox,
QHBoxLayout,
QLabel,
QLineEdit,
QMessageBox,
QPushButton,
QSpinBox,
QTableWidget,
QTableWidgetItem,
QTabWidget,
QTextEdit,
QVBoxLayout,
QWidget,
)
from aare.common.error_codes import error_code_help
from aare.common.logger_config import QtLogEmitter, QtLogHandler, find_existing_formatter, attach_to_logger
from aare.common.logger_config import attach_to_logger, find_existing_formatter
from aare.common.models import OpenGuiSessionInfo
from aare.gui.log import QtLogEmitter, QtLogHandler
from aare.gui.threads.daq_worker import DAQWorker
class DeveloperHelpDialog(QDialog):
def __init__(self, *, daq: DAQWorker, is_staff: bool, parent=None):
super().__init__(parent)
@@ -57,7 +57,9 @@ class DeveloperHelpDialog(QDialog):
self._banner = QLabel(self)
self._banner.setVisible(self._is_staff)
self._banner.setWordWrap(True)
self._banner.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
self._banner.setTextInteractionFlags(
Qt.TextInteractionFlag.TextSelectableByMouse
)
self._banner.setStyleSheet(
"QLabel {"
" background: #f6f6f6;"
@@ -76,7 +78,9 @@ class DeveloperHelpDialog(QDialog):
top.addWidget(QLabel("Filter:", self))
self._filter = QLineEdit(self)
self._filter.setPlaceholderText("Type to filter (matches name, value, or help)…")
self._filter.setPlaceholderText(
"Type to filter (matches name, value, or help)…"
)
self._filter.setClearButtonEnabled(True)
self._filter.setMinimumHeight(28)
self._filter.setStyleSheet(
@@ -120,12 +124,16 @@ class DeveloperHelpDialog(QDialog):
self._open_gui_log_btn = QPushButton("Open GUI log", self)
self._open_gui_log_btn.setVisible(self._is_staff)
self._open_gui_log_btn.clicked.connect(lambda: self._open_log_file_for_logger("aareGUI"))
self._open_gui_log_btn.clicked.connect(
lambda: self._open_log_file_for_logger("aareGUI")
)
top.addWidget(self._open_gui_log_btn)
self._open_daq_log_btn = QPushButton("Open DAQ log", self)
self._open_daq_log_btn.setVisible(self._is_staff)
self._open_daq_log_btn.clicked.connect(lambda: self._open_log_file_for_logger("aareDAQ"))
self._open_daq_log_btn.clicked.connect(
lambda: self._open_log_file_for_logger("aareDAQ")
)
top.addWidget(self._open_daq_log_btn)
self._tabs = QTabWidget(self)
@@ -136,7 +144,9 @@ class DeveloperHelpDialog(QDialog):
self._codes_table.setHorizontalHeaderLabels(["Name"])
self._codes_table.setSortingEnabled(True)
self._codes_table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers)
self._codes_table.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows)
self._codes_table.setSelectionBehavior(
QTableWidget.SelectionBehavior.SelectRows
)
self._codes_table.setSelectionMode(QTableWidget.SelectionMode.SingleSelection)
self._codes_table.itemSelectionChanged.connect(self._update_code_details)
self._codes_table.horizontalHeader().setStretchLastSection(True)
@@ -156,17 +166,23 @@ class DeveloperHelpDialog(QDialog):
details_layout.setSpacing(8)
form = QFormLayout()
form.setLabelAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
form.setLabelAlignment(
Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter
)
details_layout.addLayout(form)
self._detail_name = QLabel("-", self)
self._detail_name.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
self._detail_name.setTextInteractionFlags(
Qt.TextInteractionFlag.TextSelectableByMouse
)
form.addRow("Name:", self._detail_name)
value_row = QHBoxLayout()
value_row.setSpacing(8)
self._detail_value = QLabel("-", self)
self._detail_value.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
self._detail_value.setTextInteractionFlags(
Qt.TextInteractionFlag.TextSelectableByMouse
)
self._copy_value_btn = QPushButton("Copy value", self)
self._copy_value_btn.clicked.connect(self._copy_selected_value)
value_row.addWidget(self._detail_value, 1)
@@ -177,7 +193,9 @@ class DeveloperHelpDialog(QDialog):
self._detail_help = QLabel("-", self)
self._detail_help.setWordWrap(True)
self._detail_help.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
self._detail_help.setTextInteractionFlags(
Qt.TextInteractionFlag.TextSelectableByMouse
)
self._detail_help.setStyleSheet(
"QLabel {"
" background: white;"
@@ -201,7 +219,9 @@ class DeveloperHelpDialog(QDialog):
self._payload_summary = QLabel(self)
self._payload_summary.setVisible(self._is_staff)
self._payload_summary.setWordWrap(True)
self._payload_summary.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
self._payload_summary.setTextInteractionFlags(
Qt.TextInteractionFlag.TextSelectableByMouse
)
self._payload_text = QTextEdit(self)
self._payload_text.setReadOnly(True)
@@ -253,11 +273,26 @@ class DeveloperHelpDialog(QDialog):
self._gui_sessions_table = QTableWidget(self)
self._gui_sessions_table.setColumnCount(8)
self._gui_sessions_table.setHorizontalHeaderLabels(
["Session", "User", "Staff", "Baton", "Last seen", "Last interaction", "Close requested", "Requested by"]
[
"Session",
"User",
"Staff",
"Baton",
"Last seen",
"Last interaction",
"Close requested",
"Requested by",
]
)
self._gui_sessions_table.setEditTriggers(
QTableWidget.EditTrigger.NoEditTriggers
)
self._gui_sessions_table.setSelectionBehavior(
QTableWidget.SelectionBehavior.SelectRows
)
self._gui_sessions_table.setSelectionMode(
QTableWidget.SelectionMode.SingleSelection
)
self._gui_sessions_table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers)
self._gui_sessions_table.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows)
self._gui_sessions_table.setSelectionMode(QTableWidget.SelectionMode.SingleSelection)
self._gui_sessions_table.setSortingEnabled(True)
self._gui_sessions_table.horizontalHeader().setStretchLastSection(True)
@@ -270,7 +305,9 @@ class DeveloperHelpDialog(QDialog):
self._gui_close_grace_spin.setSuffix(" s")
self._gui_request_close_btn = QPushButton("Request Close", self)
self._gui_request_close_btn.clicked.connect(self._request_close_selected_gui)
self._gui_request_close_btn.clicked.connect(
self._request_close_selected_gui
)
self._gui_force_remove_btn = QPushButton("Force Remove", self)
self._gui_force_remove_btn.clicked.connect(self._force_remove_selected_gui)
@@ -387,7 +424,9 @@ class DeveloperHelpDialog(QDialog):
except Exception:
pretty = str(p)
blocks.append(f"#{i}\n{pretty}")
self._payloads_text.setPlainText("\n\n".join(blocks) if blocks else "(none captured yet)")
self._payloads_text.setPlainText(
"\n\n".join(blocks) if blocks else "(none captured yet)"
)
@Slot(str)
def _append_error_log_line(self, line: str) -> None:
@@ -406,7 +445,10 @@ class DeveloperHelpDialog(QDialog):
if isinstance(body, dict):
code = body.get("code")
msg = body.get("message")
return (str(code) if code is not None else None, str(msg) if msg is not None else None)
return (
str(code) if code is not None else None,
str(msg) if msg is not None else None,
)
return (None, None)
def _update_banner(self) -> None:
@@ -457,9 +499,11 @@ class DeveloperHelpDialog(QDialog):
items = sorted(self._codes.items(), key=lambda kv: kv[0])
if term:
def _match(name: str, value: str) -> bool:
h = error_code_help(value) or ""
return term in f"{name}\n{value}\n{h}".lower()
items = [(k, v) for (k, v) in items if _match(k, v)]
self._codes_table.setRowCount(len(items))
@@ -476,7 +520,11 @@ class DeveloperHelpDialog(QDialog):
self._update_code_details()
# If enabled, re-try selection after the table content changes
if self._is_staff and self._always_highlight_last_error and not self._freeze_payload:
if (
self._is_staff
and self._always_highlight_last_error
and not self._freeze_payload
):
self._select_code_from_last_error()
def _selected_code(self) -> tuple[str | None, str | None]:
@@ -502,7 +550,9 @@ class DeveloperHelpDialog(QDialog):
self._detail_name.setText(name)
self._detail_value.setText(value)
self._detail_help.setText(error_code_help(value) or "(no help text defined yet)")
self._detail_help.setText(
error_code_help(value) or "(no help text defined yet)"
)
self._copy_value_btn.setEnabled(True)
@Slot()
@@ -548,7 +598,12 @@ class DeveloperHelpDialog(QDialog):
if not self._is_staff:
return
self._gui_sessions = [s if isinstance(s, OpenGuiSessionInfo) else OpenGuiSessionInfo.model_validate(s) for s in sessions]
self._gui_sessions = [
s
if isinstance(s, OpenGuiSessionInfo)
else OpenGuiSessionInfo.model_validate(s)
for s in sessions
]
self._gui_sessions_table.setSortingEnabled(False)
self._gui_sessions_table.setRowCount(len(self._gui_sessions))
@@ -592,7 +647,9 @@ class DeveloperHelpDialog(QDialog):
def _request_close_selected_gui(self) -> None:
session_id = self._selected_gui_session_id()
if session_id is None:
QMessageBox.information(self, "Active GUIs", "Please select a GUI session first.")
QMessageBox.information(
self, "Active GUIs", "Please select a GUI session first."
)
return
grace_seconds = int(self._gui_close_grace_spin.value())
@@ -611,7 +668,9 @@ class DeveloperHelpDialog(QDialog):
def _force_remove_selected_gui(self) -> None:
session_id = self._selected_gui_session_id()
if session_id is None:
QMessageBox.information(self, "Active GUIs", "Please select a GUI session first.")
QMessageBox.information(
self, "Active GUIs", "Please select a GUI session first."
)
return
answer = QMessageBox.warning(
@@ -629,4 +688,4 @@ class DeveloperHelpDialog(QDialog):
return
self._daq.force_remove_gui_session(session_id)
self._daq.load_gui_sessions()
self._daq.load_gui_sessions()