37032216f79ad084eb8ae9f8ef99c19aba471a25
[ganeti-github.git] / lib / utils / log.py
1 #
2 #
3
4 # Copyright (C) 2006, 2007, 2010, 2011 Google Inc.
5 # All rights reserved.
6 #
7 # Redistribution and use in source and binary forms, with or without
8 # modification, are permitted provided that the following conditions are
9 # met:
10 #
11 # 1. Redistributions of source code must retain the above copyright notice,
12 # this list of conditions and the following disclaimer.
13 #
14 # 2. Redistributions in binary form must reproduce the above copyright
15 # notice, this list of conditions and the following disclaimer in the
16 # documentation and/or other materials provided with the distribution.
17 #
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
19 # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
20 # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
22 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23 # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24 # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
25 # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26 # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27 # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30 """Utility functions for logging.
31
32 """
33
34 import os.path
35 import logging
36 import logging.handlers
37 from cStringIO import StringIO
38
39 from ganeti import constants
40 from ganeti import compat
41
42
43 class _ReopenableLogHandler(logging.handlers.BaseRotatingHandler):
44 """Log handler with ability to reopen log file on request.
45
46 In combination with a SIGHUP handler this class can reopen the log file on
47 user request.
48
49 """
50 def __init__(self, filename):
51 """Initializes this class.
52
53 @type filename: string
54 @param filename: Path to logfile
55
56 """
57 logging.handlers.BaseRotatingHandler.__init__(self, filename, "a")
58
59 assert self.encoding is None, "Encoding not supported for logging"
60 assert not hasattr(self, "_reopen"), "Base class has '_reopen' attribute"
61
62 self._reopen = False
63
64 def shouldRollover(self, _): # pylint: disable=C0103
65 """Determine whether log file should be reopened.
66
67 """
68 return self._reopen or not self.stream
69
70 def doRollover(self): # pylint: disable=C0103
71 """Reopens the log file.
72
73 """
74 if self.stream:
75 self.stream.flush()
76 self.stream.close()
77 self.stream = None
78
79 # Reopen file
80 # TODO: Handle errors?
81 self.stream = open(self.baseFilename, "a")
82
83 # Don't reopen on the next message
84 self._reopen = False
85
86 def RequestReopen(self):
87 """Register a request to reopen the file.
88
89 The file will be reopened before writing the next log record.
90
91 """
92 self._reopen = True
93
94
95 def _LogErrorsToConsole(base):
96 """Create wrapper class writing errors to console.
97
98 This needs to be in a function for unittesting.
99
100 """
101 class wrapped(base): # pylint: disable=C0103
102 """Log handler that doesn't fallback to stderr.
103
104 When an error occurs while writing on the logfile, logging.FileHandler
105 tries to log on stderr. This doesn't work in Ganeti since stderr is
106 redirected to a logfile. This class avoids failures by reporting errors to
107 /dev/console.
108
109 """
110 def __init__(self, console, *args, **kwargs):
111 """Initializes this class.
112
113 @type console: file-like object or None
114 @param console: Open file-like object for console
115
116 """
117 base.__init__(self, *args, **kwargs)
118 assert not hasattr(self, "_console")
119 self._console = console
120
121 def handleError(self, record): # pylint: disable=C0103
122 """Handle errors which occur during an emit() call.
123
124 Try to handle errors with FileHandler method, if it fails write to
125 /dev/console.
126
127 """
128 try:
129 base.handleError(record)
130 except Exception: # pylint: disable=W0703
131 if self._console:
132 try:
133 # Ignore warning about "self.format", pylint: disable=E1101
134 self._console.write("Cannot log message:\n%s\n" %
135 self.format(record))
136 except Exception: # pylint: disable=W0703
137 # Log handler tried everything it could, now just give up
138 pass
139
140 return wrapped
141
142
143 #: Custom log handler for writing to console with a reopenable handler
144 _LogHandler = _LogErrorsToConsole(_ReopenableLogHandler)
145
146
147 def _GetLogFormatter(program, multithreaded, debug, syslog):
148 """Build log formatter.
149
150 @param program: Program name
151 @param multithreaded: Whether to add thread name to log messages
152 @param debug: Whether to enable debug messages
153 @param syslog: Whether the formatter will be used for syslog
154
155 """
156 parts = []
157
158 if syslog:
159 parts.append(program + "[%(process)d]:")
160 else:
161 parts.append("%(asctime)s: " + program + " pid=%(process)d")
162
163 if multithreaded:
164 if syslog:
165 parts.append(" (%(threadName)s)")
166 else:
167 parts.append("/%(threadName)s")
168
169 # Add debug info for non-syslog loggers
170 if debug and not syslog:
171 parts.append(" %(module)s:%(lineno)s")
172
173 # Ses, we do want the textual level, as remote syslog will probably lose the
174 # error level, and it's easier to grep for it.
175 parts.append(" %(levelname)s %(message)s")
176
177 return logging.Formatter("".join(parts))
178
179
180 def _ReopenLogFiles(handlers):
181 """Wrapper for reopening all log handler's files in a sequence.
182
183 """
184 for handler in handlers:
185 handler.RequestReopen()
186 logging.info("Received request to reopen log files")
187
188
189 def SetupLogging(logfile, program, debug=0, stderr_logging=False,
190 multithreaded=False, syslog=constants.SYSLOG_USAGE,
191 console_logging=False, root_logger=None):
192 """Configures the logging module.
193
194 @type logfile: str
195 @param logfile: the filename to which we should log
196 @type program: str
197 @param program: the name under which we should log messages
198 @type debug: integer
199 @param debug: if greater than zero, enable debug messages, otherwise
200 only those at C{INFO} and above level
201 @type stderr_logging: boolean
202 @param stderr_logging: whether we should also log to the standard error
203 @type multithreaded: boolean
204 @param multithreaded: if True, will add the thread name to the log file
205 @type syslog: string
206 @param syslog: one of 'no', 'yes', 'only':
207 - if no, syslog is not used
208 - if yes, syslog is used (in addition to file-logging)
209 - if only, only syslog is used
210 @type console_logging: boolean
211 @param console_logging: if True, will use a FileHandler which falls back to
212 the system console if logging fails
213 @type root_logger: logging.Logger
214 @param root_logger: Root logger to use (for unittests)
215 @raise EnvironmentError: if we can't open the log file and
216 syslog/stderr logging is disabled
217 @rtype: callable
218 @return: Function reopening all open log files when called
219
220 """
221 progname = os.path.basename(program)
222
223 formatter = _GetLogFormatter(progname, multithreaded, debug, False)
224 syslog_fmt = _GetLogFormatter(progname, multithreaded, debug, True)
225
226 reopen_handlers = []
227
228 if root_logger is None:
229 root_logger = logging.getLogger("")
230 root_logger.setLevel(logging.NOTSET)
231
232 # Remove all previously setup handlers
233 for handler in root_logger.handlers:
234 handler.close()
235 root_logger.removeHandler(handler)
236
237 if stderr_logging:
238 stderr_handler = logging.StreamHandler()
239 stderr_handler.setFormatter(formatter)
240 if debug:
241 stderr_handler.setLevel(logging.NOTSET)
242 else:
243 stderr_handler.setLevel(logging.CRITICAL)
244 root_logger.addHandler(stderr_handler)
245
246 if syslog in (constants.SYSLOG_YES, constants.SYSLOG_ONLY):
247 facility = logging.handlers.SysLogHandler.LOG_DAEMON
248 syslog_handler = logging.handlers.SysLogHandler(constants.SYSLOG_SOCKET,
249 facility)
250 syslog_handler.setFormatter(syslog_fmt)
251 # Never enable debug over syslog
252 syslog_handler.setLevel(logging.INFO)
253 root_logger.addHandler(syslog_handler)
254
255 if syslog != constants.SYSLOG_ONLY:
256 # this can fail, if the logging directories are not setup or we have
257 # a permisssion problem; in this case, it's best to log but ignore
258 # the error if stderr_logging is True, and if false we re-raise the
259 # exception since otherwise we could run but without any logs at all
260 try:
261 if console_logging:
262 logfile_handler = _LogHandler(open(constants.DEV_CONSOLE, "a"),
263 logfile)
264 else:
265 logfile_handler = _ReopenableLogHandler(logfile)
266
267 logfile_handler.setFormatter(formatter)
268 if debug:
269 logfile_handler.setLevel(logging.DEBUG)
270 else:
271 logfile_handler.setLevel(logging.INFO)
272 root_logger.addHandler(logfile_handler)
273 reopen_handlers.append(logfile_handler)
274 except EnvironmentError:
275 if stderr_logging or syslog == constants.SYSLOG_YES:
276 logging.exception("Failed to enable logging to file '%s'", logfile)
277 else:
278 # we need to re-raise the exception
279 raise
280
281 return compat.partial(_ReopenLogFiles, reopen_handlers)
282
283
284 def SetupToolLogging(debug, verbose, threadname=False,
285 _root_logger=None, _stream=None):
286 """Configures the logging module for tools.
287
288 All log messages are sent to stderr.
289
290 @type debug: boolean
291 @param debug: Disable log message filtering
292 @type verbose: boolean
293 @param verbose: Enable verbose log messages
294 @type threadname: boolean
295 @param threadname: Whether to include thread name in output
296
297 """
298 if _root_logger is None:
299 root_logger = logging.getLogger("")
300 else:
301 root_logger = _root_logger
302
303 fmt = StringIO()
304 fmt.write("%(asctime)s:")
305
306 if threadname:
307 fmt.write(" %(threadName)s")
308
309 if debug or verbose:
310 fmt.write(" %(levelname)s")
311
312 fmt.write(" %(message)s")
313
314 formatter = logging.Formatter(fmt.getvalue())
315
316 stderr_handler = logging.StreamHandler(_stream)
317 stderr_handler.setFormatter(formatter)
318 if debug:
319 stderr_handler.setLevel(logging.NOTSET)
320 elif verbose:
321 stderr_handler.setLevel(logging.INFO)
322 else:
323 stderr_handler.setLevel(logging.WARNING)
324
325 root_logger.setLevel(logging.NOTSET)
326 root_logger.addHandler(stderr_handler)