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  # 
  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. 
 10  # 
 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. 
 15  # 
 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 
 19  # 02110-1301, USA. 
 20   
 21  """Utility functions for logging. 
 22   
 23  """ 
 24   
 25  import os.path 
 26  import logging 
 27  import logging.handlers 
 28  from cStringIO import StringIO 
 29   
 30  from ganeti import constants 
 31  from ganeti import compat 
 32   
 33   
34 -class _ReopenableLogHandler(logging.handlers.BaseRotatingHandler):
35 """Log handler with ability to reopen log file on request. 36 37 In combination with a SIGHUP handler this class can reopen the log file on 38 user request. 39 40 """
41 - def __init__(self, filename):
42 """Initializes this class. 43 44 @type filename: string 45 @param filename: Path to logfile 46 47 """ 48 logging.handlers.BaseRotatingHandler.__init__(self, filename, "a") 49 50 assert self.encoding is None, "Encoding not supported for logging" 51 assert not hasattr(self, "_reopen"), "Base class has '_reopen' attribute" 52 53 self._reopen = False
54
55 - def shouldRollover(self, _): # pylint: disable=C0103
56 """Determine whether log file should be reopened. 57 58 """ 59 return self._reopen or not self.stream
60
61 - def doRollover(self): # pylint: disable=C0103
62 """Reopens the log file. 63 64 """ 65 if self.stream: 66 self.stream.flush() 67 self.stream.close() 68 self.stream = None 69 70 # Reopen file 71 # TODO: Handle errors? 72 self.stream = open(self.baseFilename, "a") 73 74 # Don't reopen on the next message 75 self._reopen = False 76
77 - def RequestReopen(self):
78 """Register a request to reopen the file. 79 80 The file will be reopened before writing the next log record. 81 82 """ 83 self._reopen = True
84 85
86 -def _LogErrorsToConsole(base):
87 """Create wrapper class writing errors to console. 88 89 This needs to be in a function for unittesting. 90 91 """ 92 class wrapped(base): # pylint: disable=C0103 93 """Log handler that doesn't fallback to stderr. 94 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 98 /dev/console. 99 100 """ 101 def __init__(self, console, *args, **kwargs): 102 """Initializes this class. 103 104 @type console: file-like object or None 105 @param console: Open file-like object for console 106 107 """ 108 base.__init__(self, *args, **kwargs) 109 assert not hasattr(self, "_console") 110 self._console = console
111 112 def handleError(self, record): # pylint: disable=C0103 113 """Handle errors which occur during an emit() call. 114 115 Try to handle errors with FileHandler method, if it fails write to 116 /dev/console. 117 118 """ 119 try: 120 base.handleError(record) 121 except Exception: # pylint: disable=W0703 122 if self._console: 123 try: 124 # Ignore warning about "self.format", pylint: disable=E1101 125 self._console.write("Cannot log message:\n%s\n" % 126 self.format(record)) 127 except Exception: # pylint: disable=W0703 128 # Log handler tried everything it could, now just give up 129 pass 130 131 return wrapped 132 133 134 #: Custom log handler for writing to console with a reopenable handler 135 _LogHandler = _LogErrorsToConsole(_ReopenableLogHandler) 136 137
138 -def _GetLogFormatter(program, multithreaded, debug, syslog):
139 """Build log formatter. 140 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 145 146 """ 147 parts = [] 148 149 if syslog: 150 parts.append(program + "[%(process)d]:") 151 else: 152 parts.append("%(asctime)s: " + program + " pid=%(process)d") 153 154 if multithreaded: 155 if syslog: 156 parts.append(" (%(threadName)s)") 157 else: 158 parts.append("/%(threadName)s") 159 160 # Add debug info for non-syslog loggers 161 if debug and not syslog: 162 parts.append(" %(module)s:%(lineno)s") 163 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") 167 168 return logging.Formatter("".join(parts))
169 170
171 -def _ReopenLogFiles(handlers):
172 """Wrapper for reopening all log handler's files in a sequence. 173 174 """ 175 for handler in handlers: 176 handler.RequestReopen() 177 logging.info("Received request to reopen log files")
178 179
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. 184 185 @type logfile: str 186 @param logfile: the filename to which we should log 187 @type program: str 188 @param program: the name under which we should log messages 189 @type debug: integer 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 196 @type syslog: string 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 208 @rtype: callable 209 @return: Function reopening all open log files when called 210 211 """ 212 progname = os.path.basename(program) 213 214 formatter = _GetLogFormatter(progname, multithreaded, debug, False) 215 syslog_fmt = _GetLogFormatter(progname, multithreaded, debug, True) 216 217 reopen_handlers = [] 218 219 if root_logger is None: 220 root_logger = logging.getLogger("") 221 root_logger.setLevel(logging.NOTSET) 222 223 # Remove all previously setup handlers 224 for handler in root_logger.handlers: 225 handler.close() 226 root_logger.removeHandler(handler) 227 228 if stderr_logging: 229 stderr_handler = logging.StreamHandler() 230 stderr_handler.setFormatter(formatter) 231 if debug: 232 stderr_handler.setLevel(logging.NOTSET) 233 else: 234 stderr_handler.setLevel(logging.CRITICAL) 235 root_logger.addHandler(stderr_handler) 236 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, 240 facility) 241 syslog_handler.setFormatter(syslog_fmt) 242 # Never enable debug over syslog 243 syslog_handler.setLevel(logging.INFO) 244 root_logger.addHandler(syslog_handler) 245 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 251 try: 252 if console_logging: 253 logfile_handler = _LogHandler(open(constants.DEV_CONSOLE, "a"), 254 logfile) 255 else: 256 logfile_handler = _ReopenableLogHandler(logfile) 257 258 logfile_handler.setFormatter(formatter) 259 if debug: 260 logfile_handler.setLevel(logging.DEBUG) 261 else: 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) 268 else: 269 # we need to re-raise the exception 270 raise 271 272 return compat.partial(_ReopenLogFiles, reopen_handlers)
273 274
275 -def SetupToolLogging(debug, verbose, threadname=False, 276 _root_logger=None, _stream=None):
277 """Configures the logging module for tools. 278 279 All log messages are sent to stderr. 280 281 @type debug: boolean 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 287 288 """ 289 if _root_logger is None: 290 root_logger = logging.getLogger("") 291 else: 292 root_logger = _root_logger 293 294 fmt = StringIO() 295 fmt.write("%(asctime)s:") 296 297 if threadname: 298 fmt.write(" %(threadName)s") 299 300 if debug or verbose: 301 fmt.write(" %(levelname)s") 302 303 fmt.write(" %(message)s") 304 305 formatter = logging.Formatter(fmt.getvalue()) 306 307 stderr_handler = logging.StreamHandler(_stream) 308 stderr_handler.setFormatter(formatter) 309 if debug: 310 stderr_handler.setLevel(logging.NOTSET) 311 elif verbose: 312 stderr_handler.setLevel(logging.INFO) 313 else: 314 stderr_handler.setLevel(logging.WARNING) 315 316 root_logger.setLevel(logging.NOTSET) 317 root_logger.addHandler(stderr_handler)
318