From ab749560c23ab93d089398f540b5318c67417039 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Sat, 28 May 2016 11:04:15 -0500 Subject: [PATCH] dashboard: forward local log messages to docks, replace status bar (#411) --- artiq/browser/log.py | 37 ------------------------------ artiq/dashboard/experiments.py | 10 ++++---- artiq/dashboard/explorer.py | 6 ++--- artiq/dashboard/schedule.py | 12 ++++++---- artiq/frontend/artiq_browser.py | 7 +++--- artiq/frontend/artiq_dashboard.py | 15 ++++++------ artiq/gui/log.py | 38 ++++++++++++++++++++++++++++++- artiq/gui/state.py | 2 +- 8 files changed, 63 insertions(+), 64 deletions(-) delete mode 100644 artiq/browser/log.py diff --git a/artiq/browser/log.py b/artiq/browser/log.py deleted file mode 100644 index cfe54b87a..000000000 --- a/artiq/browser/log.py +++ /dev/null @@ -1,37 +0,0 @@ -import logging - -from artiq.protocols.logging import SourceFilter - - -class LogWidgetHandler(logging.Handler): - def __init__(self, *args, **kwargs): - logging.Handler.__init__(self, *args, **kwargs) - self.log_widget = None - self.setFormatter(logging.Formatter("%(name)s:%(message)s")) - - def emit(self, record): - if self.log_widget is not None: - message = self.format(record) - self.log_widget.append_message((record.levelno, record.source, - record.created, message)) - - -def init_log(args): - root_logger = logging.getLogger() - root_logger.setLevel(logging.NOTSET) # we use our custom filter only - flt = SourceFilter(logging.WARNING + args.quiet*10 - args.verbose*10, - "browser") - handlers = [] - console_handler = logging.StreamHandler() - console_handler.setFormatter(logging.Formatter( - "%(levelname)s:%(source)s:%(name)s:%(message)s")) - handlers.append(console_handler) - - widget_handler = LogWidgetHandler() - handlers.append(widget_handler) - - for handler in handlers: - handler.addFilter(flt) - root_logger.addHandler(handler) - - return widget_handler diff --git a/artiq/dashboard/experiments.py b/artiq/dashboard/experiments.py index 61d27bce2..5a35063f1 100644 --- a/artiq/dashboard/experiments.py +++ b/artiq/dashboard/experiments.py @@ -504,10 +504,9 @@ class ExperimentManager: def on_dock_closed(self, expurl): del self.open_experiments[expurl] - async def _submit_task(self, *args): + async def _submit_task(self, expurl, *args): rid = await self.schedule_ctl.submit(*args) - self.main_window.statusBar().showMessage( - "Submitted RID {}".format(rid)) + logger.info("Submitted '%s', RID is %d", expurl, rid) def submit(self, expurl): file, class_name, _ = self.resolve_expurl(expurl) @@ -529,6 +528,7 @@ class ExperimentManager: if "repo_rev" in options: expid["repo_rev"] = options["repo_rev"] asyncio.ensure_future(self._submit_task( + expurl, scheduling["pipeline_name"], expid, scheduling["priority"], scheduling["due_date"], @@ -545,9 +545,9 @@ class ExperimentManager: rid, exc_info=True) def request_inst_term(self, expurl): - self.main_window.statusBar().showMessage( + logger.info( "Requesting termination of all instances " - "of '{}'".format(expurl)) + "of '%s'", expurl) file, class_name, use_repository = self.resolve_expurl(expurl) rids = [] for rid, desc in self.schedule.items(): diff --git a/artiq/dashboard/explorer.py b/artiq/dashboard/explorer.py index fee266e71..f22722946 100644 --- a/artiq/dashboard/explorer.py +++ b/artiq/dashboard/explorer.py @@ -157,7 +157,7 @@ class WaitingPanel(LayoutWidget): class ExplorerDock(QtWidgets.QDockWidget): - def __init__(self, status_bar, exp_manager, d_shortcuts, + def __init__(self, exp_manager, d_shortcuts, explist_sub, explist_status_sub, schedule_ctl, experiment_db_ctl): QtWidgets.QDockWidget.__init__(self, "Explorer") @@ -168,7 +168,6 @@ class ExplorerDock(QtWidgets.QDockWidget): top_widget = LayoutWidget() self.setWidget(top_widget) - self.status_bar = status_bar self.exp_manager = exp_manager self.d_shortcuts = d_shortcuts self.schedule_ctl = schedule_ctl @@ -287,8 +286,7 @@ class ExplorerDock(QtWidgets.QDockWidget): if expname is not None: expurl = "repo:" + expname self.d_shortcuts.set_shortcut(nr, expurl) - self.status_bar.showMessage("Set shortcut F{} to '{}'" - .format(nr+1, expurl)) + logger.info("Set shortcut F%d to '%s'", nr+1, expurl) def update_scanning(self, scanning): if scanning: diff --git a/artiq/dashboard/schedule.py b/artiq/dashboard/schedule.py index b18963050..6d7d8f20a 100644 --- a/artiq/dashboard/schedule.py +++ b/artiq/dashboard/schedule.py @@ -1,6 +1,7 @@ import asyncio import time from functools import partial +import logging from PyQt5 import QtCore, QtWidgets, QtGui @@ -8,6 +9,9 @@ from artiq.gui.models import DictSyncModel from artiq.tools import elide +logger = logging.getLogger(__name__) + + class Model(DictSyncModel): def __init__(self, init): DictSyncModel.__init__(self, @@ -55,13 +59,12 @@ class Model(DictSyncModel): class ScheduleDock(QtWidgets.QDockWidget): - def __init__(self, status_bar, schedule_ctl, schedule_sub): + def __init__(self, schedule_ctl, schedule_sub): QtWidgets.QDockWidget.__init__(self, "Schedule") self.setObjectName("Schedule") self.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable | QtWidgets.QDockWidget.DockWidgetFloatable) - self.status_bar = status_bar self.schedule_ctl = schedule_ctl self.table = QtWidgets.QTableView() @@ -118,10 +121,9 @@ class ScheduleDock(QtWidgets.QDockWidget): row = idx[0].row() rid = self.table_model.row_to_key[row] if graceful: - msg = "Requested termination of RID {}".format(rid) + logger.info("Requested termination of RID %d", rid) else: - msg = "Deleted RID {}".format(rid) - self.status_bar.showMessage(msg) + logger.info("Deleted RID %d", rid) asyncio.ensure_future(self.delete(rid, graceful)) async def request_term_multiple(self, rids): diff --git a/artiq/frontend/artiq_browser.py b/artiq/frontend/artiq_browser.py index 835cd84b6..3114ffcb3 100755 --- a/artiq/frontend/artiq_browser.py +++ b/artiq/frontend/artiq_browser.py @@ -12,7 +12,7 @@ from quamash import QEventLoop from artiq import __artiq_dir__ as artiq_dir from artiq.tools import verbosity_args, atexit_register_coroutine from artiq.gui import state, applets, models, log -from artiq.browser import datasets, files, experiments, log as browser_log +from artiq.browser import datasets, files, experiments logger = logging.getLogger(__name__) @@ -121,21 +121,20 @@ class Browser(QtWidgets.QMainWindow): def main(): # initialize application args = get_argparser().parse_args() + widget_log_handler = log.init_log(args, "browser") app = QtWidgets.QApplication(["ARTIQ Browser"]) loop = QEventLoop(app) asyncio.set_event_loop(loop) atexit.register(loop.close) - widget_log_handler = browser_log.init_log(args) - datasets_sub = models.LocalModelManager(datasets.Model) datasets_sub.init({}) smgr = state.StateManager(args.db_file) main_window = Browser(datasets_sub, args.browse_root, args.select) - widget_log_handler.log_widget = main_window.log + widget_log_handler.callback = main_window.log.append_message smgr.register(main_window) if os.name == "nt": diff --git a/artiq/frontend/artiq_dashboard.py b/artiq/frontend/artiq_dashboard.py index 401b6cc28..62af367d9 100755 --- a/artiq/frontend/artiq_dashboard.py +++ b/artiq/frontend/artiq_dashboard.py @@ -4,11 +4,12 @@ import argparse import asyncio import atexit import os +import logging from PyQt5 import QtCore, QtGui, QtWidgets from quamash import QEventLoop -from artiq import __artiq_dir__ as artiq_dir +from artiq import __artiq_dir__ as artiq_dir, __version__ as artiq_version from artiq.tools import * from artiq.protocols.pc_rpc import AsyncioClient from artiq.protocols.broadcast import Receiver @@ -89,7 +90,7 @@ class MdiArea(QtWidgets.QMdiArea): def main(): # initialize application args = get_argparser().parse_args() - init_logger(args) + widget_log_handler = log.init_log(args, "dashboard") app = QtWidgets.QApplication(["ARTIQ Dashboard"]) loop = QEventLoop(app) @@ -125,9 +126,6 @@ def main(): # initialize main window main_window = MainWindow(args.server) smgr.register(main_window) - status_bar = QtWidgets.QStatusBar() - status_bar.showMessage("Connected to {}".format(args.server)) - main_window.setStatusBar(status_bar) mdi_area = MdiArea() mdi_area.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) mdi_area.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) @@ -142,7 +140,7 @@ def main(): smgr.register(expmgr) d_shortcuts = shortcuts.ShortcutsDock(main_window, expmgr) smgr.register(d_shortcuts) - d_explorer = explorer.ExplorerDock(status_bar, expmgr, d_shortcuts, + d_explorer = explorer.ExplorerDock(expmgr, d_shortcuts, sub_clients["explist"], sub_clients["explist_status"], rpc_clients["schedule"], @@ -161,12 +159,13 @@ def main(): atexit_register_coroutine(d_ttl_dds.stop) d_schedule = schedule.ScheduleDock( - status_bar, rpc_clients["schedule"], sub_clients["schedule"]) + rpc_clients["schedule"], sub_clients["schedule"]) smgr.register(d_schedule) logmgr = log.LogDockManager(main_window) smgr.register(logmgr) log_receiver.notify_cbs.append(logmgr.append_message) + widget_log_handler.callback = logmgr.append_message # lay out docks right_docks = [ @@ -194,6 +193,8 @@ def main(): if d_log0 is not None: main_window.tabifyDockWidget(d_schedule, d_log0) + logging.info("ARTIQ dashboard %s connected to %s", artiq_version, args.server) + # run main_window.show() loop.run_until_complete(main_window.exit_request.wait()) diff --git a/artiq/gui/log.py b/artiq/gui/log.py index 98ce4bb32..c58df92ea 100644 --- a/artiq/gui/log.py +++ b/artiq/gui/log.py @@ -6,6 +6,7 @@ from functools import partial from PyQt5 import QtCore, QtGui, QtWidgets +from artiq.protocols.logging import SourceFilter from artiq.gui.tools import (LayoutWidget, log_level_to_name, QDockWidgetCloseDetect) @@ -159,10 +160,11 @@ class LogDock(QDockWidgetCloseDetect): grid.addWidget(QtWidgets.QLabel("Minimum level: "), 0, 0) self.filter_level = QtWidgets.QComboBox() self.filter_level.addItems(["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]) - self.filter_level.setToolTip("Display entries at or above this level") + self.filter_level.setToolTip("Receive entries at or above this level") grid.addWidget(self.filter_level, 0, 1) self.filter_freetext = QtWidgets.QLineEdit() self.filter_freetext.setPlaceholderText("freetext filter...") + self.filter_freetext.setToolTip("Receive entries containing this text") grid.addWidget(self.filter_freetext, 0, 2) scrollbottom = QtWidgets.QToolButton() @@ -340,3 +342,37 @@ class LogDockManager: return None dock = self.create_new_dock(False) return dock + + +class LogWidgetHandler(logging.Handler): + def __init__(self, *args, **kwargs): + logging.Handler.__init__(self, *args, **kwargs) + self.callback = None + self.setFormatter(logging.Formatter("%(name)s:%(message)s")) + + def emit(self, record): + if self.callback is not None: + message = self.format(record) + self.callback((record.levelno, record.source, + record.created, message)) + + +def init_log(args, local_source): + root_logger = logging.getLogger() + root_logger.setLevel(logging.NOTSET) # we use our custom filter only + flt = SourceFilter(logging.INFO + args.quiet*10 - args.verbose*10, + local_source) + handlers = [] + console_handler = logging.StreamHandler() + console_handler.setFormatter(logging.Formatter( + "%(levelname)s:%(source)s:%(name)s:%(message)s")) + handlers.append(console_handler) + + widget_handler = LogWidgetHandler() + handlers.append(widget_handler) + + for handler in handlers: + handler.addFilter(flt) + root_logger.addHandler(handler) + + return widget_handler diff --git a/artiq/gui/state.py b/artiq/gui/state.py index c6473866e..fc19f7919 100644 --- a/artiq/gui/state.py +++ b/artiq/gui/state.py @@ -51,7 +51,7 @@ class StateManager(TaskObject): # To help address this problem, state is restored in the opposite # order as the stateful objects are registered. for name, obj in reversed(list(self.stateful_objects.items())): - logger.info("Restoring state of object '%s'", name) + logger.debug("Restoring state of object '%s'", name) state = data.get(name, None) if state is not None: try: