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] 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
[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")