mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
removed log viewer module
This commit is contained in:
parent
0508cd986a
commit
4a6f250135
6 changed files with 0 additions and 567 deletions
|
|
@ -1,6 +0,0 @@
|
|||
from .log_view_module import LogViewModule
|
||||
|
||||
|
||||
__all__ = (
|
||||
"LogViewModule",
|
||||
)
|
||||
|
|
@ -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()
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue