Source code for qudi.core.logger.records_model
# -*- coding: utf-8 -*-
"""
This file contains a custom QAbstractTableModel object providing text data for all logged records.
.. Copyright (c) 2021, the qudi developers. See the AUTHORS.md file at the top-level directory of this
.. distribution and on <https://github.com/Ulm-IQO/qudi-core/>
..
.. This file is part of qudi.
..
.. Qudi is free software: you can redistribute it and/or modify it under the terms of
.. the GNU Lesser General Public License as published by the Free Software Foundation,
.. either version 3 of the License, or (at your option) any later version.
..
.. Qudi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
.. without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
.. See the GNU Lesser General Public License for more details.
..
.. You should have received a copy of the GNU Lesser General Public License along with qudi.
.. If not, see <https://www.gnu.org/licenses/>.
"""
__all__ = ("LogRecordsTableModel",)
import traceback
from datetime import datetime
from PySide2 import QtCore, QtGui
from qudi.util.mutex import Mutex
[docs]
class LogRecordsTableModel(QtCore.QAbstractTableModel):
"""This is a Qt model that represents textual information about all logged records.
Can be displayed with a QTableView for example.
"""
_color_map = {
"debug": QtGui.QColor("#77F"),
"info": QtGui.QColor("#1F1"),
"warning": QtGui.QColor("#F90"),
"error": QtGui.QColor("#F11"),
"critical": QtGui.QColor("#FF00FF"),
}
_fallback_color = QtGui.QColor("#FFF")
_header = ("Time", "Level", "Source", "Message")
def __init__(self, *args, max_records=10000, **kwargs):
super().__init__(*args, **kwargs)
self._thread_lock = Mutex()
self._max_records = max(int(max_records), 1)
self._records = list()
self._begin = 0
self._end = 0
self._fill_count = 0
[docs]
def rowCount(self, parent=None):
"""
Returns the number of log records stored in the model.
Returns
-------
int
Number of log records stored.
"""
return self._fill_count
[docs]
def columnCount(self, parent=None):
"""
Returns the number of columns each log record has.
Returns
-------
int
Number of columns for each log record.
"""
return len(self._header)
[docs]
def flags(self, index):
"""Determines what can be done with log record cells in the table view.
Parameters
----------
index : QModelIndex
Cell for which the flags are requested.
Returns
-------
Qt.ItemFlags
Actions allowed for this cell.
"""
return (
QtCore.Qt.ItemIsEnabled
| QtCore.Qt.ItemIsSelectable
| QtCore.Qt.ItemIsEditable
)
[docs]
def data(self, index, role):
"""Get data from model for a given cell. Data can have a role that affects display.
Parameters
----------
index : QModelIndex
Cell for which data is requested.
role : ItemDataRole
Role for which data is requested.
Returns
-------
QVariant
Data for given cell and role.
"""
if index.isValid():
record = self._records[(self._begin + index.row()) % self._max_records]
if role == QtCore.Qt.TextColorRole:
return self._color_map.get(record[1], self._fallback_color)
if role in (
QtCore.Qt.DisplayRole,
QtCore.Qt.ToolTipRole,
QtCore.Qt.EditRole,
):
return record[index.column()]
[docs]
@QtCore.Slot(object)
def add_record(self, data):
"""Add a single log entry to the end of the table model.
Parameters
----------
data : logging.LogRecord
Log record as returned from logging module.
Returns
-------
bool
True if adding entry succeeded, False otherwise.
"""
with self._thread_lock:
if self._fill_count < self._max_records:
self.beginInsertRows(QtCore.QModelIndex(), self._end, self._end)
self._records.append(self._format_log_record(data))
self._fill_count += 1
self._end = (self._end + 1) % self._max_records
self.endInsertRows()
else:
row = self._max_records - 1
self.beginRemoveRows(QtCore.QModelIndex(), 0, 0)
self._begin = (self._begin + 1) % self._max_records
self._fill_count -= 1
self.endRemoveRows()
self.beginInsertRows(QtCore.QModelIndex(), row, row)
self._records[self._end] = self._format_log_record(data)
self._end = (self._end + 1) % self._max_records
self._fill_count += 1
self.endInsertRows()
[docs]
@QtCore.Slot()
def clear(self):
with self._thread_lock:
self.beginResetModel()
self._begin = 0
self._end = 0
self._fill_count = 0
self._records = list()
self.endResetModel()
@property
def max_size(self):
return self._max_records
@staticmethod
def _format_log_record(record):
# Compose message to display
message = (
record.getMessage()
) # message if hasattr(record, 'message') else record.msg
if record.exc_info is not None:
message += f"\n\n{traceback.format_exception(*record.exc_info)[-1][:-1]}"
tb = "\n".join(traceback.format_exception(*record.exc_info)[:-1])
if tb:
message += f"\n{tb}"
# Create human-readable timestamp
timestamp = datetime.fromtimestamp(record.created).strftime("%Y-%m-%d %H:%M:%S")
# return 4 element tuple (timestamp, level, name, message)
# Avoid problems with Qt by eliminating NULL bytes in strings.
return timestamp, record.levelname, record.name, message.replace("\0", "\\x00")