Package ganeti :: Package utils :: Module log
[hide private]
[frames] | no frames]

Source Code for Module ganeti.utils.log

  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   
 38  from ganeti import constants 
 39  from ganeti import compat 
 40  from ganeti import pathutils 
 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 verbose=True):
193 """Configures the logging module. 194 195 @type logfile: str 196 @param logfile: the filename to which we should log 197 @type program: str 198 @param program: the name under which we should log messages 199 @type debug: integer 200 @param debug: if greater than zero, enable debug messages, otherwise 201 only those at C{INFO} and above level 202 @type stderr_logging: boolean 203 @param stderr_logging: whether we should also log to the standard error 204 @type multithreaded: boolean 205 @param multithreaded: if True, will add the thread name to the log file 206 @type syslog: string 207 @param syslog: one of 'no', 'yes', 'only': 208 - if no, syslog is not used 209 - if yes, syslog is used (in addition to file-logging) 210 - if only, only syslog is used 211 @type console_logging: boolean 212 @param console_logging: if True, will use a FileHandler which falls back to 213 the system console if logging fails 214 @type root_logger: logging.Logger 215 @param root_logger: Root logger to use (for unittests) 216 @type verbose: boolean 217 @param verbose: whether to log at 'info' level already (logfile logging only) 218 @raise EnvironmentError: if we can't open the log file and 219 syslog/stderr logging is disabled 220 @rtype: callable 221 @return: Function reopening all open log files when called 222 223 """ 224 progname = os.path.basename(program) 225 226 formatter = _GetLogFormatter(progname, multithreaded, debug, False) 227 syslog_fmt = _GetLogFormatter(progname, multithreaded, debug, True) 228 229 reopen_handlers = [] 230 231 if root_logger is None: 232 root_logger = logging.getLogger("") 233 root_logger.setLevel(logging.NOTSET) 234 235 # Remove all previously setup handlers 236 for handler in root_logger.handlers: 237 handler.close() 238 root_logger.removeHandler(handler) 239 240 if stderr_logging: 241 stderr_handler = logging.StreamHandler() 242 stderr_handler.setFormatter(formatter) 243 if debug: 244 stderr_handler.setLevel(logging.NOTSET) 245 else: 246 stderr_handler.setLevel(logging.CRITICAL) 247 root_logger.addHandler(stderr_handler) 248 249 if syslog in (constants.SYSLOG_YES, constants.SYSLOG_ONLY): 250 facility = logging.handlers.SysLogHandler.LOG_DAEMON 251 syslog_handler = logging.handlers.SysLogHandler(constants.SYSLOG_SOCKET, 252 facility) 253 syslog_handler.setFormatter(syslog_fmt) 254 # Never enable debug over syslog 255 syslog_handler.setLevel(logging.INFO) 256 root_logger.addHandler(syslog_handler) 257 258 if syslog != constants.SYSLOG_ONLY and logfile: 259 # this can fail, if the logging directories are not setup or we have 260 # a permisssion problem; in this case, it's best to log but ignore 261 # the error if stderr_logging is True, and if false we re-raise the 262 # exception since otherwise we could run but without any logs at all 263 try: 264 if console_logging: 265 logfile_handler = _LogHandler(open(constants.DEV_CONSOLE, "a"), 266 logfile) 267 else: 268 logfile_handler = _ReopenableLogHandler(logfile) 269 270 logfile_handler.setFormatter(formatter) 271 if debug: 272 logfile_handler.setLevel(logging.DEBUG) 273 elif verbose: 274 logfile_handler.setLevel(logging.INFO) 275 else: 276 logfile_handler.setLevel(logging.WARN) 277 root_logger.addHandler(logfile_handler) 278 reopen_handlers.append(logfile_handler) 279 except EnvironmentError: 280 if stderr_logging or syslog == constants.SYSLOG_YES: 281 logging.exception("Failed to enable logging to file '%s'", logfile) 282 else: 283 # we need to re-raise the exception 284 raise 285 286 return compat.partial(_ReopenLogFiles, reopen_handlers)
287 288
289 -def SetupToolLogging(debug, verbose, threadname=False, 290 toolname=None, logfile=pathutils.LOG_TOOLS):
291 """Configures the logging module for tools. 292 293 All log messages are sent to the tools.log logfile. 294 295 @type toolname: string 296 @param toolname: name of the tool that's logging 297 @type debug: boolean 298 @param debug: Disable log message filtering 299 @type verbose: boolean 300 @param verbose: Enable verbose log messages 301 @type threadname: boolean 302 @param threadname: Whether to include thread name in output 303 @type logfile: string 304 @param logfile: the path of the log file to use, use "None" 305 for tools which don't necessarily run on Ganeti nodes (and 306 thus don't have the Ganeti log directory). 307 308 """ 309 if not toolname: 310 toolname = "unspecified_tool" 311 312 # 'SetupLogging' takes a quite unintuitive 'debug' option that 313 # is '0' for 'log higher than debug level' and '1' for 314 # 'log at NOSET' level. Hence this conversion. 315 debug_int = 0 316 if debug: 317 debug_int = 1 318 319 SetupLogging(logfile, 320 program=toolname, 321 debug=debug_int, 322 multithreaded=threadname, 323 verbose=verbose)
324