diff --git a/lib/utils/log.py b/lib/utils/log.py index 361bd985c73478b4a69b78514f4667c37506e625..309c28b7865a817031ee39c1ab252864289e6861 100644 --- a/lib/utils/log.py +++ b/lib/utils/log.py @@ -27,6 +27,7 @@ import logging import logging.handlers from ganeti import constants +from ganeti import compat class _ReopenableLogHandler(logging.handlers.BaseRotatingHandler): @@ -163,9 +164,17 @@ def _GetLogFormatter(program, multithreaded, debug, syslog): 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() + + 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 @@ -187,6 +196,8 @@ def SetupLogging(logfile, program, debug=0, stderr_logging=False, @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 @@ -196,7 +207,10 @@ def SetupLogging(logfile, program, debug=0, stderr_logging=False, formatter = _GetLogFormatter(progname, multithreaded, debug, False) syslog_fmt = _GetLogFormatter(progname, multithreaded, debug, True) - root_logger = logging.getLogger("") + reopen_handlers = [] + + if root_logger is None: + root_logger = logging.getLogger("") root_logger.setLevel(logging.NOTSET) # Remove all previously setup handlers @@ -245,3 +259,7 @@ def SetupLogging(logfile, program, debug=0, stderr_logging=False, else: logfile_handler.setLevel(logging.INFO) root_logger.addHandler(logfile_handler) + + reopen_handlers.append(logfile_handler) + + return compat.partial(_ReopenLogFiles, reopen_handlers) diff --git a/test/ganeti.utils.log_unittest.py b/test/ganeti.utils.log_unittest.py index cb1af432d30d59abe7669595899c1c7ce709ef2d..8594fb8a39df5c152f3dcbf421d5587264f836e6 100755 --- a/test/ganeti.utils.log_unittest.py +++ b/test/ganeti.utils.log_unittest.py @@ -25,6 +25,7 @@ import os import unittest import logging import tempfile +import shutil from ganeti import constants from ganeti import errors @@ -133,5 +134,59 @@ class TestLogHandler(unittest.TestCase): raise Exception +class TestSetupLogging(unittest.TestCase): + def setUp(self): + self.tmpdir = tempfile.mkdtemp() + + def tearDown(self): + shutil.rmtree(self.tmpdir) + + def testSimple(self): + logfile = utils.PathJoin(self.tmpdir, "basic.log") + logger = logging.Logger("TestLogger") + self.assertTrue(callable(utils.SetupLogging(logfile, "test", + console_logging=False, + syslog=constants.SYSLOG_NO, + stderr_logging=False, + multithreaded=False, + root_logger=logger))) + self.assertEqual(utils.ReadFile(logfile), "") + logger.error("This is a test") + + # Ensure SetupLogging used custom logger + logging.error("This message should not show up in the test log file") + + self.assertTrue(utils.ReadFile(logfile).endswith("This is a test\n")) + + def testReopen(self): + logfile = utils.PathJoin(self.tmpdir, "reopen.log") + logfile2 = utils.PathJoin(self.tmpdir, "reopen.log.OLD") + logger = logging.Logger("TestLogger") + reopen_fn = utils.SetupLogging(logfile, "test", + console_logging=False, + syslog=constants.SYSLOG_NO, + stderr_logging=False, + multithreaded=False, + root_logger=logger) + self.assertTrue(callable(reopen_fn)) + + self.assertEqual(utils.ReadFile(logfile), "") + logger.error("This is a test") + self.assertTrue(utils.ReadFile(logfile).endswith("This is a test\n")) + + os.rename(logfile, logfile2) + assert not os.path.exists(logfile) + + # Notify logger to reopen on the next message + reopen_fn() + assert not os.path.exists(logfile) + + # Provoke actual reopen + logger.error("First message") + + self.assertTrue(utils.ReadFile(logfile).endswith("First message\n")) + self.assertTrue(utils.ReadFile(logfile2).endswith("This is a test\n")) + + if __name__ == "__main__": testutils.GanetiTestProgram()