refactor: move qt logging to gui module
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user