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