4 # Copyright (C) 2006, 2007, 2010, 2011 Google Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 """Utility functions for logging.
27 import logging.handlers
28 from cStringIO import StringIO
30 from ganeti import constants
31 from ganeti import compat
34 class _ReopenableLogHandler(logging.handlers.BaseRotatingHandler):
35 """Log handler with ability to reopen log file on request.
37 In combination with a SIGHUP handler this class can reopen the log file on
41 def __init__(self, filename):
42 """Initializes this class.
44 @type filename: string
45 @param filename: Path to logfile
48 logging.handlers.BaseRotatingHandler.__init__(self, filename, "a")
50 assert self.encoding is None, "Encoding not supported for logging"
51 assert not hasattr(self, "_reopen"), "Base class has '_reopen' attribute"
55 def shouldRollover(self, _): # pylint: disable=C0103
56 """Determine whether log file should be reopened.
59 return self._reopen or not self.stream
61 def doRollover(self): # pylint: disable=C0103
62 """Reopens the log file.
71 # TODO: Handle errors?
72 self.stream = open(self.baseFilename, "a")
74 # Don't reopen on the next message
77 def RequestReopen(self):
78 """Register a request to reopen the file.
80 The file will be reopened before writing the next log record.
86 def _LogErrorsToConsole(base):
87 """Create wrapper class writing errors to console.
89 This needs to be in a function for unittesting.
92 class wrapped(base): # pylint: disable=C0103
93 """Log handler that doesn't fallback to stderr.
95 When an error occurs while writing on the logfile, logging.FileHandler
96 tries to log on stderr. This doesn't work in Ganeti since stderr is
97 redirected to a logfile. This class avoids failures by reporting errors to
101 def __init__(self, console, *args, **kwargs):
102 """Initializes this class.
104 @type console: file-like object or None
105 @param console: Open file-like object for console
108 base.__init__(self, *args, **kwargs)
109 assert not hasattr(self, "_console")
110 self._console = console
112 def handleError(self, record): # pylint: disable=C0103
113 """Handle errors which occur during an emit() call.
115 Try to handle errors with FileHandler method, if it fails write to
120 base.handleError(record)
121 except Exception: # pylint: disable=W0703
124 # Ignore warning about "self.format", pylint: disable=E1101
125 self._console.write("Cannot log message:\n%s\n" %
127 except Exception: # pylint: disable=W0703
128 # Log handler tried everything it could, now just give up
134 #: Custom log handler for writing to console with a reopenable handler
135 _LogHandler = _LogErrorsToConsole(_ReopenableLogHandler)
138 def _GetLogFormatter(program, multithreaded, debug, syslog):
139 """Build log formatter.
141 @param program: Program name
142 @param multithreaded: Whether to add thread name to log messages
143 @param debug: Whether to enable debug messages
144 @param syslog: Whether the formatter will be used for syslog
150 parts.append(program + "[%(process)d]:")
152 parts.append("%(asctime)s: " + program + " pid=%(process)d")
156 parts.append(" (%(threadName)s)")
158 parts.append("/%(threadName)s")
160 # Add debug info for non-syslog loggers
161 if debug and not syslog:
162 parts.append(" %(module)s:%(lineno)s")
164 # Ses, we do want the textual level, as remote syslog will probably lose the
165 # error level, and it's easier to grep for it.
166 parts.append(" %(levelname)s %(message)s")
168 return logging.Formatter("".join(parts))
171 def _ReopenLogFiles(handlers):
172 """Wrapper for reopening all log handler's files in a sequence.
175 for handler in handlers:
176 handler.RequestReopen()
177 logging.info("Received request to reopen log files")
180 def SetupLogging(logfile, program, debug=0, stderr_logging=False,
181 multithreaded=False, syslog=constants.SYSLOG_USAGE,
182 console_logging=False, root_logger=None):
183 """Configures the logging module.
186 @param logfile: the filename to which we should log
188 @param program: the name under which we should log messages
190 @param debug: if greater than zero, enable debug messages, otherwise
191 only those at C{INFO} and above level
192 @type stderr_logging: boolean
193 @param stderr_logging: whether we should also log to the standard error
194 @type multithreaded: boolean
195 @param multithreaded: if True, will add the thread name to the log file
197 @param syslog: one of 'no', 'yes', 'only':
198 - if no, syslog is not used
199 - if yes, syslog is used (in addition to file-logging)
200 - if only, only syslog is used
201 @type console_logging: boolean
202 @param console_logging: if True, will use a FileHandler which falls back to
203 the system console if logging fails
204 @type root_logger: logging.Logger
205 @param root_logger: Root logger to use (for unittests)
206 @raise EnvironmentError: if we can't open the log file and
207 syslog/stderr logging is disabled
209 @return: Function reopening all open log files when called
212 progname = os.path.basename(program)
214 formatter = _GetLogFormatter(progname, multithreaded, debug, False)
215 syslog_fmt = _GetLogFormatter(progname, multithreaded, debug, True)
219 if root_logger is None:
220 root_logger = logging.getLogger("")
221 root_logger.setLevel(logging.NOTSET)
223 # Remove all previously setup handlers
224 for handler in root_logger.handlers:
226 root_logger.removeHandler(handler)
229 stderr_handler = logging.StreamHandler()
230 stderr_handler.setFormatter(formatter)
232 stderr_handler.setLevel(logging.NOTSET)
234 stderr_handler.setLevel(logging.CRITICAL)
235 root_logger.addHandler(stderr_handler)
237 if syslog in (constants.SYSLOG_YES, constants.SYSLOG_ONLY):
238 facility = logging.handlers.SysLogHandler.LOG_DAEMON
239 syslog_handler = logging.handlers.SysLogHandler(constants.SYSLOG_SOCKET,
241 syslog_handler.setFormatter(syslog_fmt)
242 # Never enable debug over syslog
243 syslog_handler.setLevel(logging.INFO)
244 root_logger.addHandler(syslog_handler)
246 if syslog != constants.SYSLOG_ONLY:
247 # this can fail, if the logging directories are not setup or we have
248 # a permisssion problem; in this case, it's best to log but ignore
249 # the error if stderr_logging is True, and if false we re-raise the
250 # exception since otherwise we could run but without any logs at all
253 logfile_handler = _LogHandler(open(constants.DEV_CONSOLE, "a"),
256 logfile_handler = _ReopenableLogHandler(logfile)
258 logfile_handler.setFormatter(formatter)
260 logfile_handler.setLevel(logging.DEBUG)
262 logfile_handler.setLevel(logging.INFO)
263 root_logger.addHandler(logfile_handler)
264 reopen_handlers.append(logfile_handler)
265 except EnvironmentError:
266 if stderr_logging or syslog == constants.SYSLOG_YES:
267 logging.exception("Failed to enable logging to file '%s'", logfile)
269 # we need to re-raise the exception
272 return compat.partial(_ReopenLogFiles, reopen_handlers)
275 def SetupToolLogging(debug, verbose, threadname=False,
276 _root_logger=None, _stream=None):
277 """Configures the logging module for tools.
279 All log messages are sent to stderr.
282 @param debug: Disable log message filtering
283 @type verbose: boolean
284 @param verbose: Enable verbose log messages
285 @type threadname: boolean
286 @param threadname: Whether to include thread name in output
289 if _root_logger is None:
290 root_logger = logging.getLogger("")
292 root_logger = _root_logger
295 fmt.write("%(asctime)s:")
298 fmt.write(" %(threadName)s")
301 fmt.write(" %(levelname)s")
303 fmt.write(" %(message)s")
305 formatter = logging.Formatter(fmt.getvalue())
307 stderr_handler = logging.StreamHandler(_stream)
308 stderr_handler.setFormatter(formatter)
310 stderr_handler.setLevel(logging.NOTSET)
312 stderr_handler.setLevel(logging.INFO)
314 stderr_handler.setLevel(logging.WARNING)
316 root_logger.setLevel(logging.NOTSET)
317 root_logger.addHandler(stderr_handler)