Merge branch 'devel-2.4'
[ganeti-github.git] / lib / utils / log.py
index 829e743..1cc6d82 100644 (file)
 
 """
 
+import os.path
 import logging
 import logging.handlers
 
 from ganeti import constants
+from ganeti import compat
 
 
 class _ReopenableLogHandler(logging.handlers.BaseRotatingHandler):
@@ -68,6 +70,9 @@ class _ReopenableLogHandler(logging.handlers.BaseRotatingHandler):
     # TODO: Handle errors?
     self.stream = open(self.baseFilename, "a")
 
+    # Don't reopen on the next message
+    self._reopen = False
+
   def RequestReopen(self):
     """Register a request to reopen the file.
 
@@ -129,20 +134,62 @@ def _LogErrorsToConsole(base):
 _LogHandler = _LogErrorsToConsole(_ReopenableLogHandler)
 
 
-def SetupLogging(logfile, debug=0, stderr_logging=False, program="",
+def _GetLogFormatter(program, multithreaded, debug, syslog):
+  """Build log formatter.
+
+  @param program: Program name
+  @param multithreaded: Whether to add thread name to log messages
+  @param debug: Whether to enable debug messages
+  @param syslog: Whether the formatter will be used for syslog
+
+  """
+  parts = []
+
+  if syslog:
+    parts.append(program + "[%(process)d]:")
+  else:
+    parts.append("%(asctime)s: " + program + " pid=%(process)d")
+
+  if multithreaded:
+    if syslog:
+      parts.append(" (%(threadName)s)")
+    else:
+      parts.append("/%(threadName)s")
+
+  # Add debug info for non-syslog loggers
+  if debug and not syslog:
+    parts.append(" %(module)s:%(lineno)s")
+
+  # Ses, we do want the textual level, as remote syslog will probably lose the
+  # error level, and it's easier to grep for it.
+  parts.append(" %(levelname)s %(message)s")
+
+  return logging.Formatter("".join(parts))
+
+
+def _ReopenLogFiles(handlers):
+  """Wrapper for reopening all log handler's files in a sequence.
+
+  """
+  for handler in handlers:
+    handler.RequestReopen()
+  logging.info("Received request to reopen log files")
+
+
+def SetupLogging(logfile, program, debug=0, stderr_logging=False,
                  multithreaded=False, syslog=constants.SYSLOG_USAGE,
-                 console_logging=False):
+                 console_logging=False, root_logger=None):
   """Configures the logging module.
 
   @type logfile: str
   @param logfile: the filename to which we should log
+  @type program: str
+  @param program: the name under which we should log messages
   @type debug: integer
   @param debug: if greater than zero, enable debug messages, otherwise
       only those at C{INFO} and above level
   @type stderr_logging: boolean
   @param stderr_logging: whether we should also log to the standard error
-  @type program: str
-  @param program: the name under which we should log messages
   @type multithreaded: boolean
   @param multithreaded: if True, will add the thread name to the log file
   @type syslog: string
@@ -153,26 +200,23 @@ def SetupLogging(logfile, debug=0, stderr_logging=False, program="",
   @type console_logging: boolean
   @param console_logging: if True, will use a FileHandler which falls back to
       the system console if logging fails
+  @type root_logger: logging.Logger
+  @param root_logger: Root logger to use (for unittests)
   @raise EnvironmentError: if we can't open the log file and
       syslog/stderr logging is disabled
+  @rtype: callable
+  @return: Function reopening all open log files when called
 
   """
-  fmt = "%(asctime)s: " + program + " pid=%(process)d"
-  sft = program + "[%(process)d]:"
-  if multithreaded:
-    fmt += "/%(threadName)s"
-    sft += " (%(threadName)s)"
-  if debug:
-    fmt += " %(module)s:%(lineno)s"
-    # no debug info for syslog loggers
-  fmt += " %(levelname)s %(message)s"
-  # yes, we do want the textual level, as remote syslog will probably
-  # lose the error level, and it's easier to grep for it
-  sft += " %(levelname)s %(message)s"
-  formatter = logging.Formatter(fmt)
-  sys_fmt = logging.Formatter(sft)
-
-  root_logger = logging.getLogger("")
+  progname = os.path.basename(program)
+
+  formatter = _GetLogFormatter(progname, multithreaded, debug, False)
+  syslog_fmt = _GetLogFormatter(progname, multithreaded, debug, True)
+
+  reopen_handlers = []
+
+  if root_logger is None:
+    root_logger = logging.getLogger("")
   root_logger.setLevel(logging.NOTSET)
 
   # Remove all previously setup handlers
@@ -193,7 +237,7 @@ def SetupLogging(logfile, debug=0, stderr_logging=False, program="",
     facility = logging.handlers.SysLogHandler.LOG_DAEMON
     syslog_handler = logging.handlers.SysLogHandler(constants.SYSLOG_SOCKET,
                                                     facility)
-    syslog_handler.setFormatter(sys_fmt)
+    syslog_handler.setFormatter(syslog_fmt)
     # Never enable debug over syslog
     syslog_handler.setLevel(logging.INFO)
     root_logger.addHandler(syslog_handler)
@@ -205,7 +249,8 @@ def SetupLogging(logfile, debug=0, stderr_logging=False, program="",
     # exception since otherwise we could run but without any logs at all
     try:
       if console_logging:
-        logfile_handler = _LogHandler(open(constants.DEV_CONSOLE, "a"), logfile)
+        logfile_handler = _LogHandler(open(constants.DEV_CONSOLE, "a"),
+                                      logfile)
       else:
         logfile_handler = _ReopenableLogHandler(logfile)
 
@@ -215,9 +260,12 @@ def SetupLogging(logfile, debug=0, stderr_logging=False, program="",
       else:
         logfile_handler.setLevel(logging.INFO)
       root_logger.addHandler(logfile_handler)
+      reopen_handlers.append(logfile_handler)
     except EnvironmentError:
       if stderr_logging or syslog == constants.SYSLOG_YES:
         logging.exception("Failed to enable logging to file '%s'", logfile)
       else:
         # we need to re-raise the exception
         raise
+
+  return compat.partial(_ReopenLogFiles, reopen_handlers)