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"
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` """