Repository: tornadoweb/tornado
Pull request #1479 (2015-08-05 07:37:56)

add timed rotating mode for logging into separated files in tornado.log
Sometimes we want to log messages into files that separated by times(e.g. by days) rather than by file-size.

For that, define `rotating_mode='timed'` for tornado options

bdarnell: If you rebase or merge with the current master the travis-ci failures should be fixed.
mr-ping: Sorry for my English... I am trying to improve it.
I have changed the variable names & fixed testing code problems as your suggests.
bdarnell: It looks like your branch has gotten mixed up. Try running `git rebase -i origin/master` and then in the list that pops up, delete the lines for the changes that you didn't write. (then you'll need to use `git push -f` next time to push the branch to github).
mr-ping: The mixing problem has been fixed.
mr-ping: Raising errors instead of using NullHandler when log_rotate_mode is invalid.

Patch:
diff --git a/tornado/log.py b/tornado/log.py
index c68dec46..040889a9 100644
--- a/tornado/log.py
+++ b/tornado/log.py
@@ -190,10 +190,22 @@ def enable_pretty_logging(options=None, logger=None):
         logger = logging.getLogger()
     logger.setLevel(getattr(logging, options.logging.upper()))
     if options.log_file_prefix:
-        channel = logging.handlers.RotatingFileHandler(
-            filename=options.log_file_prefix,
-            maxBytes=options.log_file_max_size,
-            backupCount=options.log_file_num_backups)
+        rotate_mode = options.log_rotate_mode
+        if rotate_mode == 'size':
+            channel = logging.handlers.RotatingFileHandler(
+                filename=options.log_file_prefix,
+                maxBytes=options.log_file_max_size,
+                backupCount=options.log_file_num_backups)
+        elif rotate_mode == 'time':
+            channel = logging.handlers.TimedRotatingFileHandler(
+                filename=options.log_file_prefix,
+                when=options.log_rotate_when,
+                interval=options.log_rotate_interval,
+                backupCount=options.log_file_num_backups)
+        else:
+            error_message = 'The value of log_rotate_mode option should be ' +\
+                            '"size" or "time", not "%s".' % rotate_mode
+            raise ValueError(error_message)
         channel.setFormatter(LogFormatter(color=False))
         logger.addHandler(channel)
 
@@ -235,4 +247,13 @@ def define_logging_options(options=None):
     options.define("log_file_num_backups", type=int, default=10,
                    help="number of log files to keep")
 
+    options.define("log_rotate_when", type=str, default='midnight',
+                   help=("specify the type of TimedRotatingFileHandler interval "
+                         "other options:('S', 'M', 'H', 'D', 'W0'-'W6')"))
+    options.define("log_rotate_interval", type=int, default=1,
+                   help="The interval value of timed rotating")
+
+    options.define("log_rotate_mode", type=str, default='size',
+                   help="The mode of rotating files(time or size)")
+
     options.add_parse_callback(lambda: enable_pretty_logging(options))


Test patch:
diff --git a/tornado/test/log_test.py b/tornado/test/log_test.py
index 102d3d42..8bc65e55 100644
--- a/tornado/test/log_test.py
+++ b/tornado/test/log_test.py
@@ -24,6 +24,7 @@ import subprocess
 import sys
 import tempfile
 import warnings
+import time
 
 from tornado.escape import utf8
 from tornado.log import LogFormatter, define_logging_options, enable_pretty_logging
@@ -160,6 +161,39 @@ class EnablePrettyLoggingTest(unittest.TestCase):
                 os.unlink(filename)
             os.rmdir(tmpdir)
 
+    def test_log_file_with_timed_rotating(self):
+        tmpdir = tempfile.mkdtemp()
+        try:
+            self.options.log_file_prefix = tmpdir + '/test_log'
+            self.options.log_rotate_mode = 'time'
+            enable_pretty_logging(options=self.options, logger=self.logger)
+            self.logger.error('hello')
+            self.logger.handlers[0].flush()
+            filenames = glob.glob(tmpdir + '/test_log*')
+            self.assertEqual(1, len(filenames))
+            with open(filenames[0]) as f:
+                self.assertRegexpMatches(
+                    f.read(),
+                    r'^\[E [^]]*\] hello$')
+        finally:
+            for handler in self.logger.handlers:
+                handler.flush()
+                handler.close()
+            for filename in glob.glob(tmpdir + '/test_log*'):
+                os.unlink(filename)
+            os.rmdir(tmpdir)
+
+    def test_wrong_rotate_mode_value(self):
+        try:
+            self.options.log_file_prefix = 'some_path'
+            self.options.log_rotate_mode = 'wrong_mode'
+            self.assertRaises(ValueError, enable_pretty_logging,
+                              options=self.options, logger=self.logger)
+        finally:
+            for handler in self.logger.handlers:
+                handler.flush()
+                handler.close()
+
 
 class LoggingOptionTest(unittest.TestCase):
     """Test the ability to enable and disable Tornado's logging hooks."""