diff --git a/README.md b/README.md index e93d880a99d8aa0e9d0323172a236494a900f3c9..d194c52fb860c7cf8d55063ac94559d3b28e317d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -# cutelog - GUI for Python's logging module +# cutelog – GUI for Python's logging module +[](https://pypi.python.org/pypi/cutelog) This is a graphical log viewer for Python's standard logging module. It can be targeted as a SocketHandler with no additional setup (see [Usage](#usage)). diff --git a/cutelog/config.py b/cutelog/config.py index fa27727c4bb04d88bf4fafa00a7dd404799f4a00..de3a83da437dc8296887911087e474c57918b426 100644 --- a/cutelog/config.py +++ b/cutelog/config.py @@ -2,14 +2,11 @@ import logging import os import sys from collections import namedtuple -from platform import python_version from pkg_resources import get_distribution, resource_filename from PyQt5.QtCore import (QT_VERSION_STR, QCoreApplication, QFile, QObject, QSettings, Qt, pyqtSignal) -# from PyQt5.QtGui import QFont - if sys.platform == 'win': DEFAULT_FONT = 'MS Shell Dlg 2' @@ -19,9 +16,6 @@ else: DEFAULT_FONT = 'Sans' -# @Future: when the time is right, remove everything related to this mess: -PY35_COMPAT = python_version().startswith('3.5') - # @Future: when Qt 5.6 becomes standard, remove this: QT_VER = QT_VERSION_STR.split('.') if QT_VER[0] == '5' and int(QT_VER[1]) < 6: @@ -93,7 +87,6 @@ class Config(QObject): self.benchmark_interval = None self.update_attributes() - # self.save_options() def __getitem__(self, name): # self.log.debug('Getting "{}"'.format(name)) @@ -258,16 +251,11 @@ def init_logging(): log = logging.getLogger('CL') term_handler = logging.StreamHandler() - if PY35_COMPAT: - exc = ImportError - else: - exc = ModuleNotFoundError - try: import colorlog fmt = colorlog.ColoredFormatter('%(asctime)s %(log_color)s[%(name)12s:%(lineno)3s' ' %(funcName)18s ]\t%(levelname)-.6s %(message)s') - except exc: + except ImportError: fmt = logging.Formatter('%(asctime)s [%(name)12s:%(lineno)3s ' '%(funcName)18s ]\t%(levelname)-.6s %(message)s') diff --git a/cutelog/listener.py b/cutelog/listener.py index a032b36eb0da500edb4f5d7f441abbb752986779..151588999ea191a60fc87d61c1fb8dd787f64caa 100644 --- a/cutelog/listener.py +++ b/cutelog/listener.py @@ -26,7 +26,7 @@ class LogServer(QTcpServer): self.host = QHostAddress(self.host) self.benchmark = CONFIG['benchmark'] - self.threads = {} # socketDescriptor -> LogConnection + self.threads = {} # int(socketDescriptor) -> LogConnection self.connections = 0 def start(self): @@ -61,20 +61,25 @@ class LogServer(QTcpServer): new_conn.start() self.threads[int(socketDescriptor)] = new_conn - def close_server(self): + def close_server(self, wait=True): self.log.debug('Closing the server') + self.main_window.set_status('Stopping the server...') self.close() - self.stop_all_connections() - - def stop_all_connections(self): - self.log.debug('Waiting for connection threads to stop') - for _, thread in self.threads.items(): - thread.exit() - for _, thread in self.threads.items(): - if not thread.wait(1000): - self.log.error('Thread "{}" didn\'t stop in time, terminating...'.format(thread)) - thread.terminate() - self.log.error('Thread "{}" terminated'.format(thread)) + if wait: + self.wait_connections_stopped() + self.main_window.set_status('Server has stopped') + + def wait_connections_stopped(self): + self.log.debug('Waiting for {} connections threads to stop'.format(len(self.threads))) + to_wait = self.threads.copy() # to protect against changes during iteration + for thread in to_wait.values(): + try: + if not thread.wait(1000): + self.log.error('Thread "{}" didn\'t stop in time, terminating'.format(thread)) + thread.terminate() + self.log.error('Thread "{}" terminated'.format(thread)) + except RuntimeError: # happens when thread has been deleted before we got to it + self.log.debug('Thread {} has been deleted already'.format(thread)) self.log.debug('All connections stopped') def cleanup_connection(self, socketDescriptor): @@ -99,6 +104,10 @@ class LogConnection(QThread): self.stop_signal = stop_signal self.tab_closed = tab_closed + def __repr__(self): + return "{}(name={}, socketDescriptor={})".format(self.__class__.__name__, self.name, + self.socketDescriptor) + def run(self): self.log.debug('Connection "{}" is starting'.format(self.name)) @@ -133,6 +142,7 @@ class LogConnection(QThread): data = pickle.loads(data) record = logging.makeLogRecord(data) self.new_record.emit(record) + sock.disconnectFromHost() sock.close() self.connection_finished.emit(int(self.socketDescriptor)) diff --git a/cutelog/main_window.py b/cutelog/main_window.py index 071b759d12b44b4b87544bcdd1e23f05360140f8..dd9f16f7806231b5a2183c33754b7fe673f2e354 100644 --- a/cutelog/main_window.py +++ b/cutelog/main_window.py @@ -183,11 +183,14 @@ class MainWindow(*MainWindowBase): self.server.start() self.server_running = True await self.stop_signal.wait() - self.server.close_server() + self.server.close_server(wait=False) + + # executor is used here because stopping threads can take some time and stall the loop + await self.loop.run_in_executor(None, self.server.wait_connections_stopped) + self.server_running = False self.log.debug('Run got the stop_signal with reason {}'.format(self.stop_reason)) if self.stop_reason == 'restart': - # await self.wait_server_closed() continue elif self.stop_reason == 'pause': await self.start_server_again.wait() diff --git a/setup.py b/setup.py index 953500b970584428653c922e827c5c9e101e0986..fa521a3aaf5dba854d0c5a6032b10c2a3d8605f7 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ from os.path import dirname, join from setuptools import setup -VERSION = '1.1.0' +VERSION = '1.1.1' # def build_qt_resources():