From f3ddc513c3fe2d0e53ed632ff9399361b1a4c7e6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 8 Jun 2020 11:23:35 +0200 Subject: [PATCH] changes to be able show logs by process --- pype/modules/logging/gui/app.py | 8 +- pype/modules/logging/gui/models.py | 86 +++++++---- pype/modules/logging/gui/widgets.py | 214 ++++++++++++++-------------- 3 files changed, 168 insertions(+), 140 deletions(-) diff --git a/pype/modules/logging/gui/app.py b/pype/modules/logging/gui/app.py index 74849b23bc..19256af1a8 100644 --- a/pype/modules/logging/gui/app.py +++ b/pype/modules/logging/gui/app.py @@ -1,6 +1,6 @@ from Qt import QtWidgets, QtCore -from .widgets import LogsWidget, LogDetailWidget -from avalon import style +from .widgets import LogsWidget, OutputWidget +from pypeapp import style class LogsWindow(QtWidgets.QWidget): @@ -10,7 +10,7 @@ class LogsWindow(QtWidgets.QWidget): self.setStyleSheet(style.load_stylesheet()) self.resize(1200, 800) logs_widget = LogsWidget(parent=self) - log_detail = LogDetailWidget(parent=self) + log_detail = OutputWidget(parent=self) main_layout = QtWidgets.QHBoxLayout() @@ -33,7 +33,5 @@ class LogsWindow(QtWidgets.QWidget): def on_selection_changed(self): index = self.logs_widget.selected_log() - if not index or not index.isValid(): - return node = index.data(self.logs_widget.model.NodeRole) self.log_detail.set_detail(node) diff --git a/pype/modules/logging/gui/models.py b/pype/modules/logging/gui/models.py index 2ef79554fe..484fd6dc69 100644 --- a/pype/modules/logging/gui/models.py +++ b/pype/modules/logging/gui/models.py @@ -1,4 +1,5 @@ import os +import collections from Qt import QtCore from pype.api import Logger from pypeapp.lib.log import _bootstrap_mongo_log @@ -8,31 +9,32 @@ log = Logger().get_logger("LogModel", "LoggingModule") class LogModel(QtCore.QAbstractItemModel): COLUMNS = [ - "user", - "host", - "lineNumber", - "method", - "module", - "fileName", - "loggerName", - "message", - "level", - "timestamp", + "process_name", + "hostname", + "hostip", + "username", + "system_name", + "started" ] colums_mapping = { - "user": "User", - "host": "Host", - "lineNumber": "Line n.", - "method": "Method", - "module": "Module", - "fileName": "File name", - "loggerName": "Logger name", - "message": "Message", - "level": "Level", - "timestamp": "Timestamp", + "process_name": "Process Name", + "process_id": "Process Id", + "hostname": "Hostname", + "hostip": "Host IP", + "username": "Username", + "system_name": "System name", + "started": "Started at" } - + process_keys = [ + "process_id", "hostname", "hostip", + "username", "system_name", "process_name" + ] + log_keys = [ + "timestamp", "level", "thread", "threadName", "message", "loggerName", + "fileName", "module", "method", "lineNumber" + ] + default_value = "- Not set -" NodeRole = QtCore.Qt.UserRole + 1 def __init__(self, parent=None): @@ -50,14 +52,47 @@ class LogModel(QtCore.QAbstractItemModel): self._root_node.add_child(node) def refresh(self): + self.log_by_process = collections.defaultdict(list) + self.process_info = {} + self.clear() self.beginResetModel() if self.dbcon: result = self.dbcon.find({}) for item in result: - self.add_log(item) - self.endResetModel() + process_id = item.get("process_id") + # backwards (in)compatibility + if not process_id: + continue + if process_id not in self.process_info: + proc_dict = {} + for key in self.process_keys: + proc_dict[key] = ( + item.get(key) or self.default_value + ) + self.process_info[process_id] = proc_dict + + if "_logs" not in self.process_info[process_id]: + self.process_info[process_id]["_logs"] = [] + + log_item = {} + for key in self.log_keys: + log_item[key] = item.get(key) or self.default_value + + if "exception" in item: + log_item["exception"] = item["exception"] + + self.process_info[process_id]["_logs"].append(log_item) + + for item in self.process_info.values(): + item["_logs"] = sorted( + item["_logs"], key=lambda item: item["timestamp"] + ) + item["started"] = item["_logs"][0]["timestamp"] + self.add_log(item) + + self.endResetModel() def data(self, index, role): if not index.isValid(): @@ -68,7 +103,7 @@ class LogModel(QtCore.QAbstractItemModel): column = index.column() key = self.COLUMNS[column] - if key == "timestamp": + if key == "started": return str(node.get(key, None)) return node.get(key, None) @@ -86,8 +121,7 @@ class LogModel(QtCore.QAbstractItemModel): child_item = parent_node.child(row) if child_item: return self.createIndex(row, column, child_item) - else: - return QtCore.QModelIndex() + return QtCore.QModelIndex() def rowCount(self, parent): node = self._root_node diff --git a/pype/modules/logging/gui/widgets.py b/pype/modules/logging/gui/widgets.py index 1daaa28326..cf20066397 100644 --- a/pype/modules/logging/gui/widgets.py +++ b/pype/modules/logging/gui/widgets.py @@ -1,5 +1,5 @@ -import getpass from Qt import QtCore, QtWidgets, QtGui +from PyQt5.QtCore import QVariant from .models import LogModel @@ -97,7 +97,6 @@ class SelectableMenu(QtWidgets.QMenu): class CustomCombo(QtWidgets.QWidget): selection_changed = QtCore.Signal() - checked_changed = QtCore.Signal(bool) def __init__(self, title, parent=None): super(CustomCombo, self).__init__(parent) @@ -126,27 +125,12 @@ class CustomCombo(QtWidgets.QWidget): self.toolmenu.clear() self.addItems(items) - def select_items(self, items, ignore_input=False): - if not isinstance(items, list): - items = [items] - - for action in self.toolmenu.actions(): - check = True - if ( - action.text() in items and ignore_input or - action.text() not in items and not ignore_input - ): - check = False - - action.setChecked(check) - def addItems(self, items): for item in items: action = self.toolmenu.addAction(item) action.setCheckable(True) - self.toolmenu.addAction(action) action.setChecked(True) - action.triggered.connect(self.checked_changed) + self.toolmenu.addAction(action) def items(self): for action in self.toolmenu.actions(): @@ -200,42 +184,15 @@ class CheckableComboBox(QtWidgets.QComboBox): for text, checked in items: text_item = QtGui.QStandardItem(text) checked_item = QtGui.QStandardItem() - checked_item.setData( - QtCore.QVariant(checked), QtCore.Qt.CheckStateRole - ) + checked_item.setData(QVariant(checked), QtCore.Qt.CheckStateRole) self.model.appendRow([text_item, checked_item]) -class FilterLogModel(QtCore.QSortFilterProxyModel): - sub_dict = ["$gt", "$lt", "$not"] - def __init__(self, key_values, parent=None): - super(FilterLogModel, self).__init__(parent) - self.allowed_key_values = key_values - - def filterAcceptsRow(self, row, parent): - """ - Reimplemented from base class. - """ - model = self.sourceModel() - for key, values in self.allowed_key_values.items(): - col_indx = model.COLUMNS.index(key) - value = model.index(row, col_indx, parent).data( - QtCore.Qt.DisplayRole - ) - if value not in values: - return False - return True - - class LogsWidget(QtWidgets.QWidget): """A widget that lists the published subsets for an asset""" active_changed = QtCore.Signal() - _level_order = [ - "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL" - ] - def __init__(self, parent=None): super(LogsWidget, self).__init__(parent=parent) @@ -243,41 +200,47 @@ class LogsWidget(QtWidgets.QWidget): filter_layout = QtWidgets.QHBoxLayout() + # user_filter = SearchComboBox(self, "Users") user_filter = CustomCombo("Users", self) users = model.dbcon.distinct("user") user_filter.populate(users) - user_filter.checked_changed.connect(self.user_changed) - user_filter.select_items(getpass.getuser()) + user_filter.selection_changed.connect(self.user_changed) level_filter = CustomCombo("Levels", self) + # levels = [(level, True) for level in model.dbcon.distinct("level")] levels = model.dbcon.distinct("level") - _levels = [] - for level in self._level_order: - if level in levels: - _levels.append(level) - level_filter.populate(_levels) - level_filter.checked_changed.connect(self.level_changed) + level_filter.addItems(levels) - # date_from_label = QtWidgets.QLabel("From:") - # date_filter_from = QtWidgets.QDateTimeEdit() - # - # date_from_layout = QtWidgets.QVBoxLayout() - # date_from_layout.addWidget(date_from_label) - # date_from_layout.addWidget(date_filter_from) - # - # date_to_label = QtWidgets.QLabel("To:") - # date_filter_to = QtWidgets.QDateTimeEdit() - # - # date_to_layout = QtWidgets.QVBoxLayout() - # date_to_layout.addWidget(date_to_label) - # date_to_layout.addWidget(date_filter_to) + date_from_label = QtWidgets.QLabel("From:") + date_filter_from = QtWidgets.QDateTimeEdit() + + date_from_layout = QtWidgets.QVBoxLayout() + date_from_layout.addWidget(date_from_label) + date_from_layout.addWidget(date_filter_from) + + # now = datetime.datetime.now() + # QtCore.QDateTime( + # now.year, + # now.month, + # now.day, + # now.hour, + # now.minute, + # second=0, + # msec=0, + # timeSpec=0 + # ) + date_to_label = QtWidgets.QLabel("To:") + date_filter_to = QtWidgets.QDateTimeEdit() + + date_to_layout = QtWidgets.QVBoxLayout() + date_to_layout.addWidget(date_to_label) + date_to_layout.addWidget(date_filter_to) filter_layout.addWidget(user_filter) filter_layout.addWidget(level_filter) - filter_layout.setAlignment(QtCore.Qt.AlignLeft) - # filter_layout.addLayout(date_from_layout) - # filter_layout.addLayout(date_to_layout) + filter_layout.addLayout(date_from_layout) + filter_layout.addLayout(date_to_layout) view = QtWidgets.QTreeView(self) view.setAllColumnsShowFocus(True) @@ -290,58 +253,28 @@ class LogsWidget(QtWidgets.QWidget): view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) view.setSortingEnabled(True) view.sortByColumn( - model.COLUMNS.index("timestamp"), + model.COLUMNS.index("started"), QtCore.Qt.AscendingOrder ) - key_val = { - "user": users, - "level": levels - } - proxy_model = FilterLogModel(key_val, view) - proxy_model.setSourceModel(model) - view.setModel(proxy_model) - - view.customContextMenuRequested.connect(self.on_context_menu) - view.selectionModel().selectionChanged.connect(self.active_changed) - - # WARNING this is cool but slows down widget a lot - # header = view.header() - # # Enforce the columns to fit the data (purely cosmetic) - # if Qt.__binding__ in ("PySide2", "PyQt5"): - # header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents) - # else: - # header.setResizeMode(QtWidgets.QHeaderView.ResizeToContents) - + view.setModel(model) + view.pressed.connect(self._on_activated) # prepare model.refresh() # Store to memory self.model = model - self.proxy_model = proxy_model self.view = view self.user_filter = user_filter self.level_filter = level_filter + def _on_activated(self, *args, **kwargs): + self.active_changed.emit() + def user_changed(self): - valid_actions = [] for action in self.user_filter.items(): - if action.isChecked(): - valid_actions.append(action.text()) - - self.proxy_model.allowed_key_values["user"] = valid_actions - self.proxy_model.invalidate() - - def level_changed(self): - valid_actions = [] - for action in self.level_filter.items(): - if action.isChecked(): - valid_actions.append(action.text()) - - self.proxy_model.allowed_key_values["level"] = valid_actions - self.proxy_model.invalidate() - + print(action) def on_context_menu(self, point): # TODO will be any actions? it's ready @@ -360,10 +293,74 @@ class LogsWidget(QtWidgets.QWidget): rows = selection.selectedRows(column=0) if len(rows) == 1: return rows[0] - return None +class OutputWidget(QtWidgets.QWidget): + def __init__(self, parent=None): + super(OutputWidget, self).__init__(parent=parent) + layout = QtWidgets.QVBoxLayout(self) + output_text = QtWidgets.QTextEdit() + output_text.setReadOnly(True) + # output_text.setLineWrapMode(QtWidgets.QTextEdit.FixedPixelWidth) + + layout.addWidget(output_text) + + self.setLayout(layout) + self.output_text = output_text + + def add_line(self, line): + self.output_text.append(line) + + def set_detail(self, node): + self.output_text.clear() + for log in node["_logs"]: + level = log["level"].lower() + + line_f = "{message}" + if level == "debug": + line_f = ( + " -" + " {{ {loggerName} }}: [" + " {message}" + " ]" + ) + elif level == "info": + line_f = ( + ">>> [" + " {message}" + " ]" + ) + elif level == "warning": + line_f = ( + "*** WRN:" + " >>> {{ {loggerName} }}: [" + " {message}" + " ]" + ) + elif level == "error": + line_f = ( + "!!! ERR:" + " {timestamp}" + " >>> {{ {loggerName} }}: [" + " {message}" + " ]" + ) + + exc = log.get("exception") + if exc: + log["message"] = exc["message"] + + line = line_f.format(**log) + + self.add_line(line) + + if not exc: + continue + for _line in exc["stackTrace"].split("\n"): + self.add_line(_line) + + class LogDetailWidget(QtWidgets.QWidget): """A Widget that display information about a specific version""" data_rows = [ @@ -418,5 +415,4 @@ class LogDetailWidget(QtWidgets.QWidget): value = detail_data.get(row) or "< Not set >" data[row] = value - self.detail_widget.setHtml(self.html_text.format(**data))