summaryrefslogtreecommitdiffstats
path: root/deluge/log.py
diff options
context:
space:
mode:
authorPedro Algarvio <ufs@ufsoft.org>2010-12-06 11:20:22 +0000
committerPedro Algarvio <ufs@ufsoft.org>2010-12-06 11:20:22 +0000
commit3b00a7de59b2f6ed889d4c0816051af50725fa12 (patch)
treed09b5054b61cd38e03a0582a83a6d7e023893f89 /deluge/log.py
parent3d64f0d8daa0d7ee9ddefe17e55d6247c4657bcf (diff)
downloaddeluge-3b00a7de59b2f6ed889d4c0816051af50725fa12.tar.gz
deluge-3b00a7de59b2f6ed889d4c0816051af50725fa12.tar.bz2
deluge-3b00a7de59b2f6ed889d4c0816051af50725fa12.zip
Swhiched the old style logging, ie, a single logger for all logging output to several loggers. This brings the ability to tweak the logging levels for each of the loggers, ie, we can have the "deluge" logger be at the ERROR level while having "deluge.core" at the DEBUG level, meaning we will only see log message for all of the deluge loggers above the ERROR level while still having all messages above the DEBUG level for the "deluge.core" logger and it's children. This kind of tweak can be achieved by adding a file named "logging.conf" to deluge's config dir and this is explained on the `deluge.log.tweak_logging_levels` function.
Passing `-r` to the cli's while also passing `-l` will make the logfile rotate when reaching 5Mb in size. Three backups will be kept at all times. All deluge's code is now using this new style logging along with the git hosted plugins. For other plugins not hosted by deluge, which still imports `LOG` as the logger, a deprecation warning will be shown explaining the required changes needed to use the new style logging. New plugins created by the `create_plugin` script will use the new logging facilities.
Diffstat (limited to 'deluge/log.py')
-rw-r--r--deluge/log.py228
1 files changed, 211 insertions, 17 deletions
diff --git a/deluge/log.py b/deluge/log.py
index 327375e06..a0a4e1dbc 100644
--- a/deluge/log.py
+++ b/deluge/log.py
@@ -2,6 +2,7 @@
# log.py
#
# Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com>
+# Copyright (C) 2010 Pedro Algarvio <pedro@algarvio.me>
#
# Deluge is free software.
#
@@ -33,18 +34,100 @@
#
#
-
"""Logging functions"""
+import os
import logging
+import inspect
+import pkg_resources
+from deluge import configmanager, common, component
+from twisted.internet import defer
+from twisted.python.log import PythonLoggingObserver
+
+__all__ = ["setupLogger", "setLoggerLevel", "getPluginLogger", "LOG"]
+
+LoggingLoggerClass = logging.getLoggerClass()
+
+if 'dev' in common.get_version():
+ DEFAULT_LOGGING_FORMAT = "%%(asctime)s.%%(msecs)03.0f [%%(name)-%ds:%%(lineno)-4d][%%(levelname)-8s] %%(message)s"
+else:
+ DEFAULT_LOGGING_FORMAT = "%%(asctime)s [%%(name)-%ds][%%(levelname)-8s] %%(message)s"
+MAX_LOGGER_NAME_LENGTH = 3
+
+class Logging(LoggingLoggerClass):
+ def __init__(self, logger_name):
+ LoggingLoggerClass.__init__(self, logger_name)
+
+ # This makes module name padding increase to the biggest module name
+ # so that logs keep readability.
+ global MAX_LOGGER_NAME_LENGTH
+ if len(logger_name) > MAX_LOGGER_NAME_LENGTH:
+ MAX_LOGGER_NAME_LENGTH = len(logger_name)
+ for handler in logging.getLogger().handlers:
+ handler.setFormatter(logging.Formatter(
+ DEFAULT_LOGGING_FORMAT % MAX_LOGGER_NAME_LENGTH,
+ datefmt="%H:%M:%S"
+ ))
+
+ @defer.inlineCallbacks
+ def garbage(self, msg, *args, **kwargs):
+ yield LoggingLoggerClass.log(self, 1, msg, *args, **kwargs)
+
+ @defer.inlineCallbacks
+ def trace(self, msg, *args, **kwargs):
+ yield LoggingLoggerClass.log(self, 5, msg, *args, **kwargs)
+
+ @defer.inlineCallbacks
+ def debug(self, msg, *args, **kwargs):
+ yield LoggingLoggerClass.debug(self, msg, *args, **kwargs)
+
+ @defer.inlineCallbacks
+ def info(self, msg, *args, **kwargs):
+ yield LoggingLoggerClass.info(self, msg, *args, **kwargs)
+
+ @defer.inlineCallbacks
+ def warning(self, msg, *args, **kwargs):
+ yield LoggingLoggerClass.warning(self, msg, *args, **kwargs)
+
+ warn = warning
+
+ @defer.inlineCallbacks
+ def error(self, msg, *args, **kwargs):
+ yield LoggingLoggerClass.error(self, msg, *args, **kwargs)
+
+ @defer.inlineCallbacks
+ def critical(self, msg, *args, **kwargs):
+ yield LoggingLoggerClass.critical(self, msg, *args, **kwargs)
+
+ @defer.inlineCallbacks
+ def exception(self, msg, *args, **kwargs):
+ yield LoggingLoggerClass.exception(self, msg, *args, **kwargs)
+
+ def findCaller(self):
+ f = logging.currentframe().f_back
+ rv = "(unknown file)", 0, "(unknown function)"
+ while hasattr(f, "f_code"):
+ co = f.f_code
+ filename = os.path.normcase(co.co_filename)
+ if filename in (__file__.replace('.pyc', '.py'),
+ defer.__file__.replace('.pyc', '.py')):
+ f = f.f_back
+ continue
+ rv = (filename, f.f_lineno, co.co_name)
+ break
+ return rv
levels = {
"info": logging.INFO,
"warning": logging.WARNING,
"error": logging.ERROR,
"none": logging.CRITICAL,
- "debug": logging.DEBUG
+ "debug": logging.DEBUG,
+ "trace": 5,
+ "garbage": 1
}
+
+
def setupLogger(level="error", filename=None, filemode="w"):
"""
Sets up the basic logger and if `:param:filename` is set, then it will log
@@ -53,30 +136,141 @@ def setupLogger(level="error", filename=None, filemode="w"):
:param level: str, the level to log
:param filename: str, the file to log to
"""
+ import logging
+
+ if logging.getLoggerClass() is not Logging:
+ logging.setLoggerClass(Logging)
+
+ level = levels.get(level, "error")
- if not level or level not in levels:
- level = "error"
+ rootLogger = logging.getLogger()
- logging.basicConfig(
- level=levels[level],
- format="[%(levelname)-8s] %(asctime)s %(module)s:%(lineno)d %(message)s",
- datefmt="%H:%M:%S",
- filename=filename,
- filemode=filemode
+ if filename and filemode=='a':
+ import logging.handlers
+ handler = logging.handlers.RotatingFileHandler(
+ filename, filemode,
+ maxBytes=5*1024*1024, # 5 Mb
+ backupCount=3,
+ encoding='utf-8',
+ delay=0
+ )
+ elif filename and filemode=='w':
+ handler = logging.FileHandler(filename, filemode, 'utf-8', delay=0)
+ else:
+ handler = logging.StreamHandler()
+ handler.setLevel(level)
+
+ formatter = logging.Formatter(
+ DEFAULT_LOGGING_FORMAT % MAX_LOGGER_NAME_LENGTH,
+ datefmt="%H:%M:%S"
)
-def setLoggerLevel(level):
+ handler.setFormatter(formatter)
+ rootLogger.addHandler(handler)
+ rootLogger.setLevel(level)
+
+ twisted_logging = PythonLoggingObserver('twisted')
+ twisted_logging.start()
+ logging.getLogger("twisted").setLevel(level)
+
+def tweak_logging_levels():
+ """This function allows tweaking the logging levels for all or some loggers.
+ This is mostly usefull for developing purposes hence the contents of the
+ file are NOT like regular deluge config file's.
+
+ To use is, create a file named "logging.conf" on your Deluge's config dir
+ with contents like for example:
+ deluge:warn
+ deluge.core:debug
+ deluge.plugin:error
+
+ What the above mean is the logger "deluge" will be set to the WARN level,
+ the "deluge.core" logger will be set to the DEBUG level and the
+ "deluge.plugin" will be set to the ERROR level.
+
+ Remember, one rule per line and this WILL override the setting passed from
+ the command line.
+ """
+ logging_config_file = os.path.join(configmanager.get_config_dir(),
+ 'logging.conf')
+ if not os.path.isfile(logging_config_file):
+ return
+ log = logging.getLogger(__name__)
+ log.warn("logging.conf found! tweaking logging levels from %s",
+ logging_config_file)
+ for line in open(logging_config_file, 'r'):
+ if line.strip().startswith("#"):
+ continue
+ name, level = line.strip().split(':')
+ if level in levels:
+ log.warn("Setting logger \"%s\" to logging level \"%s\"", name, level)
+ logging.getLogger(name).setLevel(levels.get(level))
+
+
+def setLoggerLevel(level, logger_name=None):
"""
Sets the logger level.
:param level: str, a string representing the desired level
+ :param logger_name: str, a string representing desired logger name for which
+ the level should change. The default is "None" will will
+ tweak the root logger level.
"""
- if level not in levels:
- return
+ logging.getLogger(logger_name).setLevel(levels.get(level, "error"))
+
+
+def getPluginLogger(logger_name):
+ return logging.getLogger("deluge.plugin.%s" % logger_name)
+
+
+DEPRECATION_WARNING = """You seem to be using old style logging on your code, ie:
+ from deluge.log import LOG as log
+
+This has been deprecated in favour of an enhanced logging system and "LOG" will
+be removed on the next major version release of Deluge, meaning, code will break,
+specially plugins.
+If you're seeing this message and you're not the developer of the plugin which
+triggered this warning, please report to it's author.
+If you're the developer, please stop using the above code and instead use:
+
+ from deluge.log import getPluginLogger
+ log = getPluginLogger(__name__)
+
+
+The above will result in, regarding the "Label" plugin for example a log message similar to:
+ 15:33:54 [deluge.plugin.label.core:78 ][INFO ] *** Start Label plugin ***
+
+If you wish not to have 'deluge.plugin' on the log message you can then instead use:
+
+ import logging
+ log = logging.getLogger(__name__)
+
+The above will result in, regarding the "Label" plugin for example a log message similar to:
+ 15:33:54 [label.core:78 ][INFO ] *** Start Label plugin ***
+
+Triggering code:"""
- global LOG
- LOG.setLevel(levels[level])
+class __BackwardsCompatibleLOG(object):
+ def __getattribute__(self, name):
+ import warnings
+ logger_name = 'deluge'
+ stack = inspect.stack()
+ module_stack = stack.pop(1)
+ caller_module = inspect.getmodule(module_stack[0])
+ warnings.warn_explicit(DEPRECATION_WARNING, DeprecationWarning,
+ module_stack[1], module_stack[2],
+ caller_module.__name__)
+ for member in stack:
+ module = inspect.getmodule(member[0])
+ if not module:
+ continue
+ if module.__name__ in ('deluge.plugins.pluginbase',
+ 'deluge.plugins.init'):
+ logger_name += '.plugin.%s' % caller_module.__name__
+ # Monkey Patch The Plugin Module
+ caller_module.log = logging.getLogger(logger_name)
+ break
+ return getattr(logging.getLogger(logger_name), name)
-# Get the logger
-LOG = logging.getLogger("deluge")
+LOG = __BackwardsCompatibleLOG()