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
29 from ganeti import constants
30 from ganeti import compat
33 class _ReopenableLogHandler(logging.handlers.BaseRotatingHandler):
34 """Log handler with ability to reopen log file on request.
36 In combination with a SIGHUP handler this class can reopen the log file on
40 def __init__(self, filename):
41 """Initializes this class.
43 @type filename: string
44 @param filename: Path to logfile
47 logging.handlers.BaseRotatingHandler.__init__(self, filename, "a")
49 assert self.encoding is None, "Encoding not supported for logging"
50 assert not hasattr(self, "_reopen"), "Base class has '_reopen' attribute"
54 def shouldRollover(self, _): # pylint: disable=C0103
55 """Determine whether log file should be reopened.
58 return self._reopen or not self.stream
60 def doRollover(self): # pylint: disable=C0103
61 """Reopens the log file.
70 # TODO: Handle errors?
71 self.stream = open(self.baseFilename, "a")
73 # Don't reopen on the next message
76 def RequestReopen(self):
77 """Register a request to reopen the file.
79 The file will be reopened before writing the next log record.
85 def _LogErrorsToConsole(base):
86 """Create wrapper class writing errors to console.
88 This needs to be in a function for unittesting.
91 class wrapped(base): # pylint: disable=C0103
92 """Log handler that doesn't fallback to stderr.
94 When an error occurs while writing on the logfile, logging.FileHandler
95 tries to log on stderr. This doesn't work in Ganeti since stderr is
96 redirected to a logfile. This class avoids failures by reporting errors to
100 def __init__(self, console, *args, **kwargs):
101 """Initializes this class.
103 @type console: file-like object or None
104 @param console: Open file-like object for console
107 base.__init__(self, *args, **kwargs)
108 assert not hasattr(self, "_console")
109 self._console = console
111 def handleError(self, record): # pylint: disable=C0103
112 """Handle errors which occur during an emit() call.
114 Try to handle errors with FileHandler method, if it fails write to
119 base.handleError(record)
120 except Exception: # pylint: disable=W0703
123 # Ignore warning about "self.format", pylint: disable=E1101
124 self._console.write("Cannot log message:\n%s\n" %
126 except Exception: # pylint: disable=W0703
127 # Log handler tried everything it could, now just give up
133 #: Custom log handler for writing to console with a reopenable handler
134 _LogHandler = _LogErrorsToConsole(_ReopenableLogHandler)
137 def _GetLogFormatter(program, multithreaded, debug, syslog):
138 """Build log formatter.
140 @param program: Program name
141 @param multithreaded: Whether to add thread name to log messages
142 @param debug: Whether to enable debug messages
143 @param syslog: Whether the formatter will be used for syslog
149 parts.append(program + "[%(process)d]:")
151 parts.append("%(asctime)s: " + program + " pid=%(process)d")
155 parts.append(" (%(threadName)s)")
157 parts.append("/%(threadName)s")
159 # Add debug info for non-syslog loggers
160 if debug and not syslog:
161 parts.append(" %(module)s:%(lineno)s")
163 # Ses, we do want the textual level, as remote syslog will probably lose the
164 # error level, and it's easier to grep for it.
165 parts.append(" %(levelname)s %(message)s")
167 return logging.Formatter("".join(parts))
170 def _ReopenLogFiles(handlers):
171 """Wrapper for reopening all log handler's files in a sequence.
174 for handler in handlers:
175 handler.RequestReopen()
176 logging.info("Received request to reopen log files")
179 def SetupLogging(logfile, program, debug=0, stderr_logging=False,
180 multithreaded=False, syslog=constants.SYSLOG_USAGE,
181 console_logging=False, root_logger=None):
182 """Configures the logging module.
185 @param logfile: the filename to which we should log
187 @param program: the name under which we should log messages
189 @param debug: if greater than zero, enable debug messages, otherwise
190 only those at C{INFO} and above level
191 @type stderr_logging: boolean
192 @param stderr_logging: whether we should also log to the standard error
193 @type multithreaded: boolean
194 @param multithreaded: if True, will add the thread name to the log file
196 @param syslog: one of 'no', 'yes', 'only':
197 - if no, syslog is not used
198 - if yes, syslog is used (in addition to file-logging)
199 - if only, only syslog is used
200 @type console_logging: boolean
201 @param console_logging: if True, will use a FileHandler which falls back to
202 the system console if logging fails
203 @type root_logger: logging.Logger
204 @param root_logger: Root logger to use (for unittests)
205 @raise EnvironmentError: if we can't open the log file and
206 syslog/stderr logging is disabled
208 @return: Function reopening all open log files when called
211 progname = os.path.basename(program)
213 formatter = _GetLogFormatter(progname, multithreaded, debug, False)
214 syslog_fmt = _GetLogFormatter(progname, multithreaded, debug, True)
218 if root_logger is None:
219 root_logger = logging.getLogger("")
220 root_logger.setLevel(logging.NOTSET)
222 # Remove all previously setup handlers
223 for handler in root_logger.handlers:
225 root_logger.removeHandler(handler)
228 stderr_handler = logging.StreamHandler()
229 stderr_handler.setFormatter(formatter)
231 stderr_handler.setLevel(logging.NOTSET)
233 stderr_handler.setLevel(logging.CRITICAL)
234 root_logger.addHandler(stderr_handler)
236 if syslog in (constants.SYSLOG_YES, constants.SYSLOG_ONLY):
237 facility = logging.handlers.SysLogHandler.LOG_DAEMON
238 syslog_handler = logging.handlers.SysLogHandler(constants.SYSLOG_SOCKET,
240 syslog_handler.setFormatter(syslog_fmt)
241 # Never enable debug over syslog
242 syslog_handler.setLevel(logging.INFO)
243 root_logger.addHandler(syslog_handler)
245 if syslog != constants.SYSLOG_ONLY:
246 # this can fail, if the logging directories are not setup or we have
247 # a permisssion problem; in this case, it's best to log but ignore
248 # the error if stderr_logging is True, and if false we re-raise the
249 # exception since otherwise we could run but without any logs at all
252 logfile_handler = _LogHandler(open(constants.DEV_CONSOLE, "a"),
255 logfile_handler = _ReopenableLogHandler(logfile)
257 logfile_handler.setFormatter(formatter)
259 logfile_handler.setLevel(logging.DEBUG)
261 logfile_handler.setLevel(logging.INFO)
262 root_logger.addHandler(logfile_handler)
263 reopen_handlers.append(logfile_handler)
264 except EnvironmentError:
265 if stderr_logging or syslog == constants.SYSLOG_YES:
266 logging.exception("Failed to enable logging to file '%s'", logfile)
268 # we need to re-raise the exception
271 return compat.partial(_ReopenLogFiles, reopen_handlers)