added (yet untested) I2nLogger to log_helpers
authorChristian Herdtweck <christian.herdtweck@intra2net.com>
Thu, 10 Sep 2015 15:55:58 +0000 (17:55 +0200)
committerChristian Herdtweck <christian.herdtweck@intra2net.com>
Fri, 11 Sep 2015 07:16:23 +0000 (09:16 +0200)
log_helpers.py

index 373e11f..0b925cc 100644 (file)
 ShortLevelFormatter: provide a 4-char-sized field "shortlevel" for message
 urgency (dbug/info/warn/err /crit)
 
-Further ideas
+I2nLogger: logger that provides a notice(), allows omission for str.format
 
-* create logger sublcass with logger.notice()
+Further ideas: ::
 * allow milliseconds in dateformat field (see test_short_level_format)
 * create own basicConfig-like function that uses our classes as default
 
 .. codeauthor:: Christian Herdtweck, christian.herdtweck@intra2net.com
 """
 
-from logging import Formatter, DEBUG, INFO, WARNING, ERROR, CRITICAL, NOTSET
+import logging
+from logging import DEBUG, INFO, WARNING, ERROR, CRITICAL, NOTSET
 
+#: log level half-way between INFO and WARNING
+NOTICE = (INFO + WARNING)/2
+logging.addLevelName(NOTICE, 'NOTICE')
 
-class ShortLevelFormatter(Formatter):
+#: default formatting string for ShortLevelFormatter
+DEFAULT_SHORT_LEVEL_FORMAT = '%(asctime)s:%(msecs)03d %(shortlevel)s| %(msg)s'
+
+#: default formatting string for date/time in ShortLevelFormatter
+DEFAULT_SHORT_LEVEL_DATE_FORMAT = '%H:%M:%S'
+
+#: mapping from level name to level int for I2nLogger's set_level
+LEVEL_DICT = dict(notset=NOTSET, debug=DEBUG, info=INFO, notice=NOTICE,
+                  warning=WARNING, error=ERROR, critical=CRITICAL)
+
+#: min level allowed in I2nLogger
+MIN_LEVEL = NOTSET
+
+#: max level allowed in I2nLogger
+MAX_LEVEL = CRITICAL
+
+class ShortLevelFormatter(logging.Formatter):
     """
     Formatter for logging handlers that allows use of format field "shortlevel"
 
@@ -98,6 +118,95 @@ class ShortLevelFormatter(Formatter):
         self._shortlevel_dict[levelno] = shortlevel_str
 
 
+class I2nLogger:
+    """ a more convenient logger
+
+    Features::
+    * can be used as follows::
+
+        logger.info('the result is {0}', result)    # more convenient than...
+        logger.info('the result is {0}'.format(result))    # ... the original
+
+    * Has a :py:meth:`Logger.notice` function
+    * Has by default as only handler a :py:class:`logging.StreamHandler` to
+      stdout with a :py:class:`ShortLevelFormatter`
+    * Simplifies setting of logger's logging level using
+      :py:meth:`Logger.set_level`. The level is kept in sync between logger and
+      handlers and can be queried using :py:meth:`get_level`
+    * can specify name and level and [date]fmt all in constructor
+
+    ..note:: Creating multiple instances with the same name is possible but
+             will internally use the same logger. Each instance overwrites at
+             construction time the previous instances' handlers and levels!
+    """
+
+    def __init__(self, name, level=INFO, fmt=DEFAULT_SHORT_LEVEL_FORMAT,
+                 datefmt=DEFAULT_SHORT_LEVEL_DATE_FORMAT)
+        """ creates a I2nLogger; forwards args to logging.getLogger 
+        
+        ..note:: You should use a different name in each constructor call!
+        """
+        self._level = min(MAX_LEVEL, max(MIN_LEVEL, level))
+        self._log = logging.getLogger(name)
+        self._log.setLevel(self.level)
+
+        # remove handlers (sometimes there are mutliple by default)
+        for handler in self._log.handlers:
+            self._log.removeHandler(handler)
+
+        # create new handler and formatter
+        formatter = ShortLevelFormatter(fmt=fmt, datefmt=datefmt)
+        formatter.add_level(notice, 'note')
+        stdout_handler = logging.StreamHandler(formatter)
+        stdout_handler.setLevel(self.level)
+        self._log.addHandler(stdout_handler)
+
+    def debug(self, message, *args, **kwargs):
+        self.log(DEBUG, message, *args, **kwargs)
+
+    def info(self, message, *args, **kwargs):
+        self.log(INFO, message, *args, **kwargs)
+
+    def notice(self, message, *args, **kwargs):
+        self.log(NOTICE, message, *args, **kwargs)
+
+    def warning(self, message, *args, **kwargs):
+        self.log(WARNING, message, *args, **kwargs)
+
+    def error(self, message, *args, **kwargs):
+        self.log(ERROR, message, *args, **kwargs)
+
+    def critical(self, message, *args, **kwargs):
+        self.log(CRITICAL, message, *args, **kwargs)
+
+    def log(self, level, message, *args, **kwargs):
+        if level >= self._level:
+            self._log.log(level, message.format(*args), **kwargs)
+
+    def get_level(self):
+        """ return int level of this logger """
+        return self._level
+
+    def get_level_str(self):
+        """ returns :py:func:`logging.getLevelName` on :py:meth:`get_level` """
+        return logging.getLevelName(self._level)
+
+    def set_level(self, new_level):
+        """ set level given an int or a str
+
+        :arg new_level: int or str (str is converted to lower case)
+        :raises: KeyError if new_level is a string that is not in
+                 :py:data:`LEVEL_DICT`
+        """
+        if isstr(new_level):
+            self._level = LEVEL_DICT[new_level.lower()]
+        else:
+            self._level = min(MAX_LEVEL, max(MIN_LEVEL, level))
+        self._log.setLevel(self._level)
+        for handler in self._log.handlers:
+            handler.setLevel(self._level)
+
+
 def test_short_level_format():
     """ quick test of :py:class:`ShortLevelFormatter` """