removed log viewer module

This commit is contained in:
Jakub Trllo 2024-02-05 16:05:13 +01:00
parent 0508cd986a
commit 4a6f250135
6 changed files with 0 additions and 567 deletions

View file

@ -1,6 +0,0 @@
from .log_view_module import LogViewModule
__all__ = (
"LogViewModule",
)

View file

@ -1,46 +0,0 @@
from ayon_core import AYON_SERVER_ENABLED
from ayon_core.modules import OpenPypeModule, ITrayModule
class LogViewModule(OpenPypeModule, ITrayModule):
name = "log_viewer"
def initialize(self, modules_settings):
logging_settings = modules_settings[self.name]
self.enabled = logging_settings["enabled"]
if AYON_SERVER_ENABLED:
self.enabled = False
# Tray attributes
self.window = None
def tray_init(self):
try:
from .tray.app import LogsWindow
self.window = LogsWindow()
except Exception:
self.log.warning(
"Couldn't set Logging GUI due to error.", exc_info=True
)
# Definition of Tray menu
def tray_menu(self, tray_menu):
from qtpy import QtWidgets
# Menu for Tray App
menu = QtWidgets.QMenu('Logging', tray_menu)
show_action = QtWidgets.QAction("Show Logs", menu)
show_action.triggered.connect(self._show_logs_gui)
menu.addAction(show_action)
tray_menu.addMenu(menu)
def tray_start(self):
return
def tray_exit(self):
return
def _show_logs_gui(self):
if self.window:
self.window.show()

View file

@ -1,37 +0,0 @@
from qtpy import QtWidgets, QtCore
from .widgets import LogsWidget, OutputWidget
from ayon_core import style
class LogsWindow(QtWidgets.QWidget):
def __init__(self, parent=None):
super(LogsWindow, self).__init__(parent)
self.setWindowTitle("Logs viewer")
self.resize(1400, 800)
log_detail = OutputWidget(parent=self)
logs_widget = LogsWidget(log_detail, parent=self)
main_layout = QtWidgets.QHBoxLayout(self)
log_splitter = QtWidgets.QSplitter(self)
log_splitter.setOrientation(QtCore.Qt.Horizontal)
log_splitter.addWidget(logs_widget)
log_splitter.addWidget(log_detail)
main_layout.addWidget(log_splitter)
self.logs_widget = logs_widget
self.log_detail = log_detail
self.setStyleSheet(style.load_stylesheet())
self._first_show = True
def showEvent(self, event):
super(LogsWindow, self).showEvent(event)
if self._first_show:
self._first_show = False
self.logs_widget.refresh()

View file

@ -1,138 +0,0 @@
import collections
from qtpy import QtCore, QtGui
from ayon_core.lib import Logger
class LogModel(QtGui.QStandardItemModel):
COLUMNS = (
"process_name",
"hostname",
"hostip",
"username",
"system_name",
"started"
)
colums_mapping = {
"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 -"
ROLE_LOGS = QtCore.Qt.UserRole + 2
ROLE_PROCESS_ID = QtCore.Qt.UserRole + 3
def __init__(self, parent=None):
super(LogModel, self).__init__(parent)
self.log_by_process = None
self.dbcon = None
# Crash if connection is not possible to skip this module
if not Logger.initialized:
Logger.initialize()
connection = Logger.get_log_mongo_connection()
if connection:
Logger.bootstrap_mongo_log()
database = connection[Logger.log_database_name]
self.dbcon = database[Logger.log_collection_name]
def headerData(self, section, orientation, role):
if (
role == QtCore.Qt.DisplayRole
and orientation == QtCore.Qt.Horizontal
):
if section < len(self.COLUMNS):
key = self.COLUMNS[section]
return self.colums_mapping.get(key, key)
super(LogModel, self).headerData(section, orientation, role)
def add_process_logs(self, process_logs):
items = []
first_item = True
for key in self.COLUMNS:
display_value = str(process_logs[key])
item = QtGui.QStandardItem(display_value)
if first_item:
first_item = False
item.setData(process_logs["_logs"], self.ROLE_LOGS)
item.setData(process_logs["process_id"], self.ROLE_PROCESS_ID)
items.append(item)
self.appendRow(items)
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:
process_id = item.get("process_id")
# backwards (in)compatibility
if not process_id:
continue
if process_id not in self.process_info:
proc_dict = {"_logs": []}
for key in self.process_keys:
proc_dict[key] = (
item.get(key) or self.default_value
)
self.process_info[process_id] = proc_dict
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_process_logs(item)
self.endResetModel()
class LogsFilterProxy(QtCore.QSortFilterProxyModel):
def __init__(self, *args, **kwargs):
super(LogsFilterProxy, self).__init__(*args, **kwargs)
self.col_usernames = None
self.filter_usernames = set()
def update_users_filter(self, users):
self.filter_usernames = set()
for user in users or tuple():
self.filter_usernames.add(user)
self.invalidateFilter()
def filterAcceptsRow(self, source_row, source_parent):
if self.col_usernames is not None:
index = self.sourceModel().index(
source_row, self.col_usernames, source_parent
)
user = index.data(QtCore.Qt.DisplayRole)
if user not in self.filter_usernames:
return False
return True

View file

@ -1,340 +0,0 @@
import html
from qtpy import QtCore, QtWidgets
import qtawesome
from .models import LogModel, LogsFilterProxy
class SearchComboBox(QtWidgets.QComboBox):
"""Searchable ComboBox with empty placeholder value as first value"""
def __init__(self, parent=None, placeholder=""):
super(SearchComboBox, self).__init__(parent)
self.setEditable(True)
self.setInsertPolicy(QtWidgets.QComboBox.NoInsert)
self.lineEdit().setPlaceholderText(placeholder)
# Apply completer settings
completer = self.completer()
completer.setCompletionMode(completer.PopupCompletion)
completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
# Force style sheet on popup menu
# It won't take the parent stylesheet for some reason
# todo: better fix for completer popup stylesheet
if parent:
popup = completer.popup()
popup.setStyleSheet(parent.styleSheet())
self.currentIndexChanged.connect(self.onIndexChange)
def onIndexChange(self, index):
print(index)
def populate(self, items):
self.clear()
self.addItems([""]) # ensure first item is placeholder
self.addItems(items)
def get_valid_value(self):
"""Return the current text if it's a valid value else None
Note: The empty placeholder value is valid and returns as ""
"""
text = self.currentText()
lookup = set(self.itemText(i) for i in range(self.count()))
if text not in lookup:
return None
return text
class SelectableMenu(QtWidgets.QMenu):
selection_changed = QtCore.Signal()
def mouseReleaseEvent(self, event):
action = self.activeAction()
if action and action.isEnabled():
action.trigger()
self.selection_changed.emit()
else:
super(SelectableMenu, self).mouseReleaseEvent(event)
class CustomCombo(QtWidgets.QWidget):
selection_changed = QtCore.Signal()
def __init__(self, title, parent=None):
super(CustomCombo, self).__init__(parent)
toolbutton = QtWidgets.QToolButton(self)
toolbutton.setText(title)
toolmenu = SelectableMenu(self)
toolbutton.setMenu(toolmenu)
toolbutton.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup)
layout = QtWidgets.QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(toolbutton)
toolmenu.selection_changed.connect(self.selection_changed)
self.toolbutton = toolbutton
self.toolmenu = toolmenu
self.main_layout = layout
def populate(self, items):
self.toolmenu.clear()
self.addItems(items)
def addItems(self, items):
for item in items:
action = self.toolmenu.addAction(item)
action.setCheckable(True)
action.setChecked(True)
self.toolmenu.addAction(action)
def items(self):
for action in self.toolmenu.actions():
yield action
class LogsWidget(QtWidgets.QWidget):
"""A widget that lists the published subsets for an asset"""
def __init__(self, detail_widget, parent=None):
super(LogsWidget, self).__init__(parent=parent)
model = LogModel()
proxy_model = LogsFilterProxy()
proxy_model.setSourceModel(model)
proxy_model.col_usernames = model.COLUMNS.index("username")
filter_layout = QtWidgets.QHBoxLayout()
user_filter = CustomCombo("Users", self)
users = model.dbcon.distinct("username")
user_filter.populate(users)
user_filter.selection_changed.connect(self._user_changed)
proxy_model.update_users_filter(users)
level_filter = CustomCombo("Levels", self)
levels = model.dbcon.distinct("level")
level_filter.addItems(levels)
level_filter.selection_changed.connect(self._level_changed)
detail_widget.update_level_filter(levels)
icon = qtawesome.icon("fa.refresh", color="white")
refresh_btn = QtWidgets.QPushButton(icon, "")
filter_layout.addWidget(user_filter)
filter_layout.addWidget(level_filter)
filter_layout.addStretch(1)
filter_layout.addWidget(refresh_btn)
view = QtWidgets.QTreeView(self)
view.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
layout = QtWidgets.QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.addLayout(filter_layout)
layout.addWidget(view)
view.setModel(proxy_model)
view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
view.setSortingEnabled(True)
view.sortByColumn(
model.COLUMNS.index("started"),
QtCore.Qt.DescendingOrder
)
refresh_triggered_timer = QtCore.QTimer()
refresh_triggered_timer.setSingleShot(True)
refresh_triggered_timer.setInterval(200)
refresh_triggered_timer.timeout.connect(self._on_refresh_timeout)
view.selectionModel().selectionChanged.connect(self._on_index_change)
refresh_btn.clicked.connect(self._on_refresh_clicked)
# Store to memory
self.model = model
self.proxy_model = proxy_model
self.view = view
self.user_filter = user_filter
self.level_filter = level_filter
self.detail_widget = detail_widget
self.refresh_btn = refresh_btn
self._refresh_triggered_timer = refresh_triggered_timer
def refresh(self):
self._refresh_triggered_timer.start()
def _on_refresh_timeout(self):
self.model.refresh()
self.detail_widget.refresh()
def _on_refresh_clicked(self):
self.refresh()
def _on_index_change(self, to_index, from_index):
index = self._selected_log()
if index:
logs = index.data(self.model.ROLE_LOGS)
else:
logs = []
self.detail_widget.set_detail(logs)
def _user_changed(self):
checked_values = set()
for action in self.user_filter.items():
if action.isChecked():
checked_values.add(action.text())
self.proxy_model.update_users_filter(checked_values)
def _level_changed(self):
checked_values = set()
for action in self.level_filter.items():
if action.isChecked():
checked_values.add(action.text())
self.detail_widget.update_level_filter(checked_values)
def on_context_menu(self, point):
# TODO will be any actions? it's ready
return
point_index = self.view.indexAt(point)
if not point_index.isValid():
return
# Get selected subsets without groups
selection = self.view.selectionModel()
rows = selection.selectedRows(column=0)
def _selected_log(self):
selection = self.view.selectionModel()
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)
show_timecode_checkbox = QtWidgets.QCheckBox("Show timestamp", self)
output_text = QtWidgets.QTextEdit(self)
output_text.setReadOnly(True)
# output_text.setLineWrapMode(QtWidgets.QTextEdit.FixedPixelWidth)
layout.addWidget(show_timecode_checkbox)
layout.addWidget(output_text)
show_timecode_checkbox.stateChanged.connect(
self.on_show_timecode_change
)
self.setLayout(layout)
self.output_text = output_text
self.show_timecode_checkbox = show_timecode_checkbox
self.refresh()
def refresh(self):
self.set_detail()
def show_timecode(self):
return self.show_timecode_checkbox.isChecked()
def on_show_timecode_change(self):
self.set_detail(self.las_logs)
def update_level_filter(self, levels):
self.filter_levels = set()
for level in levels or tuple():
self.filter_levels.add(level.lower())
self.set_detail(self.las_logs)
def add_line(self, line):
self.output_text.append(line)
def set_detail(self, logs=None):
self.las_logs = logs
self.output_text.clear()
if not logs:
return
show_timecode = self.show_timecode()
for log in logs:
level = log["level"].lower()
if level not in self.filter_levels:
continue
line_f = "<font color=\"White\">{message}"
if level == "debug":
line_f = (
"<font color=\"Yellow\"> -"
" <font color=\"Lime\">{{ {logger_name} }}: ["
" <font color=\"White\">{message}"
" <font color=\"Lime\">]"
)
elif level == "info":
line_f = (
"<font color=\"Lime\">>>> ["
" <font color=\"White\">{message}"
" <font color=\"Lime\">]"
)
elif level == "warning":
line_f = (
"<font color=\"Yellow\">*** WRN:"
" <font color=\"Lime\"> >>> {{ {logger_name} }}: ["
" <font color=\"White\">{message}"
" <font color=\"Lime\">]"
)
elif level == "error":
line_f = (
"<font color=\"Red\">!!! ERR:"
" <font color=\"White\">{timestamp}"
" <font color=\"Lime\">>>> {{ {logger_name} }}: ["
" <font color=\"White\">{message}"
" <font color=\"Lime\">]"
)
logger_name = log["loggerName"]
timestamp = ""
if not show_timecode:
timestamp = log["timestamp"]
message = log["message"]
exc = log.get("exception")
if exc:
message = exc["message"]
line = line_f.format(
message=html.escape(message),
logger_name=logger_name,
timestamp=timestamp
)
if show_timecode:
timestamp = log["timestamp"]
line = timestamp.strftime("%Y-%d-%m %H:%M:%S") + " " + line
self.add_line(line)
if not exc:
continue
for _line in exc["stackTrace"].split("\n"):
self.add_line(_line)