diff options
author | Mike Frysinger <vapier@gentoo.org> | 2015-10-09 01:29:27 -0400 |
---|---|---|
committer | Mike Frysinger <vapier@gentoo.org> | 2015-10-09 01:29:27 -0400 |
commit | 3c5ef2e76dd5c57b64a160fbfc4b441569d1401e (patch) | |
tree | 5477a0afaa172eb4e0b227313000d1c84c28bf4f /catalyst/log.py | |
parent | main: group related command line flags (diff) | |
download | catalyst-3c5ef2e76dd5c57b64a160fbfc4b441569d1401e.tar.gz catalyst-3c5ef2e76dd5c57b64a160fbfc4b441569d1401e.tar.bz2 catalyst-3c5ef2e76dd5c57b64a160fbfc4b441569d1401e.zip |
log: new logging module to standardize catalyst output
This has everything you could ever want:
- control where output is sent (stdout or a file)
- control over log level
- automatic exit when CRITICAL is used
- automatic colorization of critical/error/warning messages
- explicit control over use of colors
- automatic handling of embedded newlines
- standardized output format
- all logging routed through a single logger (not the root)
- additional log level between warning & info: notice
This just lays the groundwork -- no code is actually converted over
to using this. That will be done in follow up commit(s).
Note: The default output level has changed from "info" to "notice".
That means the default display won't spam w/irrelevant details.
Example output (only the main.py module is converted):
$ ./bin/wrapper.py -s test
09 Oct 2015 01:34:56 EDT: NOTICE : Using default Catalyst configuration file: /etc/catalyst/catalyst.conf
ContentsMap: __init__(), search_order = ['pixz', 'lbzip2', 'isoinfo_l', 'squashfs', 'gzip', 'xz', 'bzip2', 'tar', 'isoinfo_f']
Creating Portage tree snapshot test from /usr/portage...
^C
$ ./bin/wrapper.py -s test --debug
09 Oct 2015 01:35:43 EDT: INFO : main.py:version: Catalyst git
09 Oct 2015 01:35:43 EDT: INFO : main.py:version: vcs version 4440e8908c677d8764e29b0f127e2dd6c02b7621, date Fri Oct 9 01:28:19 2015 -0400
09 Oct 2015 01:35:43 EDT: INFO : main.py:version: Copyright 2003-2015 Gentoo Foundation
09 Oct 2015 01:35:43 EDT: INFO : main.py:version: Copyright 2008-2012 various authors
09 Oct 2015 01:35:43 EDT: INFO : main.py:version: Distributed under the GNU General Public License version 2.1
09 Oct 2015 01:35:43 EDT: NOTICE : log.py:notice: Using default Catalyst configuration file: /etc/catalyst/catalyst.conf
09 Oct 2015 01:35:43 EDT: INFO : main.py:parse_config: Snapshot cache support enabled.
09 Oct 2015 01:35:43 EDT: INFO : main.py:parse_config: Kernel cache support enabled.
09 Oct 2015 01:35:43 EDT: INFO : main.py:parse_config: Autoresuming support enabled.
09 Oct 2015 01:35:43 EDT: INFO : main.py:parse_config: Package cache support enabled.
09 Oct 2015 01:35:43 EDT: INFO : main.py:parse_config: Seed cache support enabled.
09 Oct 2015 01:35:43 EDT: INFO : main.py:parse_config: Envscript support enabled.
09 Oct 2015 01:35:43 EDT: DEBUG : main.py:main: conf_values[options] = set(['snapcache', 'kerncache', 'autoresume', 'pkgcache', 'bindist', 'seedcache'])
^C
$ ./bin/wrapper.py -s test -C /asdf
09 Oct 2015 01:36:59 EDT: NOTICE : Using default Catalyst configuration file: /etc/catalyst/catalyst.conf
ContentsMap: __init__(), search_order = ['pixz', 'lbzip2', 'isoinfo_l', 'squashfs', 'gzip', 'xz', 'bzip2', 'tar', 'isoinfo_f']
!!! catalyst: Syntax error: 0
09 Oct 2015 01:36:59 EDT: CRITICAL: Could not parse commandline
Diffstat (limited to 'catalyst/log.py')
-rw-r--r-- | catalyst/log.py | 127 |
1 files changed, 127 insertions, 0 deletions
diff --git a/catalyst/log.py b/catalyst/log.py new file mode 100644 index 00000000..871c53cf --- /dev/null +++ b/catalyst/log.py @@ -0,0 +1,127 @@ +# Copyright 2003-2015 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 + +"""Logging related code + +This largely exposes the same interface as the logging module except we add +another level "notice" between warning & info, and all output goes through +the "catalyst" logger. +""" + +from __future__ import print_function + +import logging +import logging.handlers +import os +import sys +import time + + +class CatalystLogger(logging.Logger): + """Override the _log member to autosplit on new lines""" + + def _log(self, level, msg, args, **kwargs): + """If given a multiline message, split it""" + # We have to interpolate it first in case they spread things out + # over multiple lines like: Bad Thing:\n%s\nGoodbye! + msg %= args + for line in msg.splitlines(): + super(CatalystLogger, self)._log(level, line, (), **kwargs) + + +# The logger that all output should go through. +# This is ugly because we want to not perturb the logging module state. +_klass = logging.getLoggerClass() +logging.setLoggerClass(CatalystLogger) +logger = logging.getLogger('catalyst') +logging.setLoggerClass(_klass) +del _klass + + +# Set the notice level between warning and info. +NOTICE = (logging.WARNING + logging.INFO) / 2 +logging.addLevelName(NOTICE, 'NOTICE') + + +# The API we expose to consumers. +def notice(msg, *args, **kwargs): + """Log a notice message""" + logger.log(NOTICE, msg, *args, **kwargs) + +def critical(msg, *args, **kwargs): + """Log a critical message and then exit""" + status = kwargs.pop('status', 1) + logger.critical(msg, *args, **kwargs) + sys.exit(status) + +error = logger.error +warning = logger.warning +info = logger.info +debug = logger.debug + + +class CatalystFormatter(logging.Formatter): + """Mark bad messages with colors automatically""" + + _COLORS = { + 'CRITICAL': '\033[1;35m', + 'ERROR': '\033[1;31m', + 'WARNING': '\033[1;33m', + 'DEBUG': '\033[1;34m', + } + _NORMAL = '\033[0m' + + @staticmethod + def detect_color(): + """Figure out whether the runtime env wants color""" + if 'NOCOLOR' is os.environ: + return False + return os.isatty(sys.stdout.fileno()) + + def __init__(self, *args, **kwargs): + """Initialize""" + color = kwargs.pop('color', None) + if color is None: + color = self.detect_color() + if not color: + self._COLORS = {} + + super(CatalystFormatter, self).__init__(*args, **kwargs) + + def format(self, record, **kwargs): + """Format the |record| with our color settings""" + msg = super(CatalystFormatter, self).format(record, **kwargs) + color = self._COLORS.get(record.levelname) + if color: + return color + msg + self._NORMAL + else: + return msg + + +def setup_logging(level, output=None, debug=False, color=None): + """Initialize the logging module using the |level| level""" + # The incoming level will be things like "info", but setLevel wants + # the numeric constant. Convert it here. + level = logging.getLevelName(level.upper()) + + # The good stuff. + fmt = '%(asctime)s: %(levelname)-8s: ' + if debug: + fmt += '%(filename)s:%(funcName)s: ' + fmt += '%(message)s' + + # Figure out where to send the log output. + if output is None: + handler = logging.StreamHandler(stream=sys.stdout) + else: + handler = logging.FileHandler(output) + + # Use a date format that is readable by humans & machines. + # Think e-mail/RFC 2822: 05 Oct 2013 18:58:50 EST + tzname = time.strftime('%Z', time.localtime()) + datefmt = '%d %b %Y %H:%M:%S ' + tzname + formatter = CatalystFormatter(fmt, datefmt, color=color) + handler.setFormatter(formatter) + + logger.addHandler(handler) + logger.setLevel(level) |