dashboard: forward local log messages to docks, replace status bar (#411)

This commit is contained in:
Sebastien Bourdeauducq 2016-05-28 11:04:15 -05:00
parent 10267f39c9
commit ab749560c2
8 changed files with 63 additions and 64 deletions

View File

@ -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

View File

@ -504,10 +504,9 @@ class ExperimentManager:
def on_dock_closed(self, expurl): def on_dock_closed(self, expurl):
del self.open_experiments[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) rid = await self.schedule_ctl.submit(*args)
self.main_window.statusBar().showMessage( logger.info("Submitted '%s', RID is %d", expurl, rid)
"Submitted RID {}".format(rid))
def submit(self, expurl): def submit(self, expurl):
file, class_name, _ = self.resolve_expurl(expurl) file, class_name, _ = self.resolve_expurl(expurl)
@ -529,6 +528,7 @@ class ExperimentManager:
if "repo_rev" in options: if "repo_rev" in options:
expid["repo_rev"] = options["repo_rev"] expid["repo_rev"] = options["repo_rev"]
asyncio.ensure_future(self._submit_task( asyncio.ensure_future(self._submit_task(
expurl,
scheduling["pipeline_name"], scheduling["pipeline_name"],
expid, expid,
scheduling["priority"], scheduling["due_date"], scheduling["priority"], scheduling["due_date"],
@ -545,9 +545,9 @@ class ExperimentManager:
rid, exc_info=True) rid, exc_info=True)
def request_inst_term(self, expurl): def request_inst_term(self, expurl):
self.main_window.statusBar().showMessage( logger.info(
"Requesting termination of all instances " "Requesting termination of all instances "
"of '{}'".format(expurl)) "of '%s'", expurl)
file, class_name, use_repository = self.resolve_expurl(expurl) file, class_name, use_repository = self.resolve_expurl(expurl)
rids = [] rids = []
for rid, desc in self.schedule.items(): for rid, desc in self.schedule.items():

View File

@ -157,7 +157,7 @@ class WaitingPanel(LayoutWidget):
class ExplorerDock(QtWidgets.QDockWidget): 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, explist_sub, explist_status_sub,
schedule_ctl, experiment_db_ctl): schedule_ctl, experiment_db_ctl):
QtWidgets.QDockWidget.__init__(self, "Explorer") QtWidgets.QDockWidget.__init__(self, "Explorer")
@ -168,7 +168,6 @@ class ExplorerDock(QtWidgets.QDockWidget):
top_widget = LayoutWidget() top_widget = LayoutWidget()
self.setWidget(top_widget) self.setWidget(top_widget)
self.status_bar = status_bar
self.exp_manager = exp_manager self.exp_manager = exp_manager
self.d_shortcuts = d_shortcuts self.d_shortcuts = d_shortcuts
self.schedule_ctl = schedule_ctl self.schedule_ctl = schedule_ctl
@ -287,8 +286,7 @@ class ExplorerDock(QtWidgets.QDockWidget):
if expname is not None: if expname is not None:
expurl = "repo:" + expname expurl = "repo:" + expname
self.d_shortcuts.set_shortcut(nr, expurl) self.d_shortcuts.set_shortcut(nr, expurl)
self.status_bar.showMessage("Set shortcut F{} to '{}'" logger.info("Set shortcut F%d to '%s'", nr+1, expurl)
.format(nr+1, expurl))
def update_scanning(self, scanning): def update_scanning(self, scanning):
if scanning: if scanning:

View File

@ -1,6 +1,7 @@
import asyncio import asyncio
import time import time
from functools import partial from functools import partial
import logging
from PyQt5 import QtCore, QtWidgets, QtGui from PyQt5 import QtCore, QtWidgets, QtGui
@ -8,6 +9,9 @@ from artiq.gui.models import DictSyncModel
from artiq.tools import elide from artiq.tools import elide
logger = logging.getLogger(__name__)
class Model(DictSyncModel): class Model(DictSyncModel):
def __init__(self, init): def __init__(self, init):
DictSyncModel.__init__(self, DictSyncModel.__init__(self,
@ -55,13 +59,12 @@ class Model(DictSyncModel):
class ScheduleDock(QtWidgets.QDockWidget): 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") QtWidgets.QDockWidget.__init__(self, "Schedule")
self.setObjectName("Schedule") self.setObjectName("Schedule")
self.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable | self.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable |
QtWidgets.QDockWidget.DockWidgetFloatable) QtWidgets.QDockWidget.DockWidgetFloatable)
self.status_bar = status_bar
self.schedule_ctl = schedule_ctl self.schedule_ctl = schedule_ctl
self.table = QtWidgets.QTableView() self.table = QtWidgets.QTableView()
@ -118,10 +121,9 @@ class ScheduleDock(QtWidgets.QDockWidget):
row = idx[0].row() row = idx[0].row()
rid = self.table_model.row_to_key[row] rid = self.table_model.row_to_key[row]
if graceful: if graceful:
msg = "Requested termination of RID {}".format(rid) logger.info("Requested termination of RID %d", rid)
else: else:
msg = "Deleted RID {}".format(rid) logger.info("Deleted RID %d", rid)
self.status_bar.showMessage(msg)
asyncio.ensure_future(self.delete(rid, graceful)) asyncio.ensure_future(self.delete(rid, graceful))
async def request_term_multiple(self, rids): async def request_term_multiple(self, rids):

View File

@ -12,7 +12,7 @@ from quamash import QEventLoop
from artiq import __artiq_dir__ as artiq_dir from artiq import __artiq_dir__ as artiq_dir
from artiq.tools import verbosity_args, atexit_register_coroutine from artiq.tools import verbosity_args, atexit_register_coroutine
from artiq.gui import state, applets, models, log 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__) logger = logging.getLogger(__name__)
@ -121,21 +121,20 @@ class Browser(QtWidgets.QMainWindow):
def main(): def main():
# initialize application # initialize application
args = get_argparser().parse_args() args = get_argparser().parse_args()
widget_log_handler = log.init_log(args, "browser")
app = QtWidgets.QApplication(["ARTIQ Browser"]) app = QtWidgets.QApplication(["ARTIQ Browser"])
loop = QEventLoop(app) loop = QEventLoop(app)
asyncio.set_event_loop(loop) asyncio.set_event_loop(loop)
atexit.register(loop.close) atexit.register(loop.close)
widget_log_handler = browser_log.init_log(args)
datasets_sub = models.LocalModelManager(datasets.Model) datasets_sub = models.LocalModelManager(datasets.Model)
datasets_sub.init({}) datasets_sub.init({})
smgr = state.StateManager(args.db_file) smgr = state.StateManager(args.db_file)
main_window = Browser(datasets_sub, args.browse_root, args.select) 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) smgr.register(main_window)
if os.name == "nt": if os.name == "nt":

View File

@ -4,11 +4,12 @@ import argparse
import asyncio import asyncio
import atexit import atexit
import os import os
import logging
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
from quamash import QEventLoop 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.tools import *
from artiq.protocols.pc_rpc import AsyncioClient from artiq.protocols.pc_rpc import AsyncioClient
from artiq.protocols.broadcast import Receiver from artiq.protocols.broadcast import Receiver
@ -89,7 +90,7 @@ class MdiArea(QtWidgets.QMdiArea):
def main(): def main():
# initialize application # initialize application
args = get_argparser().parse_args() args = get_argparser().parse_args()
init_logger(args) widget_log_handler = log.init_log(args, "dashboard")
app = QtWidgets.QApplication(["ARTIQ Dashboard"]) app = QtWidgets.QApplication(["ARTIQ Dashboard"])
loop = QEventLoop(app) loop = QEventLoop(app)
@ -125,9 +126,6 @@ def main():
# initialize main window # initialize main window
main_window = MainWindow(args.server) main_window = MainWindow(args.server)
smgr.register(main_window) 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 = MdiArea()
mdi_area.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) mdi_area.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
mdi_area.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) mdi_area.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
@ -142,7 +140,7 @@ def main():
smgr.register(expmgr) smgr.register(expmgr)
d_shortcuts = shortcuts.ShortcutsDock(main_window, expmgr) d_shortcuts = shortcuts.ShortcutsDock(main_window, expmgr)
smgr.register(d_shortcuts) 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"],
sub_clients["explist_status"], sub_clients["explist_status"],
rpc_clients["schedule"], rpc_clients["schedule"],
@ -161,12 +159,13 @@ def main():
atexit_register_coroutine(d_ttl_dds.stop) atexit_register_coroutine(d_ttl_dds.stop)
d_schedule = schedule.ScheduleDock( d_schedule = schedule.ScheduleDock(
status_bar, rpc_clients["schedule"], sub_clients["schedule"]) rpc_clients["schedule"], sub_clients["schedule"])
smgr.register(d_schedule) smgr.register(d_schedule)
logmgr = log.LogDockManager(main_window) logmgr = log.LogDockManager(main_window)
smgr.register(logmgr) smgr.register(logmgr)
log_receiver.notify_cbs.append(logmgr.append_message) log_receiver.notify_cbs.append(logmgr.append_message)
widget_log_handler.callback = logmgr.append_message
# lay out docks # lay out docks
right_docks = [ right_docks = [
@ -194,6 +193,8 @@ def main():
if d_log0 is not None: if d_log0 is not None:
main_window.tabifyDockWidget(d_schedule, d_log0) main_window.tabifyDockWidget(d_schedule, d_log0)
logging.info("ARTIQ dashboard %s connected to %s", artiq_version, args.server)
# run # run
main_window.show() main_window.show()
loop.run_until_complete(main_window.exit_request.wait()) loop.run_until_complete(main_window.exit_request.wait())

View File

@ -6,6 +6,7 @@ from functools import partial
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
from artiq.protocols.logging import SourceFilter
from artiq.gui.tools import (LayoutWidget, log_level_to_name, from artiq.gui.tools import (LayoutWidget, log_level_to_name,
QDockWidgetCloseDetect) QDockWidgetCloseDetect)
@ -159,10 +160,11 @@ class LogDock(QDockWidgetCloseDetect):
grid.addWidget(QtWidgets.QLabel("Minimum level: "), 0, 0) grid.addWidget(QtWidgets.QLabel("Minimum level: "), 0, 0)
self.filter_level = QtWidgets.QComboBox() self.filter_level = QtWidgets.QComboBox()
self.filter_level.addItems(["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]) 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) grid.addWidget(self.filter_level, 0, 1)
self.filter_freetext = QtWidgets.QLineEdit() self.filter_freetext = QtWidgets.QLineEdit()
self.filter_freetext.setPlaceholderText("freetext filter...") self.filter_freetext.setPlaceholderText("freetext filter...")
self.filter_freetext.setToolTip("Receive entries containing this text")
grid.addWidget(self.filter_freetext, 0, 2) grid.addWidget(self.filter_freetext, 0, 2)
scrollbottom = QtWidgets.QToolButton() scrollbottom = QtWidgets.QToolButton()
@ -340,3 +342,37 @@ class LogDockManager:
return None return None
dock = self.create_new_dock(False) dock = self.create_new_dock(False)
return dock 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

View File

@ -51,7 +51,7 @@ class StateManager(TaskObject):
# To help address this problem, state is restored in the opposite # To help address this problem, state is restored in the opposite
# order as the stateful objects are registered. # order as the stateful objects are registered.
for name, obj in reversed(list(self.stateful_objects.items())): 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) state = data.get(name, None)
if state is not None: if state is not None:
try: try: