13515.2.1
by Jeroen Vermeulen
Allow scripts' logger initialization to be skipped. |
1 |
# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
|
8687.15.18
by Karl Fogel
Add the copyright header block to files under lib/canonical/. |
2 |
# GNU Affero General Public License version 3 (see the file LICENSE).
|
3 |
||
4983.1.3
by Curtis Hovey
Added pylint exceptions to script classes. |
4 |
# pylint: disable-msg=W0702
|
5 |
||
2449
by Canonical.com Patch Queue Manager
[r=spiv] script librarian logging |
6 |
"""Logging setup for scripts.
|
7 |
||
14565.2.15
by Curtis Hovey
Moved canonical.launchpad.scripts __init__ to lp.services.scripts. |
8 |
Don't import from this module. Import it from lp.services.scripts.
|
2449
by Canonical.com Patch Queue Manager
[r=spiv] script librarian logging |
9 |
"""
|
10 |
||
11 |
__metaclass__ = type |
|
12 |
||
13 |
# Don't import stuff from this module. Import it from canonical.scripts
|
|
7483.2.55
by Jonathan Lange
Add backport of WatchedFileHandler, exporting it from scripts and |
14 |
__all__ = [ |
12336.2.2
by Gavin Panella
New function traceback_info(). |
15 |
'DEBUG2', |
16 |
'DEBUG3', |
|
17 |
'DEBUG4', |
|
18 |
'DEBUG5', |
|
19 |
'DEBUG6', |
|
20 |
'DEBUG7', |
|
21 |
'DEBUG8', |
|
22 |
'DEBUG9', |
|
13515.2.1
by Jeroen Vermeulen
Allow scripts' logger initialization to be skipped. |
23 |
'dummy_logger_options', |
12336.2.2
by Gavin Panella
New function traceback_info(). |
24 |
'LaunchpadFormatter', |
7483.2.55
by Jonathan Lange
Add backport of WatchedFileHandler, exporting it from scripts and |
25 |
'log', |
26 |
'logger', |
|
27 |
'logger_options', |
|
11300.2.26
by Stuart Bishop
Test unhandled exception handling by LaunchpadScript |
28 |
'OopsHandler', |
7483.2.55
by Jonathan Lange
Add backport of WatchedFileHandler, exporting it from scripts and |
29 |
]
|
2449
by Canonical.com Patch Queue Manager
[r=spiv] script librarian logging |
30 |
|
10293.1.1
by Barry Warsaw, Max Bowsher
Switch from using sha and md5 to hashlib. Also use hashlib.sha256 instead of |
31 |
|
12394.1.3
by Gavin Panella
Context manager to temporarily override the script logger. |
32 |
from contextlib import contextmanager |
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
33 |
from cStringIO import StringIO |
14550.1.1
by Steve Kowalik
Run format-imports over lib/lp and lib/canonical/launchpad |
34 |
from datetime import timedelta |
10293.1.1
by Barry Warsaw, Max Bowsher
Switch from using sha and md5 to hashlib. Also use hashlib.sha256 instead of |
35 |
import hashlib |
2449
by Canonical.com Patch Queue Manager
[r=spiv] script librarian logging |
36 |
import logging |
11300.2.38
by Stuart Bishop
Alter --log-file for scripts, allowing it to specify an aditional log destination with a log level seperate to the console output |
37 |
from logging.handlers import WatchedFileHandler |
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
38 |
from optparse import OptionParser |
11300.2.38
by Stuart Bishop
Alter --log-file for scripts, allowing it to specify an aditional log destination with a log level seperate to the console output |
39 |
import os.path |
2449
by Canonical.com Patch Queue Manager
[r=spiv] script librarian logging |
40 |
import re |
41 |
import sys |
|
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
42 |
import time |
12336.2.1
by Gavin Panella
Change LaunchpadFormatter to inherit from zope.exceptions.log.Formatter, so that __traceback_info__ is included in tracebacks. |
43 |
from traceback import format_exception_only |
10293.1.1
by Barry Warsaw, Max Bowsher
Switch from using sha and md5 to hashlib. Also use hashlib.sha256 instead of |
44 |
|
2449
by Canonical.com Patch Queue Manager
[r=spiv] script librarian logging |
45 |
from zope.component import getUtility |
12336.2.1
by Gavin Panella
Change LaunchpadFormatter to inherit from zope.exceptions.log.Formatter, so that __traceback_info__ is included in tracebacks. |
46 |
from zope.exceptions.log import Formatter |
2449
by Canonical.com Patch Queue Manager
[r=spiv] script librarian logging |
47 |
|
14605.1.1
by Curtis Hovey
Moved canonical.config to lp.services. |
48 |
from lp.services.config import config |
14606.2.3
by William Grant
canonical.librarian.interfaces -> lp.services.librarian.interfaces.client |
49 |
from lp.services.librarian.interfaces.client import ( |
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
50 |
ILibrarianClient, |
51 |
UploadFailed, |
|
52 |
)
|
|
11300.2.49
by Stuart Bishop
Tidy and improve logging system infrastructure for great justice. |
53 |
from lp.services.log import loglevels |
13333.5.3
by Jonathan Lange
Use the new utc_now() |
54 |
from lp.services.utils import ( |
55 |
compress_hash, |
|
56 |
utc_now, |
|
57 |
)
|
|
14606.2.8
by William Grant
format-imports |
58 |
from lp.services.webapp.errorlog import ( |
59 |
globalErrorUtility, |
|
60 |
ScriptRequest, |
|
61 |
)
|
|
11300.2.49
by Stuart Bishop
Tidy and improve logging system infrastructure for great justice. |
62 |
|
63 |
# Reexport our custom loglevels for old callsites. These callsites
|
|
64 |
# should be importing the symbols from lp.services.log.loglevels
|
|
65 |
DEBUG2 = loglevels.DEBUG2 |
|
66 |
DEBUG3 = loglevels.DEBUG3 |
|
67 |
DEBUG4 = loglevels.DEBUG4 |
|
68 |
DEBUG5 = loglevels.DEBUG5 |
|
69 |
DEBUG6 = loglevels.DEBUG6 |
|
70 |
DEBUG7 = loglevels.DEBUG7 |
|
71 |
DEBUG8 = loglevels.DEBUG8 |
|
72 |
DEBUG9 = loglevels.DEBUG9 |
|
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
73 |
|
4476.6.17
by Danilo Šegan
Add corner-case test, factor out FakeLogger into scripts.logger. |
74 |
|
11300.2.24
by Stuart Bishop
Make cronscripts emit OOPS reports when WARN or higher messages are logged |
75 |
class OopsHandler(logging.Handler): |
76 |
"""Handler to log to the OOPS system."""
|
|
77 |
||
11300.2.35
by Stuart Bishop
Better parameter name |
78 |
def __init__(self, script_name, level=logging.WARN): |
11300.2.25
by Stuart Bishop
Context to cronscript OOPS reports |
79 |
logging.Handler.__init__(self, level) |
80 |
# Context for OOPS reports.
|
|
81 |
self.request = ScriptRequest( |
|
11300.2.35
by Stuart Bishop
Better parameter name |
82 |
[('script_name', script_name), ('path', sys.argv[0])]) |
11300.2.25
by Stuart Bishop
Context to cronscript OOPS reports |
83 |
self.setFormatter(LaunchpadFormatter()) |
84 |
||
11300.2.24
by Stuart Bishop
Make cronscripts emit OOPS reports when WARN or higher messages are logged |
85 |
def emit(self, record): |
86 |
"""Emit a record as an OOPS."""
|
|
87 |
try: |
|
14104.6.42
by Robert Collins
Fix some test failures. |
88 |
info = record.exc_info |
14104.6.45
by Robert Collins
Fix thinko in OopsHandler. |
89 |
if info is None: |
14104.6.42
by Robert Collins
Fix some test failures. |
90 |
info = sys.exc_info() |
11300.2.24
by Stuart Bishop
Make cronscripts emit OOPS reports when WARN or higher messages are logged |
91 |
msg = record.getMessage() |
92 |
with globalErrorUtility.oopsMessage(msg): |
|
14104.6.42
by Robert Collins
Fix some test failures. |
93 |
globalErrorUtility.raising(info, self.request) |
12070.1.4
by Tim Penhey
Move FakeLogger and BufferLogger to lp.services.log.logging and delete the QuietFakeLogger. |
94 |
except Exception: |
11300.2.24
by Stuart Bishop
Make cronscripts emit OOPS reports when WARN or higher messages are logged |
95 |
self.handleError(record) |
96 |
||
97 |
||
12336.2.1
by Gavin Panella
Change LaunchpadFormatter to inherit from zope.exceptions.log.Formatter, so that __traceback_info__ is included in tracebacks. |
98 |
class LaunchpadFormatter(Formatter): |
11300.2.24
by Stuart Bishop
Make cronscripts emit OOPS reports when WARN or higher messages are logged |
99 |
"""logging.Formatter encoding our preferred output format."""
|
100 |
||
11300.2.47
by Stuart Bishop
Nicer |
101 |
def __init__(self, fmt=None, datefmt=None): |
102 |
if fmt is None: |
|
11728.3.6
by Robert Collins
Convert another hard coded 'if testrunner' check, and factor out the duplication. |
103 |
if config.isTestRunner(): |
11300.2.47
by Stuart Bishop
Nicer |
104 |
# Don't output timestamps in the test environment
|
105 |
fmt = '%(levelname)-7s %(message)s' |
|
106 |
else: |
|
107 |
fmt = '%(asctime)s %(levelname)-7s %(message)s' |
|
108 |
if datefmt is None: |
|
109 |
datefmt = "%Y-%m-%d %H:%M:%S" |
|
11300.2.24
by Stuart Bishop
Make cronscripts emit OOPS reports when WARN or higher messages are logged |
110 |
logging.Formatter.__init__(self, fmt, datefmt) |
13515.2.1
by Jeroen Vermeulen
Allow scripts' logger initialization to be skipped. |
111 |
# Output should be UTC.
|
112 |
self.converter = time.gmtime |
|
11300.2.24
by Stuart Bishop
Make cronscripts emit OOPS reports when WARN or higher messages are logged |
113 |
|
114 |
||
115 |
class LibrarianFormatter(LaunchpadFormatter): |
|
2449
by Canonical.com Patch Queue Manager
[r=spiv] script librarian logging |
116 |
"""A logging.Formatter that stores tracebacks in the Librarian and emits
|
117 |
a URL rather than emitting the traceback directly.
|
|
118 |
||
119 |
The traceback will be emitted as a fallback if the Librarian cannot be
|
|
120 |
contacted.
|
|
11300.2.24
by Stuart Bishop
Make cronscripts emit OOPS reports when WARN or higher messages are logged |
121 |
|
122 |
XXX bug=641103 StuartBishop -- This class should die. Remove it and
|
|
123 |
replace with LaunchpadFormatter, fixing the test fallout.
|
|
2449
by Canonical.com Patch Queue Manager
[r=spiv] script librarian logging |
124 |
"""
|
11300.2.21
by Stuart Bishop
Delint |
125 |
|
2449
by Canonical.com Patch Queue Manager
[r=spiv] script librarian logging |
126 |
def formatException(self, ei): |
127 |
"""Format the exception and store it in the Librian.
|
|
4785.3.7
by Jeroen Vermeulen
Removed whitespace at ends of lines |
128 |
|
2449
by Canonical.com Patch Queue Manager
[r=spiv] script librarian logging |
129 |
Returns the URL, or the formatted exception if the Librarian is
|
130 |
not available.
|
|
131 |
"""
|
|
12336.2.1
by Gavin Panella
Change LaunchpadFormatter to inherit from zope.exceptions.log.Formatter, so that __traceback_info__ is included in tracebacks. |
132 |
traceback = LaunchpadFormatter.formatException(self, ei) |
3691.267.31
by Stuart Bishop
Fix more tests |
133 |
# Uncomment this line to stop exception storage in the librarian.
|
134 |
# Useful for debugging tests.
|
|
135 |
# return traceback
|
|
2449
by Canonical.com Patch Queue Manager
[r=spiv] script librarian logging |
136 |
try: |
137 |
librarian = getUtility(ILibrarianClient) |
|
138 |
except LookupError: |
|
139 |
return traceback |
|
1681.1.143
by Stuart Bishop
Improve script exception output and fix noisy log test |
140 |
|
2684
by Canonical.com Patch Queue Manager
[trivial] Make logging to the librarian more informative when dealing with minimal or broken exceptions |
141 |
exception_string = '' |
1681.1.143
by Stuart Bishop
Improve script exception output and fix noisy log test |
142 |
try: |
143 |
exception_string = str(ei[1]).encode('ascii') |
|
144 |
except: |
|
2684
by Canonical.com Patch Queue Manager
[trivial] Make logging to the librarian more informative when dealing with minimal or broken exceptions |
145 |
pass
|
146 |
if not exception_string: |
|
9678.15.6
by Guilherme Salgado
Use the exception's class __name__ instead of extracting it from its __str__ |
147 |
exception_string = ei[0].__name__ |
4785.3.7
by Jeroen Vermeulen
Removed whitespace at ends of lines |
148 |
|
13333.5.3
by Jonathan Lange
Use the new utc_now() |
149 |
expiry = utc_now() + timedelta(days=90) |
2449
by Canonical.com Patch Queue Manager
[r=spiv] script librarian logging |
150 |
try: |
12398.2.6
by Jonathan Lange
Many of the uses of 'base' were actually for compressing hashes. Use a function |
151 |
filename = compress_hash(hashlib.sha1(traceback)) + '.txt' |
2449
by Canonical.com Patch Queue Manager
[r=spiv] script librarian logging |
152 |
url = librarian.remoteAddFile( |
153 |
filename, len(traceback), StringIO(traceback), |
|
2816.1.17
by Stuart Bishop
Make librarian exception logger set file expiry date to 3 months in the future |
154 |
'text/plain;charset=%s' % sys.getdefaultencoding(), |
11300.2.21
by Stuart Bishop
Delint |
155 |
expires=expiry) |
1681.1.143
by Stuart Bishop
Improve script exception output and fix noisy log test |
156 |
return ' -> %s (%s)' % (url, exception_string) |
2449
by Canonical.com Patch Queue Manager
[r=spiv] script librarian logging |
157 |
except UploadFailed: |
158 |
return traceback |
|
11300.2.46
by Stuart Bishop
Fix test_scriptmonitor |
159 |
except Exception: |
2449
by Canonical.com Patch Queue Manager
[r=spiv] script librarian logging |
160 |
# Exceptions raised by the Formatter get swallowed, but we want
|
161 |
# to know about them. Since we are already spitting out exception
|
|
162 |
# information, we can stuff our own problems in there too.
|
|
163 |
return '%s\n\nException raised in formatter:\n%s\n' % ( |
|
164 |
traceback, |
|
12336.2.1
by Gavin Panella
Change LaunchpadFormatter to inherit from zope.exceptions.log.Formatter, so that __traceback_info__ is included in tracebacks. |
165 |
LaunchpadFormatter.formatException(self, sys.exc_info())) |
2449
by Canonical.com Patch Queue Manager
[r=spiv] script librarian logging |
166 |
|
167 |
||
13515.2.1
by Jeroen Vermeulen
Allow scripts' logger initialization to be skipped. |
168 |
class LogLevelNudger: |
169 |
"""Callable to adjust the global log level.
|
|
170 |
||
171 |
Use instances as callbacks for `optparse`.
|
|
172 |
"""
|
|
173 |
||
174 |
def __init__(self, default, increment=True): |
|
175 |
"""Initialize nudger to increment or decrement log level.
|
|
176 |
||
177 |
:param default: Default starting level.
|
|
178 |
:param increment: Whether to increase the log level (as when
|
|
179 |
handling the --verbose option). If not, will decrease
|
|
180 |
instead (as with the --quiet option).
|
|
181 |
"""
|
|
182 |
self.default = default |
|
183 |
self.increment = increment |
|
184 |
||
185 |
def getIncrement(self, current_level): |
|
186 |
"""Figure out how much to increment the log level.
|
|
187 |
||
188 |
Increment is negative when decreasing log level, of course.
|
|
189 |
"""
|
|
190 |
if self.increment: |
|
191 |
if current_level < 10: |
|
192 |
return 1 |
|
193 |
else: |
|
194 |
return 10 |
|
195 |
else: |
|
196 |
if current_level <= 10: |
|
197 |
return -1 |
|
198 |
else: |
|
199 |
return -10 |
|
200 |
||
201 |
def __call__(self, option, opt_str, value, parser): |
|
202 |
"""Callback for `optparse` to handle --verbose or --quiet option."""
|
|
203 |
current_level = getattr(parser.values, 'loglevel', self.default) |
|
204 |
increment = self.getIncrement(current_level) |
|
205 |
parser.values.loglevel = current_level + increment |
|
206 |
parser.values.verbose = (parser.values.loglevel < self.default) |
|
207 |
# Reset the global log.
|
|
208 |
log._log = _logger(parser.values.loglevel, out_stream=sys.stderr) |
|
209 |
||
210 |
||
211 |
def define_verbosity_options(parser, default, verbose_callback, |
|
212 |
quiet_callback): |
|
213 |
"""Define the -v and -q options on `parser`."""
|
|
13515.2.5
by Jeroen Vermeulen
OK so that last bit was needed after all. At least now we know. |
214 |
# Only one of these specifies dest and default. That's because
|
215 |
# that's enough to make the parser create the option value; there's
|
|
216 |
# no need for the other option to specify them as well.
|
|
13515.2.1
by Jeroen Vermeulen
Allow scripts' logger initialization to be skipped. |
217 |
parser.add_option( |
13515.2.5
by Jeroen Vermeulen
OK so that last bit was needed after all. At least now we know. |
218 |
"-v", "--verbose", dest="loglevel", default=default, |
219 |
action="callback", callback=verbose_callback, |
|
13515.2.1
by Jeroen Vermeulen
Allow scripts' logger initialization to be skipped. |
220 |
help="Increase stderr verbosity. May be specified multiple times.") |
221 |
parser.add_option( |
|
222 |
"-q", "--quiet", action="callback", callback=quiet_callback, |
|
223 |
help="Decrease stderr verbosity. May be specified multiple times.") |
|
224 |
||
225 |
||
226 |
def do_nothing(*args, **kwargs): |
|
227 |
"""Do absolutely nothing."""
|
|
228 |
||
229 |
||
230 |
def dummy_logger_options(parser): |
|
231 |
"""Add dummy --verbose and --quiet options to `parser`."""
|
|
232 |
define_verbosity_options(parser, None, do_nothing, do_nothing) |
|
233 |
||
234 |
||
2449
by Canonical.com Patch Queue Manager
[r=spiv] script librarian logging |
235 |
def logger_options(parser, default=logging.INFO): |
236 |
"""Add the --verbose and --quiet options to an optparse.OptionParser.
|
|
237 |
||
238 |
The requested loglevel will end up in the option's loglevel attribute.
|
|
3410.4.1
by Stuart Bishop
Add options.verbose flag to script verbosity setup |
239 |
Note that loglevel is not clamped to any particular range.
|
2449
by Canonical.com Patch Queue Manager
[r=spiv] script librarian logging |
240 |
|
241 |
>>> from optparse import OptionParser
|
|
242 |
>>> parser = OptionParser()
|
|
243 |
>>> logger_options(parser)
|
|
3410.4.1
by Stuart Bishop
Add options.verbose flag to script verbosity setup |
244 |
>>> options, args = parser.parse_args(['-v', '-v', '-q', '-qqqqqqq'])
|
245 |
>>> options.loglevel > logging.CRITICAL
|
|
2449
by Canonical.com Patch Queue Manager
[r=spiv] script librarian logging |
246 |
True
|
3410.4.1
by Stuart Bishop
Add options.verbose flag to script verbosity setup |
247 |
>>> options.verbose
|
248 |
False
|
|
2449
by Canonical.com Patch Queue Manager
[r=spiv] script librarian logging |
249 |
|
3410.4.1
by Stuart Bishop
Add options.verbose flag to script verbosity setup |
250 |
>>> parser = OptionParser()
|
251 |
>>> logger_options(parser)
|
|
2449
by Canonical.com Patch Queue Manager
[r=spiv] script librarian logging |
252 |
>>> options, args = parser.parse_args([])
|
253 |
>>> options.loglevel == logging.INFO
|
|
254 |
True
|
|
3410.4.1
by Stuart Bishop
Add options.verbose flag to script verbosity setup |
255 |
>>> options.verbose
|
256 |
False
|
|
257 |
||
258 |
>>> from optparse import OptionParser
|
|
259 |
>>> parser = OptionParser()
|
|
260 |
>>> logger_options(parser, logging.WARNING)
|
|
261 |
>>> options, args = parser.parse_args(['-v'])
|
|
262 |
>>> options.loglevel == logging.INFO
|
|
263 |
True
|
|
264 |
>>> options.verbose
|
|
265 |
True
|
|
2449
by Canonical.com Patch Queue Manager
[r=spiv] script librarian logging |
266 |
|
2425.1.13
by James Henstridge
reset handlers on root logger in logger doctests |
267 |
Cleanup:
|
14604.1.1
by Curtis Hovey
Separate test-authoring classes from test-running classes. |
268 |
>>> from lp.testing import reset_logging
|
3691.57.16
by Stuart Bishop
Fix logger tests |
269 |
>>> reset_logging()
|
2425.1.13
by James Henstridge
reset handlers on root logger in logger doctests |
270 |
|
2449
by Canonical.com Patch Queue Manager
[r=spiv] script librarian logging |
271 |
As part of the options parsing, the 'log' global variable is updated.
|
272 |
This can be used by code too lazy to pass it around as a variable.
|
|
273 |
"""
|
|
274 |
||
275 |
# Raise an exception if the constants have changed. If they change we
|
|
276 |
# will need to fix the arithmetic
|
|
277 |
assert logging.DEBUG == 10 |
|
278 |
assert logging.INFO == 20 |
|
279 |
assert logging.WARNING == 30 |
|
280 |
assert logging.ERROR == 40 |
|
281 |
assert logging.CRITICAL == 50 |
|
282 |
||
3410.4.1
by Stuart Bishop
Add options.verbose flag to script verbosity setup |
283 |
# Undocumented use of the optparse module
|
284 |
parser.defaults['verbose'] = False |
|
285 |
||
13515.2.1
by Jeroen Vermeulen
Allow scripts' logger initialization to be skipped. |
286 |
define_verbosity_options( |
287 |
parser, default, |
|
13515.2.3
by Jeroen Vermeulen
Got nudgers wrong way around. |
288 |
LogLevelNudger(default, False), LogLevelNudger(default, True)) |
11300.2.38
by Stuart Bishop
Alter --log-file for scripts, allowing it to specify an aditional log destination with a log level seperate to the console output |
289 |
|
290 |
debug_levels = ', '.join([ |
|
291 |
v for k, v in sorted(logging._levelNames.items(), reverse=True) |
|
292 |
if isinstance(k, int)]) |
|
293 |
||
294 |
def log_file(option, opt_str, value, parser): |
|
295 |
try: |
|
296 |
level, path = value.split(':', 1) |
|
297 |
except ValueError: |
|
11300.2.42
by Stuart Bishop
Ensure global logger is up to date and set default log-file level to INFO |
298 |
level, path = logging.INFO, value |
11300.2.38
by Stuart Bishop
Alter --log-file for scripts, allowing it to specify an aditional log destination with a log level seperate to the console output |
299 |
|
300 |
if isinstance(level, int): |
|
301 |
pass
|
|
302 |
elif level.upper() not in logging._levelNames: |
|
303 |
parser.error( |
|
304 |
"'%s' is not a valid logging level. Must be one of %s" % ( |
|
305 |
level, debug_levels)) |
|
306 |
else: |
|
307 |
level = logging._levelNames[level.upper()] |
|
308 |
||
309 |
if not path: |
|
310 |
parser.error("Path to log file not specified") |
|
311 |
||
312 |
path = os.path.abspath(path) |
|
313 |
try: |
|
314 |
open(path, 'a') |
|
315 |
except Exception: |
|
316 |
parser.error("Unable to open log file %s" % path) |
|
317 |
||
318 |
parser.values.log_file = path |
|
319 |
parser.values.log_file_level = level |
|
320 |
||
11300.2.42
by Stuart Bishop
Ensure global logger is up to date and set default log-file level to INFO |
321 |
# Reset the global log.
|
322 |
log._log = _logger(parser.values.loglevel, out_stream=sys.stderr) |
|
323 |
||
11300.2.38
by Stuart Bishop
Alter --log-file for scripts, allowing it to specify an aditional log destination with a log level seperate to the console output |
324 |
parser.add_option( |
325 |
"--log-file", type="string", action="callback", callback=log_file, |
|
11300.2.40
by Stuart Bishop
Initialize log_file_level with a default |
326 |
metavar="LVL:FILE", default=None, |
11300.2.38
by Stuart Bishop
Alter --log-file for scripts, allowing it to specify an aditional log destination with a log level seperate to the console output |
327 |
help="Send log messages to FILE. LVL is one of %s" % debug_levels) |
11300.2.40
by Stuart Bishop
Initialize log_file_level with a default |
328 |
parser.set_default('log_file_level', None) |
2449
by Canonical.com Patch Queue Manager
[r=spiv] script librarian logging |
329 |
|
330 |
# Set the global log
|
|
3147.2.63
by Celso Providelo
review comments (take 2) |
331 |
log._log = _logger(default, out_stream=sys.stderr) |
2449
by Canonical.com Patch Queue Manager
[r=spiv] script librarian logging |
332 |
|
333 |
||
334 |
def logger(options=None, name=None): |
|
335 |
"""Return a logging instance with standard setup.
|
|
336 |
||
337 |
options should be the options as returned by an option parser that
|
|
338 |
has been initilized with logger_options(parser)
|
|
339 |
||
340 |
>>> from optparse import OptionParser
|
|
341 |
>>> parser = OptionParser()
|
|
342 |
>>> logger_options(parser)
|
|
343 |
>>> options, args = parser.parse_args(['-v', '-v', '-q', '-q', '-q'])
|
|
344 |
>>> log = logger(options)
|
|
7483.1.34
by Jonathan Lange
I really don't like doctest. |
345 |
>>> log.debug('Not shown - too quiet')
|
2425.1.13
by James Henstridge
reset handlers on root logger in logger doctests |
346 |
|
347 |
Cleanup:
|
|
3691.57.28
by Stuart Bishop
Fix more tests |
348 |
|
14604.1.1
by Curtis Hovey
Separate test-authoring classes from test-running classes. |
349 |
>>> from lp.testing import reset_logging
|
3691.57.42
by Stuart Bishop
More post review feedback updates |
350 |
>>> reset_logging()
|
3691.57.28
by Stuart Bishop
Fix more tests |
351 |
"""
|
2449
by Canonical.com Patch Queue Manager
[r=spiv] script librarian logging |
352 |
if options is None: |
353 |
parser = OptionParser() |
|
354 |
logger_options(parser) |
|
355 |
options, args = parser.parse_args() |
|
356 |
||
11300.2.46
by Stuart Bishop
Fix test_scriptmonitor |
357 |
log_file = getattr(options, 'log_file', None) |
358 |
log_file_level = getattr(options, 'log_file_level', None) |
|
359 |
||
11300.2.38
by Stuart Bishop
Alter --log-file for scripts, allowing it to specify an aditional log destination with a log level seperate to the console output |
360 |
return _logger( |
361 |
options.loglevel, out_stream=sys.stderr, name=name, |
|
11300.2.46
by Stuart Bishop
Fix test_scriptmonitor |
362 |
log_file=log_file, log_file_level=log_file_level) |
5204.5.4
by Barry Warsaw
Make LaunchpadScript a new-style class, and adjust a super() call accordingly. |
363 |
|
2449
by Canonical.com Patch Queue Manager
[r=spiv] script librarian logging |
364 |
|
2425.1.13
by James Henstridge
reset handlers on root logger in logger doctests |
365 |
def reset_root_logger(): |
366 |
root_logger = logging.getLogger() |
|
367 |
for hdlr in root_logger.handlers[:]: |
|
368 |
hdlr.flush() |
|
3691.57.28
by Stuart Bishop
Fix more tests |
369 |
try: |
370 |
hdlr.close() |
|
371 |
except KeyError: |
|
372 |
pass
|
|
2425.1.13
by James Henstridge
reset handlers on root logger in logger doctests |
373 |
root_logger.removeHandler(hdlr) |
374 |
||
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
375 |
|
11300.2.38
by Stuart Bishop
Alter --log-file for scripts, allowing it to specify an aditional log destination with a log level seperate to the console output |
376 |
def _logger( |
377 |
level, out_stream, name=None, |
|
378 |
log_file=None, log_file_level=logging.DEBUG): |
|
2449
by Canonical.com Patch Queue Manager
[r=spiv] script librarian logging |
379 |
"""Create the actual logger instance, logging at the given level
|
380 |
||
381 |
if name is None, it will get args[0] without the extension (e.g. gina).
|
|
3147.2.63
by Celso Providelo
review comments (take 2) |
382 |
'out_stream must be passed, the recommended value is sys.stderr'
|
2449
by Canonical.com Patch Queue Manager
[r=spiv] script librarian logging |
383 |
"""
|
384 |
if name is None: |
|
385 |
# Determine the logger name from the script name
|
|
386 |
name = sys.argv[0] |
|
387 |
name = re.sub('.py[oc]?$', '', name) |
|
388 |
||
1681.1.126
by Stuart Bishop
Ensure specialized log handlers work for the root logger too |
389 |
# We install our custom handlers and formatters on the root logger.
|
390 |
# This means that if the root logger is used, we still get correct
|
|
391 |
# formatting. The root logger should probably not be used.
|
|
392 |
root_logger = logging.getLogger() |
|
2449
by Canonical.com Patch Queue Manager
[r=spiv] script librarian logging |
393 |
|
2425.1.13
by James Henstridge
reset handlers on root logger in logger doctests |
394 |
# reset state of root logger
|
395 |
reset_root_logger() |
|
2449
by Canonical.com Patch Queue Manager
[r=spiv] script librarian logging |
396 |
|
4785.3.7
by Jeroen Vermeulen
Removed whitespace at ends of lines |
397 |
# Make it print output in a standard format, suitable for
|
2449
by Canonical.com Patch Queue Manager
[r=spiv] script librarian logging |
398 |
# both command line tools and cron jobs (command line tools often end
|
399 |
# up being run from inside cron, so this is a good thing).
|
|
12109.2.1
by William Grant
Fix canonical.launchpad.scripts.logger to work with Python 2.7. |
400 |
hdlr = logging.StreamHandler(out_stream) |
11300.2.24
by Stuart Bishop
Make cronscripts emit OOPS reports when WARN or higher messages are logged |
401 |
# We set the level on the handler rather than the logger, so other
|
402 |
# handlers with different levels can be added for things like debug
|
|
403 |
# logs.
|
|
11300.2.27
by Stuart Bishop
Fix log level bug and test WARN and above generate OOPS reports |
404 |
root_logger.setLevel(0) |
11300.2.24
by Stuart Bishop
Make cronscripts emit OOPS reports when WARN or higher messages are logged |
405 |
hdlr.setLevel(level) |
11300.2.48
by Stuart Bishop
Reenable the LibrarianFormatter |
406 |
formatter = LibrarianFormatter() |
2449
by Canonical.com Patch Queue Manager
[r=spiv] script librarian logging |
407 |
hdlr.setFormatter(formatter) |
1681.1.126
by Stuart Bishop
Ensure specialized log handlers work for the root logger too |
408 |
root_logger.addHandler(hdlr) |
409 |
||
11300.2.38
by Stuart Bishop
Alter --log-file for scripts, allowing it to specify an aditional log destination with a log level seperate to the console output |
410 |
# Add an optional aditional log file.
|
411 |
if log_file is not None: |
|
412 |
handler = WatchedFileHandler(log_file, encoding="UTF8") |
|
413 |
handler.setFormatter(formatter) |
|
414 |
handler.setLevel(log_file_level) |
|
415 |
root_logger.addHandler(handler) |
|
416 |
||
1681.1.126
by Stuart Bishop
Ensure specialized log handlers work for the root logger too |
417 |
# Create our logger
|
418 |
logger = logging.getLogger(name) |
|
419 |
||
5204.5.4
by Barry Warsaw
Make LaunchpadScript a new-style class, and adjust a super() call accordingly. |
420 |
# Set the global log
|
2449
by Canonical.com Patch Queue Manager
[r=spiv] script librarian logging |
421 |
log._log = logger |
422 |
||
11300.2.38
by Stuart Bishop
Alter --log-file for scripts, allowing it to specify an aditional log destination with a log level seperate to the console output |
423 |
# Inform the user the extra log file is in operation.
|
424 |
if log_file is not None: |
|
425 |
log.info( |
|
426 |
"Logging %s and higher messages to %s" % ( |
|
427 |
logging.getLevelName(log_file_level), log_file)) |
|
428 |
||
2449
by Canonical.com Patch Queue Manager
[r=spiv] script librarian logging |
429 |
return logger |
430 |
||
431 |
||
432 |
class _LogWrapper: |
|
433 |
"""Changes the logger instance.
|
|
434 |
||
14565.2.15
by Curtis Hovey
Moved canonical.launchpad.scripts __init__ to lp.services.scripts. |
435 |
Other modules will do 'from lp.services.scripts import log'.
|
2449
by Canonical.com Patch Queue Manager
[r=spiv] script librarian logging |
436 |
This wrapper allows us to change the logger instance these other modules
|
437 |
use, by replacing the _log attribute. This is done each call to logger()
|
|
438 |
"""
|
|
439 |
||
440 |
def __init__(self, log): |
|
441 |
self._log = log |
|
442 |
||
443 |
def __getattr__(self, key): |
|
444 |
return getattr(self._log, key) |
|
445 |
||
446 |
def __setattr__(self, key, value): |
|
447 |
if key == '_log': |
|
448 |
self.__dict__['_log'] = value |
|
449 |
return value |
|
450 |
else: |
|
451 |
return setattr(self._log, key, value) |
|
452 |
||
12394.1.3
by Gavin Panella
Context manager to temporarily override the script logger. |
453 |
@contextmanager
|
454 |
def use(self, log): |
|
455 |
"""Temporarily use a different `log`."""
|
|
456 |
self._log, log = log, self._log |
|
457 |
try: |
|
458 |
yield
|
|
459 |
finally: |
|
460 |
self._log = log |
|
461 |
||
3109.3.3
by David Allouche
factor shortException into _LogWrapper |
462 |
def shortException(self, msg, *args): |
463 |
"""Like Logger.exception, but does not print a traceback."""
|
|
464 |
exctype, value = sys.exc_info()[:2] |
|
12336.2.1
by Gavin Panella
Change LaunchpadFormatter to inherit from zope.exceptions.log.Formatter, so that __traceback_info__ is included in tracebacks. |
465 |
report = ''.join(format_exception_only(exctype, value)) |
3109.3.3
by David Allouche
factor shortException into _LogWrapper |
466 |
# _log.error interpolates msg, so we need to escape % chars
|
467 |
msg += '\n' + report.rstrip('\n').replace('%', '%%') |
|
468 |
self._log.error(msg, *args) |
|
469 |
||
470 |
||
2449
by Canonical.com Patch Queue Manager
[r=spiv] script librarian logging |
471 |
log = _LogWrapper(logging.getLogger()) |