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')
[docs] 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
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 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) 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 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()] def headerData(self, section, orientation, role=None): """Data for the table view headers. Parameters ---------- section : int Number of the column to get header data for. orientation : Qt.Orientation Orientation of header (horizontal or vertical). role : ItemDataRole Role for which to get data. Returns ------- QVariant Header data for given column and role. """ if (role is None or role == QtCore.Qt.DisplayRole) and orientation == QtCore.Qt.Horizontal: try: return self._header[section] except IndexError: pass return @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() @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')