diff options
-rw-r--r-- | ranger/__init__.py | 35 | ||||
-rw-r--r-- | ranger/api/commands.py | 5 | ||||
-rw-r--r-- | ranger/core/actions.py | 14 | ||||
-rw-r--r-- | ranger/core/fm.py | 19 | ||||
-rw-r--r-- | ranger/core/main.py | 46 | ||||
-rw-r--r-- | ranger/ext/logutils.py | 78 | ||||
-rw-r--r-- | ranger/ext/vcs/vcs.py | 7 |
7 files changed, 144 insertions, 60 deletions
diff --git a/ranger/__init__.py b/ranger/__init__.py index 4d8e4afe..9c4f8922 100644 --- a/ranger/__init__.py +++ b/ranger/__init__.py @@ -29,44 +29,9 @@ CACHEDIR = os.path.expanduser("~/.cache/ranger") USAGE = '%prog [options] [path]' VERSION = 'ranger-master %s\n\nPython %s' % (__version__, sys.version) -try: - ExceptionClass = FileNotFoundError -except NameError: - ExceptionClass = IOError -try: - LOGFILE = tempfile.gettempdir() + '/ranger_errorlog' -except ExceptionClass: - LOGFILE = '/dev/null' -del ExceptionClass # If the environment variable XDG_CONFIG_HOME is non-empty, CONFDIR is ignored # and the configuration directory will be $XDG_CONFIG_HOME/ranger instead. CONFDIR = '~/.config/ranger' -# Debugging functions. These will be activated when run with --debug. -# Example usage in the code: -# import ranger; ranger.log("hello world") - - -def log(*objects, **keywords): - """Writes objects to a logfile (for the purpose of debugging only.) - Has the same arguments as print() in python3. - """ - from ranger import arg - if LOGFILE is None or not arg.debug or arg.clean: - return - start = keywords.get('start', 'ranger:') - sep = keywords.get('sep', ' ') - end = keywords.get('end', '\n') - _file = keywords['file'] if 'file' in keywords else open(LOGFILE, 'a') - _file.write(sep.join(map(str, (start, ) + objects)) + end) - - -def log_traceback(): - from ranger import arg - if LOGFILE is None or not arg.debug or arg.clean: - return - import traceback - traceback.print_stack(file=open(LOGFILE, 'a')) - from ranger.core.main import main diff --git a/ranger/api/commands.py b/ranger/api/commands.py index 9b8ec7d5..93c50adb 100644 --- a/ranger/api/commands.py +++ b/ranger/api/commands.py @@ -236,6 +236,11 @@ class Command(FileManagerAware): break return flags, rest + @lazy_property + def log(self): + import logging + return logging.getLogger('ranger.commands.' + self.__class__.__name__) + # XXX: Lazy properties? Not so smart? self.line can change after all! @lazy_property def _tabinsert_left(self): diff --git a/ranger/core/actions.py b/ranger/core/actions.py index ab3abb06..1b35e57b 100644 --- a/ranger/core/actions.py +++ b/ranger/core/actions.py @@ -13,6 +13,7 @@ from inspect import cleandoc from stat import S_IEXEC from hashlib import sha1 from sys import version_info +from logging import getLogger import ranger from ranger.ext.direction import Direction @@ -31,6 +32,8 @@ from ranger.core.linemode import DEFAULT_LINEMODE MACRO_FAIL = "<\x01\x01MACRO_HAS_NO_VALUE\x01\01>" +log = getLogger(__name__) + class _MacroTemplate(string.Template): """A template for substituting macros in commands""" @@ -152,7 +155,7 @@ class Actions(FileManagerAware, SettingsAware): elif bad is True and ranger.arg.debug: raise Exception(str(text)) text = str(text) - self.log.appendleft(text) + log.debug("Command notify invoked: [Bad: {0}, Text: '{1}']".format(bad, text)) if self.ui and self.ui.is_on: self.ui.status.notify(" ".join(text.split("\n")), duration=duration, bad=bad) @@ -344,9 +347,10 @@ class Actions(FileManagerAware, SettingsAware): Load a config file. """ filename = os.path.expanduser(filename) + log.debug("Sourcing config file '{0}'".format(filename)) with open(filename, 'r') as f: for line in f: - line = line.lstrip().rstrip("\r\n") + line = line.strip(" \r\n") if line.startswith("#") or not line.strip(): continue try: @@ -869,11 +873,13 @@ class Actions(FileManagerAware, SettingsAware): self.notify("Could not find manpage.", bad=True) def display_log(self): + logs = list(self.get_log()) pager = self.ui.open_pager() - if self.log: - pager.set_source(["Message Log:"] + list(self.log)) + if logs: + pager.set_source(["Message Log:"] + logs) else: pager.set_source(["Message Log:", "No messages!"]) + pager.move(to=100, percentage=True) def display_file(self): if not self.thisfile or not self.thisfile.is_file: diff --git a/ranger/core/fm.py b/ranger/core/fm.py index a08de2e1..5d630464 100644 --- a/ranger/core/fm.py +++ b/ranger/core/fm.py @@ -5,6 +5,7 @@ from time import time from collections import deque +import logging import mimetypes import os.path import pwd @@ -25,8 +26,10 @@ from ranger.core.metadata import MetadataManager from ranger.ext.rifle import Rifle from ranger.container.directory import Directory from ranger.ext.signals import SignalDispatcher -from ranger import __version__ from ranger.core.loader import Loader +from ranger.ext import logutils + +log = logging.getLogger(__name__) class FM(Actions, SignalDispatcher): @@ -50,7 +53,6 @@ class FM(Actions, SignalDispatcher): self.ui = ui self.start_paths = paths self.directories = dict() - self.log = deque(maxlen=1000) self.bookmarks = bookmarks self.current_tab = 1 self.tabs = {} @@ -71,10 +73,6 @@ class FM(Actions, SignalDispatcher): self.hostname = socket.gethostname() self.home_path = os.path.expanduser('~') - self.log.appendleft('ranger {0} started! Process ID is {1}.' - .format(__version__, os.getpid())) - self.log.appendleft('Running on Python ' + sys.version.replace('\n', '')) - mimetypes.knownfiles.append(os.path.expanduser('~/.mime.types')) mimetypes.knownfiles.append(self.relpath('data/mime.types')) self.mimetypes = mimetypes.MimeTypes() @@ -205,6 +203,15 @@ class FM(Actions, SignalDispatcher): if debug: raise + def get_log(self): + """Return the current log + + The log is returned as a list of string + """ + for log in logutils.log_queue: + for line in log.split('\n'): + yield line + def _get_image_displayer(self): if self.settings.preview_images_method == "w3m": return W3MImageDisplayer() diff --git a/ranger/core/main.py b/ranger/core/main.py index 93c71e81..b0e3b600 100644 --- a/ranger/core/main.py +++ b/ranger/core/main.py @@ -6,6 +6,10 @@ import os.path import sys import tempfile +from ranger import __version__ +from logging import getLogger + +log = getLogger(__name__) def main(): @@ -15,6 +19,14 @@ def main(): from ranger.container.settings import Settings from ranger.core.shared import FileManagerAware, SettingsAware from ranger.core.fm import FM + from ranger.ext.logutils import setup_logging + + ranger.arg = arg = parse_arguments() + setup_logging(debug=arg.debug, logfile=arg.logfile) + + log.info("Ranger version {0}".format(__version__)) + log.info('Running on Python ' + sys.version.replace('\n', '')) + log.info("Process ID is {0}".format(os.getpid())) try: locale.setlocale(locale.LC_ALL, '') @@ -31,7 +43,9 @@ def main(): if 'SHELL' not in os.environ: os.environ['SHELL'] = 'sh' - ranger.arg = arg = parse_arguments() + log.debug("config dir: '{0}'".format(arg.confdir)) + log.debug("cache dir: '{0}'".format(arg.cachedir)) + if arg.copy_config is not None: fm = FM() fm.copy_config_files(arg.copy_config) @@ -73,8 +87,6 @@ def main(): "deprecated.\nPlease use the standalone file launcher " "'rifle' instead.\n") - def print_function(string): - print(string) from ranger.ext.rifle import Rifle fm = FM() if not arg.clean and os.path.isfile(fm.confpath('rifle.conf')): @@ -112,7 +124,7 @@ def main(): if fm.username == 'root': fm.settings.preview_files = False fm.settings.use_preview_script = False - fm.log.appendleft("Running as root, disabling the file previews.") + log.info("Running as root, disabling the file previews.") if not arg.debug: from ranger.ext import curses_interrupt_handler curses_interrupt_handler.install_interrupt_handler() @@ -199,6 +211,8 @@ def parse_arguments(): help="activate debug mode") parser.add_option('-c', '--clean', action='store_true', help="don't touch/require any config files. ") + parser.add_option('--logfile', type='string', metavar='file', + help="log file to use, '-' for stderr") parser.add_option('-r', '--confdir', type='string', metavar='dir', default=default_confdir, help="change the configuration directory. (%default)") @@ -265,14 +279,18 @@ def load_settings(fm, clean): allow_access_to_confdir(ranger.arg.confdir, True) # Load custom commands - if os.path.exists(fm.confpath('commands.py')): + custom_comm_path = fm.confpath('commands.py') + if os.path.exists(custom_comm_path): old_bytecode_setting = sys.dont_write_bytecode sys.dont_write_bytecode = True try: import commands fm.commands.load_commands_from_module(commands) - except ImportError: - pass + except ImportError as e: + log.debug("Failed to import custom commands from '{0}'".format(custom_comm_path)) + log.exception(e) + else: + log.debug("Loaded custom commands from '{0}'".format(custom_comm_path)) sys.dont_write_bytecode = old_bytecode_setting allow_access_to_confdir(ranger.arg.confdir, False) @@ -297,6 +315,7 @@ def load_settings(fm, clean): pass else: if not os.path.exists(fm.confpath('plugins', '__init__.py')): + log.debug("Creating missing '__init__.py' file in plugin folder") f = open(fm.confpath('plugins', '__init__.py'), 'w') f.close() @@ -313,11 +332,12 @@ def load_settings(fm, clean): else: module = importlib.import_module('plugins.' + plugin) fm.commands.load_commands_from_module(module) - fm.log.appendleft("Loaded plugin '%s'." % plugin) - except Exception: - import traceback - fm.log.extendleft(reversed(traceback.format_exc().splitlines())) - fm.notify("Error in plugin '%s'" % plugin, bad=True) + log.debug("Loaded plugin '{0}'".format(plugin)) + except Exception as e: + mex = "Error while loading plugin '{0}'".format(plugin) + log.error(mex) + log.exception(e) + fm.notify(mex, bad=True) ranger.fm = None # COMPAT: Load the outdated options.py @@ -360,6 +380,8 @@ def allow_access_to_confdir(confdir, allow): print("To run ranger without the need for configuration") print("files, use the --clean option.") raise SystemExit() + else: + log.debug("Created config directory '{0}'".format(confdir)) if confdir not in sys.path: sys.path[0:0] = [confdir] else: diff --git a/ranger/ext/logutils.py b/ranger/ext/logutils.py new file mode 100644 index 00000000..0de6c333 --- /dev/null +++ b/ranger/ext/logutils.py @@ -0,0 +1,78 @@ +import logging +from collections import deque + +LOG_FORMAT = "[%(levelname)s] %(message)s" +LOG_FORMAT_EXT = "%(asctime)s,%(msecs)d [%(name)s] |%(levelname)s| %(message)s" +LOG_DATA_FORMAT = "%H:%M:%S" + + +class QueueHandler(logging.Handler): + """ + This handler store logs events into a queue. + """ + + def __init__(self, queue): + """ + Initialize an instance, using the passed queue. + """ + logging.Handler.__init__(self) + self.queue = queue + + def enqueue(self, record): + """ + Enqueue a log record. + """ + self.queue.append(record) + + def emit(self, record): + self.enqueue(self.format(record)) + + +log_queue = deque(maxlen=1000) +concise_formatter = logging.Formatter(fmt=LOG_FORMAT, datefmt=LOG_DATA_FORMAT) +extended_formatter = logging.Formatter(fmt=LOG_FORMAT_EXT, datefmt=LOG_DATA_FORMAT) + + +def setup_logging(debug=True, logfile=None): + """ + All the produced logs using the standard logging function + will be collected in a queue by the `queue_handler` as well + as outputted on the standard error `stderr_handler`. + + The verbosity and the format of the log message is + controlled by the `debug` parameter + + - debug=False: + a concise log format will be used, debug messsages will be discarded + and only important message will be passed to the `stderr_handler` + + - debug=True: + an extended log format will be used, all messages will be processed + by both the handlers + """ + root_logger = logging.getLogger() + + if debug: + # print all logging in extended format + log_level = logging.DEBUG + formatter = extended_formatter + else: + # print only warning and critical message + # in a concise format + log_level = logging.INFO + formatter = concise_formatter + + handlers = [] + handlers.append(QueueHandler(log_queue)) + if logfile: + if logfile is '-': + handlers.append(logging.StreamHandler()) + else: + handlers.append(logging.FileHandler(logfile)) + + for handler in handlers: + handler.setLevel(log_level) + handler.setFormatter(formatter) + root_logger.addHandler(handler) + + root_logger.setLevel(0) diff --git a/ranger/ext/vcs/vcs.py b/ranger/ext/vcs/vcs.py index cfdc1e3b..9c2a8b5a 100644 --- a/ranger/ext/vcs/vcs.py +++ b/ranger/ext/vcs/vcs.py @@ -7,6 +7,7 @@ import os import subprocess import threading import time +from logging import getLogger from ranger.ext.spawn import spawn # Python2 compatibility @@ -19,6 +20,7 @@ try: except NameError: FileNotFoundError = OSError # pylint: disable=redefined-builtin +log = getLogger(__name__) class VcsError(Exception): """VCS exception""" @@ -470,9 +472,8 @@ class VcsThread(threading.Thread): # pylint: disable=too-many-instance-attribut column.need_redraw = True self.ui.status.need_redraw = True self.ui.redraw() - except Exception: # pylint: disable=broad-except - import traceback - self.ui.fm.log.extendleft(reversed(traceback.format_exc().splitlines())) + except Exception as e: # pylint: disable=broad-except + log.exception(e) self.ui.fm.notify('VCS Exception', bad=True) def pause(self): |