mirror of
https://github.com/rbreu/beeref.git
synced 2026-03-11 08:54:28 +00:00
Log to file; Add 'Show Debug Log' action
This commit is contained in:
parent
05e44851c2
commit
5f4d008203
16 changed files with 170 additions and 26 deletions
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ menu_structure = [
|
|||
'menu': '&Help',
|
||||
'items': [
|
||||
'help',
|
||||
'debuglog',
|
||||
],
|
||||
},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
12
tests/conftest.py
Normal 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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
Loading…
Reference in a new issue