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