Gentoo Archives: gentoo-catalyst

From: Mike Frysinger <vapier@g.o>
To: gentoo-catalyst@l.g.o
Subject: [gentoo-catalyst] [PATCH 2/3] log: new logging module to standardize catalyst output
Date: Fri, 09 Oct 2015 05:57:44
Message-Id: 1444370248-13159-2-git-send-email-vapier@gentoo.org
In Reply to: [gentoo-catalyst] [PATCH 1/3] main: group related command line flags by Mike Frysinger
1 This has everything you could ever want:
2 - control where output is sent (stdout or a file)
3 - control over log level
4 - automatic exit when CRITICAL is used
5 - automatic colorization of critical/error/warning messages
6 - explicit control over use of colors
7 - automatic handling of embedded newlines
8 - standardized output format
9 - all logging routed through a single logger (not the root)
10 - additional log level between warning & info: notice
11
12 This just lays the groundwork -- no code is actually converted over
13 to using this. That will be done in follow up commit(s).
14
15 Note: The default output level has changed from "info" to "notice".
16 That means the default display won't spam w/irrelevant details.
17
18 Example output (only the main.py module is converted):
19 $ ./bin/wrapper.py -s test
20 09 Oct 2015 01:34:56 EDT: NOTICE : Using default Catalyst configuration file: /etc/catalyst/catalyst.conf
21 ContentsMap: __init__(), search_order = ['pixz', 'lbzip2', 'isoinfo_l', 'squashfs', 'gzip', 'xz', 'bzip2', 'tar', 'isoinfo_f']
22 Creating Portage tree snapshot test from /usr/portage...
23 ^C
24
25 $ ./bin/wrapper.py -s test --debug
26 09 Oct 2015 01:35:43 EDT: INFO : main.py:version: Catalyst git
27 09 Oct 2015 01:35:43 EDT: INFO : main.py:version: vcs version 4440e8908c677d8764e29b0f127e2dd6c02b7621, date Fri Oct 9 01:28:19 2015 -0400
28 09 Oct 2015 01:35:43 EDT: INFO : main.py:version: Copyright 2003-2015 Gentoo Foundation
29 09 Oct 2015 01:35:43 EDT: INFO : main.py:version: Copyright 2008-2012 various authors
30 09 Oct 2015 01:35:43 EDT: INFO : main.py:version: Distributed under the GNU General Public License version 2.1
31 09 Oct 2015 01:35:43 EDT: NOTICE : log.py:notice: Using default Catalyst configuration file: /etc/catalyst/catalyst.conf
32 09 Oct 2015 01:35:43 EDT: INFO : main.py:parse_config: Snapshot cache support enabled.
33 09 Oct 2015 01:35:43 EDT: INFO : main.py:parse_config: Kernel cache support enabled.
34 09 Oct 2015 01:35:43 EDT: INFO : main.py:parse_config: Autoresuming support enabled.
35 09 Oct 2015 01:35:43 EDT: INFO : main.py:parse_config: Package cache support enabled.
36 09 Oct 2015 01:35:43 EDT: INFO : main.py:parse_config: Seed cache support enabled.
37 09 Oct 2015 01:35:43 EDT: INFO : main.py:parse_config: Envscript support enabled.
38 09 Oct 2015 01:35:43 EDT: DEBUG : main.py:main: conf_values[options] = set(['snapcache', 'kerncache', 'autoresume', 'pkgcache', 'bindist', 'seedcache'])
39 ^C
40
41 $ ./bin/wrapper.py -s test -C /asdf
42 09 Oct 2015 01:36:59 EDT: NOTICE : Using default Catalyst configuration file: /etc/catalyst/catalyst.conf
43 ContentsMap: __init__(), search_order = ['pixz', 'lbzip2', 'isoinfo_l', 'squashfs', 'gzip', 'xz', 'bzip2', 'tar', 'isoinfo_f']
44
45 !!! catalyst: Syntax error: 0
46
47 09 Oct 2015 01:36:59 EDT: CRITICAL: Could not parse commandline
48 ---
49 catalyst/log.py | 130 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
50 catalyst/main.py | 30 ++++++++++++-
51 2 files changed, 158 insertions(+), 2 deletions(-)
52 create mode 100644 catalyst/log.py
53
54 diff --git a/catalyst/log.py b/catalyst/log.py
55 new file mode 100644
56 index 0000000..bf39116
57 --- /dev/null
58 +++ b/catalyst/log.py
59 @@ -0,0 +1,130 @@
60 +# Copyright 2003-2015 Gentoo Foundation
61 +# Distributed under the terms of the GNU General Public License v2
62 +
63 +"""Logging related code
64 +
65 +This largely exposes the same interface as the logging module except we add
66 +another level "notice" between warning & info, and all output goes through
67 +the "catalyst" logger.
68 +"""
69 +
70 +from __future__ import print_function
71 +
72 +import logging
73 +import logging.handlers
74 +import os
75 +import sys
76 +import time
77 +
78 +
79 +class CatalystLogger(logging.Logger):
80 + """Override the _log member to autosplit on new lines"""
81 +
82 + def _log(self, level, msg, args, **kwargs):
83 + """If given a multiline message, split it"""
84 + # We have to interpolate it first in case they spread things out
85 + # over multiple lines like: Bad Thing:\n%s\nGoodbye!
86 + msg %= args
87 + for line in msg.splitlines():
88 + super(CatalystLogger, self)._log(level, line, (), **kwargs)
89 +
90 +
91 +# The logger that all output should go through.
92 +# This is ugly because we want to not perturb the logging module state.
93 +_klass = logging.getLoggerClass()
94 +logging.setLoggerClass(CatalystLogger)
95 +logger = logging.getLogger('catalyst')
96 +logging.setLoggerClass(_klass)
97 +del _klass
98 +
99 +
100 +# Set the notice level between warning and info.
101 +NOTICE = (logging.WARNING + logging.INFO) / 2
102 +logging.addLevelName(NOTICE, 'NOTICE')
103 +
104 +
105 +# The API we expose to consumers.
106 +def notice(msg, *args, **kwargs):
107 + """Log a notice message"""
108 + logger.log(NOTICE, msg, *args, **kwargs)
109 +
110 +def critical(msg, *args, **kwargs):
111 + """Log a critical message and then exit"""
112 + status = kwargs.pop('status', 1)
113 + logger.critical(msg, *args, **kwargs)
114 + sys.exit(status)
115 +
116 +error = logger.error
117 +warning = logger.warning
118 +info = logger.info
119 +debug = logger.debug
120 +
121 +
122 +class CatalystFormatter(logging.Formatter):
123 + """Mark bad messages with colors automatically"""
124 +
125 + _COLORS = {
126 + 'CRITICAL': '\033[1;35m',
127 + 'ERROR': '\033[1;31m',
128 + 'WARNING': '\033[1;33m',
129 + 'DEBUG': '\033[1;34m',
130 + }
131 + _NORMAL = '\033[0m'
132 +
133 + @staticmethod
134 + def detect_color():
135 + """Figure out whether the runtime env wants color"""
136 + if 'NOCOLOR' is os.environ:
137 + return False
138 + return os.isatty(sys.stdout.fileno())
139 +
140 + def __init__(self, *args, **kwargs):
141 + """Initialize"""
142 + color = kwargs.pop('color', None)
143 + if color is None:
144 + color = self.detect_color()
145 + if not color:
146 + self._COLORS = {}
147 +
148 + super(CatalystFormatter, self).__init__(*args, **kwargs)
149 +
150 + def format(self, record, **kwargs):
151 + """Format the |record| with our color settings"""
152 + #print(dir(record))
153 + #print(record.getMessage())
154 + msg = super(CatalystFormatter, self).format(record, **kwargs)
155 + #print('{', msg, '}')
156 + color = self._COLORS.get(record.levelname)
157 + if color:
158 + return color + msg + self._NORMAL
159 + else:
160 + return msg
161 +
162 +
163 +def setup_logging(level, output=None, debug=False, color=None):
164 + """Initialize the logging module using the |level| level"""
165 + # The incoming level will be things like "info", but setLevel wants
166 + # the numeric constant. Convert it here.
167 + level = logging.getLevelName(level.upper())
168 +
169 + # The good stuff.
170 + fmt = '%(asctime)s: %(levelname)-8s: '
171 + if debug:
172 + fmt += '%(filename)s:%(funcName)s: '
173 + fmt += '%(message)s'
174 +
175 + # Figure out where to send the log output.
176 + if output is None:
177 + handler = logging.StreamHandler(stream=sys.stdout)
178 + else:
179 + handler = logging.FileHandler(output)
180 +
181 + # Use a date format that is readable by humans & machines.
182 + # Think e-mail/RFC 2822: 05 Oct 2013 18:58:50 EST
183 + tzname = time.strftime('%Z', time.localtime())
184 + datefmt = '%d %b %Y %H:%M:%S ' + tzname
185 + formatter = CatalystFormatter(fmt, datefmt, color=color)
186 + handler.setFormatter(formatter)
187 +
188 + logger.addHandler(handler)
189 + logger.setLevel(level)
190 diff --git a/catalyst/main.py b/catalyst/main.py
191 index e6b6447..c9a2219 100644
192 --- a/catalyst/main.py
193 +++ b/catalyst/main.py
194 @@ -18,6 +18,7 @@ from DeComp.definitions import (COMPRESS_DEFINITIONS, DECOMPRESS_DEFINITIONS,
195 CONTENTS_DEFINITIONS)
196 from DeComp.contents import ContentsMap
197
198 +from catalyst import log
199 import catalyst.config
200 import catalyst.util
201 from catalyst.defaults import confdefaults, option_messages
202 @@ -176,10 +177,23 @@ $ catalyst -f stage1-specfile.spec"""
203 group = parser.add_argument_group('Program output options')
204 group.add_argument('-d', '--debug',
205 default=False, action='store_true',
206 - help='enable debugging')
207 + help='enable debugging (and default --log-level debug)')
208 group.add_argument('-v', '--verbose',
209 default=False, action='store_true',
210 - help='verbose output')
211 + help='verbose output (and default --log-level info)')
212 + group.add_argument('--log-level',
213 + default=None,
214 + choices=('critical', 'error', 'warning', 'notice', 'info', 'debug'),
215 + help='set verbosity of output')
216 + group.add_argument('--log-file',
217 + type=FilePath(exists=False),
218 + help='write all output to this file (instead of stdout)')
219 + group.add_argument('--color',
220 + default=None, action='store_true',
221 + help='colorize output all the time (default: detect)')
222 + group.add_argument('--nocolor',
223 + dest='color', action='store_false',
224 + help='never colorize output all the time (default: detect)')
225
226 group = parser.add_argument_group('Temporary file management')
227 group.add_argument('-a', '--clear-autoresume',
228 @@ -218,6 +232,18 @@ def main():
229 parser = get_parser()
230 opts = parser.parse_args(sys.argv[1:])
231
232 + # Initialize the logger before anything else.
233 + log_level = opts.log_level
234 + if log_level is None:
235 + if opts.debug:
236 + log_level = 'debug'
237 + elif opts.verbose:
238 + log_level = 'info'
239 + else:
240 + log_level = 'notice'
241 + log.setup_logging(log_level, output=opts.log_file, debug=opts.debug,
242 + color=opts.color)
243 +
244 # Parse the command line options.
245 myconfig = opts.config
246 myspecfile = opts.file
247 --
248 2.5.2

Replies