Log to file; Add 'Show Debug Log' action

This commit is contained in:
Rebecca Breu 2021-05-14 13:37:44 +02:00
parent 05e44851c2
commit 5f4d008203
16 changed files with 170 additions and 26 deletions

View file

@ -26,7 +26,7 @@ from beeref.config import CommandlineArgs
from beeref import constants
from beeref.view import BeeGraphicsView
logger = logging.getLogger(constants.APPNAME)
logger = logging.getLogger(__name__)
class BeeRefMainWindow(QtWidgets.QWidget):
@ -66,15 +66,23 @@ def handle_sigint(signum, frame):
QtWidgets.QApplication.quit()
def handle_uncaught_exception(exc_type, value, traceback):
logger.critical('Unhandled exception',
exc_info=(exc_type, value, traceback))
sys.excepthook = handle_uncaught_exception
def main():
commandline_args = CommandlineArgs(with_check=True)
logging.basicConfig(level=getattr(logging, commandline_args.loglevel))
logger.info(f'Starting {constants.APPNAME} version {constants.VERSION}')
CommandlineArgs(with_check=True) # Force checking
app = QtWidgets.QApplication(sys.argv)
bee = BeeRefMainWindow(app) # NOQA:F841
signal.signal(signal.SIGINT, handle_sigint)
# Repeatedly run python-noop to give the interpreter time to
# handel signals
# handle signals
safe_timer(50, lambda: None)
app.exec()

View file

@ -203,6 +203,11 @@ actions = [
'shortcuts': ['F1', 'Ctrl+H'],
'callback': 'on_action_help',
},
{
'id': 'debuglog',
'text': 'Show &Debug Log',
'callback': 'on_action_debuglog',
},
{
'id': 'show_scrollbars',
'text': 'Show &Scrollbars',

View file

@ -89,6 +89,7 @@ menu_structure = [
'menu': '&Help',
'items': [
'help',
'debuglog',
],
},
]

View file

@ -20,10 +20,8 @@ import os.path
from PyQt6 import QtGui
from beeref import constants
logger = logging.getLogger(constants.APPNAME)
logger = logging.getLogger(__name__)
class BeeAssets:

View file

@ -17,6 +17,7 @@
import argparse
import logging
import logging.config
import os.path
from PyQt6 import QtCore
@ -24,7 +25,7 @@ from PyQt6 import QtCore
from beeref import constants
logger = logging.getLogger(constants.APPNAME)
logger = logging.getLogger(__name__)
parser = argparse.ArgumentParser(
@ -135,3 +136,51 @@ class BeeSettings(QtCore.QSettings):
if existing_only:
values = [f for f in values if os.path.exists(f)]
return values
logfile_name = os.path.join(
os.path.dirname(BeeSettings().fileName()), f'{constants.APPNAME}.log')
logging_conf = {
'version': 1,
'formatters': {
'verbose': {
'format': ('{asctime} {name} {process:d} {thread:d} {message}'),
'style': '{',
},
'simple': {
'format': '{levelname} {name}: {message}',
'style': '{',
},
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'formatter': 'simple',
'level': CommandlineArgs().loglevel,
},
'file': {
'class': 'beeref.utils.BeeRotatingFileHandler',
'formatter': 'verbose',
'filename': logfile_name,
'maxBytes': 1024 * 1000 * 50,
'backupCount': 1,
'level': 'DEBUG',
'delay': True,
}
},
'loggers': {
'beeref': {
'handlers': ['console', 'file'],
'level': 'DEBUG',
'propagate': False,
},
},
'root': {
'handlers': ['console', 'file'],
'level': 'DEBUG',
},
}
logging.config.dictConfig(logging_conf)

View file

@ -19,7 +19,6 @@ import logging
from PyQt6 import QtCore, QtGui
from beeref import commands
from beeref import constants
from beeref.fileio.errors import BeeFileIOError
from beeref.fileio.sql import SQLiteIO
from beeref.items import BeePixmapItem
@ -33,7 +32,7 @@ __all__ = [
'BeeFileIOError',
]
logger = logging.getLogger(constants.APPNAME)
logger = logging.getLogger(__name__)
def load_bee(filename, scene, worker=None):

View file

@ -30,12 +30,11 @@ import sqlite3
from PyQt6 import QtGui
from beeref.items import BeePixmapItem
from beeref import constants
from .errors import BeeFileIOError
from .schema import SCHEMA
logger = logging.getLogger(constants.APPNAME)
logger = logging.getLogger(__name__)
def handle_sqlite_errors(func):

View file

@ -20,9 +20,10 @@ from PyQt6 import QtWidgets
from PyQt6.QtCore import Qt
from beeref import constants
from beeref.config import logfile_name
logger = logging.getLogger(constants.APPNAME)
logger = logging.getLogger(__name__)
class WelcomeOverlay(QtWidgets.QWidget):
@ -78,9 +79,44 @@ class HelpDialog(QtWidgets.QDialog):
with open(os.path.join(docdir, 'controls.html')) as f:
controls_txt = f.read()
controls = QtWidgets.QLabel(controls_txt)
controls.setTextInteractionFlags(
Qt.TextInteractionFlag.TextSelectableByMouse)
tabs.addTab(controls, '&Controls')
layout = QtWidgets.QVBoxLayout()
self.setLayout(layout)
layout.addWidget(tabs)
self.show()
class DebugLogDialog(QtWidgets.QDialog):
def __init__(self, parent):
super().__init__(parent)
self.setWindowTitle(f'{constants.APPNAME} Debug Log')
with open(logfile_name) as f:
self.log_txt = f.read()
log = QtWidgets.QLabel(self.log_txt)
log.setTextInteractionFlags(
Qt.TextInteractionFlag.TextSelectableByMouse)
scroll = QtWidgets.QScrollArea(self)
scroll.setWidgetResizable(True)
scroll.setWidget(log)
buttons = QtWidgets.QDialogButtonBox(
QtWidgets.QDialogButtonBox.StandardButton.Close)
buttons.rejected.connect(self.reject)
copy_button = QtWidgets.QPushButton('Co&py To Clipboard')
copy_button.released.connect(self.copy_to_clipboard)
buttons.addButton(
copy_button, QtWidgets.QDialogButtonBox.ButtonRole.ActionRole)
layout = QtWidgets.QVBoxLayout()
self.setLayout(layout)
layout.addWidget(scroll)
layout.addWidget(buttons)
self.show()
def copy_to_clipboard(self):
clipboard = QtWidgets.QApplication.clipboard()
clipboard.setText(self.log_txt)

View file

@ -21,11 +21,10 @@ import logging
from PyQt6 import QtCore, QtGui, QtWidgets
from beeref import constants
from beeref.selection import SelectableMixin
logger = logging.getLogger(constants.APPNAME)
logger = logging.getLogger(__name__)
class BeePixmapItem(SelectableMixin, QtWidgets.QGraphicsPixmapItem):

View file

@ -23,11 +23,10 @@ from PyQt6.QtCore import Qt
import rpack
from beeref import commands
from beeref import constants
from beeref.selection import MultiSelectItem, RubberbandItem
logger = logging.getLogger(constants.APPNAME)
logger = logging.getLogger(__name__)
class BeeGraphicsScene(QtWidgets.QGraphicsScene):

View file

@ -25,12 +25,11 @@ from PyQt6.QtWidgets import QGraphicsItem
from beeref.assets import BeeAssets
from beeref import commands
from beeref.config import CommandlineArgs
from beeref import constants
from beeref import utils
commandline_args = CommandlineArgs()
logger = logging.getLogger(constants.APPNAME)
logger = logging.getLogger(__name__)
SELECT_COLOR = QtGui.QColor(116, 234, 231, 255)

View file

@ -13,6 +13,9 @@
# You should have received a copy of the GNU General Public License
# along with BeeRef. If not, see <https://www.gnu.org/licenses/>.
import logging.handlers
import os
import os.path
from PyQt6 import QtCore
@ -37,3 +40,11 @@ def round_to(number, base):
"""
return base * round(number / base)
class BeeRotatingFileHandler(logging.handlers.RotatingFileHandler):
"""RotatingFileHandler that creates log directory if necessary."""
def __init__(self, filename, **kwargs):
os.makedirs(os.path.dirname(filename), exist_ok=True)
super().__init__(filename, **kwargs)

View file

@ -24,13 +24,13 @@ from beeref import commands
from beeref.config import CommandlineArgs, BeeSettings
from beeref import constants
from beeref import fileio
from beeref.gui import BeeProgressDialog, WelcomeOverlay, HelpDialog
from beeref import gui
from beeref.items import BeePixmapItem
from beeref.scene import BeeGraphicsScene
commandline_args = CommandlineArgs()
logger = logging.getLogger(constants.APPNAME)
logger = logging.getLogger(__name__)
class BeeGraphicsView(QtWidgets.QGraphicsView, ActionsMixin):
@ -72,7 +72,7 @@ class BeeGraphicsView(QtWidgets.QGraphicsView, ActionsMixin):
self.customContextMenuRequested.connect(self.on_context_menu)
self.context_menu = self.build_menu_and_actions()
self.welcome_overlay = WelcomeOverlay(self)
self.welcome_overlay = gui.WelcomeOverlay(self)
# Load file given via command line
if commandline_args.filename:
@ -279,7 +279,7 @@ class BeeGraphicsView(QtWidgets.QGraphicsView, ActionsMixin):
fileio.load_bee, filename, self.scene)
self.worker.progress.connect(self.on_items_loaded)
self.worker.finished.connect(self.on_loading_finished)
self.progress = BeeProgressDialog(
self.progress = gui.BeeProgressDialog(
'Loading %s' % filename,
worker=self.worker,
parent=self)
@ -311,7 +311,7 @@ class BeeGraphicsView(QtWidgets.QGraphicsView, ActionsMixin):
self.worker = fileio.ThreadedIO(
fileio.save_bee, filename, self.scene, create_new=create_new)
self.worker.finished.connect(self.on_saving_finished)
self.progress = BeeProgressDialog(
self.progress = gui.BeeProgressDialog(
'Saving %s' % filename,
worker=self.worker,
parent=self)
@ -336,7 +336,10 @@ class BeeGraphicsView(QtWidgets.QGraphicsView, ActionsMixin):
self.app.quit()
def on_action_help(self):
HelpDialog(self)
gui.HelpDialog(self)
def on_action_debuglog(self):
gui.DebugLogDialog(self)
def on_insert_images_finished(self, filename, errors):
if errors:
@ -369,7 +372,7 @@ class BeeGraphicsView(QtWidgets.QGraphicsView, ActionsMixin):
self.scene)
self.worker.progress.connect(self.on_items_loaded)
self.worker.finished.connect(self.on_insert_images_finished)
self.progress = BeeProgressDialog(
self.progress = gui.BeeProgressDialog(
'Loading images',
worker=self.worker,
parent=self)

12
tests/conftest.py Normal file
View file

@ -0,0 +1,12 @@
from unittest.mock import MagicMock
def pytest_configure(config):
# Ignore logging configuration for BeeRef during test runs. This
# avoids logging to the regular log file and spamming test output
# with debug messages.
#
# This needs to be done before the application code is even loaded since
# logging configuration happens on module level
import logging.config
logging.config.dictConfig = MagicMock

View file

@ -1,4 +1,6 @@
import os.path
import pytest
import tempfile
from PyQt6 import QtCore
@ -32,3 +34,22 @@ class GetRectFromPointsTestCase(BeeTestCase):
(3.1, 0.5, 3.0)])
def test_round_to(number, base, expected):
assert utils.round_to(number, base) == expected
class BeeRotatingFileHandler(BeeTestCase):
def test_creates_new_dir(self):
with tempfile.TemporaryDirectory() as tmpdir:
logfile = os.path.join(tmpdir, 'foo', 'bar.log')
handler = utils.BeeRotatingFileHandler(logfile)
handler.emit('foo')
handler.close()
assert os.path.exists(logfile)
def test_uses_existing_dir(self):
with tempfile.TemporaryDirectory() as tmpdir:
logfile = os.path.join(tmpdir, 'bar.log')
handler = utils.BeeRotatingFileHandler(logfile)
handler.emit('foo')
handler.close()
assert os.path.exists(logfile)

View file

@ -284,6 +284,11 @@ class BeeGraphicsViewTestCase(ViewBaseTestCase):
self.view.on_action_help()
show_mock.assert_called_once()
@patch('beeref.gui.DebugLogDialog.show')
def test_on_action_debuglog(self, show_mock):
self.view.on_action_debuglog()
show_mock.assert_called_once()
@patch('beeref.scene.BeeGraphicsScene.clearSelection')
@patch('PyQt6.QtWidgets.QFileDialog.getOpenFileNames')
def test_on_action_insert_images(self, dialog_mock, clear_mock):