]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Introduce parse callbacks in the options module.
authorBen Darnell <ben@bendarnell.com>
Sun, 30 Sep 2012 22:05:29 +0000 (15:05 -0700)
committerBen Darnell <ben@bendarnell.com>
Sun, 30 Sep 2012 22:13:23 +0000 (15:13 -0700)
This is a generalization of the existing magic for logging.  Logging
configuration is now moved to log.py.

tornado/log.py
tornado/options.py
tornado/test/options_test.py
website/sphinx/releases/next.rst

index 988af3ac4ed621db137e5002aa8d9fafe59d3c66..7a4a8332f29e67628c2dc3784314bc8529856bcc 100644 (file)
@@ -140,3 +140,55 @@ class LogFormatter(logging.Formatter):
         if record.exc_text:
             formatted = formatted.rstrip() + "\n" + record.exc_text
         return formatted.replace("\n", "\n    ")
+
+def enable_pretty_logging(options=None):
+    """Turns on formatted logging output as configured.
+
+    This is called automaticaly by `tornado.options.parse_command_line`
+    and `tornado.options.parse_config_file`.
+    """
+    if options is None:
+        from tornado.options import options
+    if options.logging == 'none':
+        return
+    root_logger = logging.getLogger()
+    root_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)
+        channel.setFormatter(LogFormatter(color=False))
+        root_logger.addHandler(channel)
+
+    if (options.log_to_stderr or
+        (options.log_to_stderr is None and not root_logger.handlers)):
+        # Set up color if we are in a tty and curses is installed
+        channel = logging.StreamHandler()
+        channel.setFormatter(LogFormatter())
+        root_logger.addHandler(channel)
+
+
+def define_logging_options(options=None):
+    if options is None:
+        # late import to prevent cycle
+        from tornado.options import options
+    options.define("logging", default="info",
+           help=("Set the Python log level. If 'none', tornado won't touch the "
+                 "logging configuration."),
+           metavar="debug|info|warning|error|none")
+    options.define("log_to_stderr", type=bool, default=None,
+           help=("Send log output to stderr (colorized if possible). "
+                 "By default use stderr if --log_file_prefix is not set and "
+                 "no other logging is configured."))
+    options.define("log_file_prefix", type=str, default=None, metavar="PATH",
+           help=("Path prefix for log files. "
+                 "Note that if you are running multiple tornado processes, "
+                 "log_file_prefix must be different for each of them (e.g. "
+                 "include the port number)"))
+    options.define("log_file_max_size", type=int, default=100 * 1000 * 1000,
+           help="max size of log files before rollover")
+    options.define("log_file_num_backups", type=int, default=10,
+           help="number of log files to keep")
+
+    options.add_parse_callback(enable_pretty_logging)
index 1f15050e16b61c62a25ffcaa7200a7088803fbc2..c5677c290557e88c72c64518b6d4ca7aecab3f33 100644 (file)
@@ -51,15 +51,14 @@ for define() below.
 from __future__ import absolute_import, division, with_statement
 
 import datetime
-import logging
-import logging.handlers
 import re
 import sys
 import os
 import textwrap
 
 from tornado.escape import _unicode
-from tornado.log import LogFormatter
+from tornado.log import define_logging_options
+from tornado import stack_context
 
 
 class Error(Exception):
@@ -73,6 +72,10 @@ class _Options(dict):
     Normally accessed via static functions in the `tornado.options` module,
     which reference a global instance.
     """
+    def __init__(self):
+        super(_Options, self).__init__()
+        self.__dict__['_parse_callbacks'] = []
+
     def __getattr__(self, name):
         if isinstance(self.get(name), _Option):
             return self[name].value()
@@ -106,7 +109,7 @@ class _Options(dict):
                              type=type, help=help, metavar=metavar,
                              multiple=multiple, group_name=group_name)
 
-    def parse_command_line(self, args=None):
+    def parse_command_line(self, args=None, final=True):
         if args is None:
             args = sys.argv
         remaining = []
@@ -135,20 +138,21 @@ class _Options(dict):
             print_help()
             sys.exit(0)
 
-        # Set up log level and pretty console logging by default
-        if self.logging != 'none':
-            logging.getLogger().setLevel(getattr(logging, self.logging.upper()))
-            enable_pretty_logging()
+        if final:
+            self.run_parse_callbacks()
 
         return remaining
 
-    def parse_config_file(self, path):
+    def parse_config_file(self, path, final=True):
         config = {}
         execfile(path, config, config)
         for name in config:
             if name in self:
                 self[name].set(config[name])
 
+        if final:
+            self.run_parse_callbacks()
+
     def print_help(self, file=sys.stdout):
         """Prints all the command line options to stdout."""
         print >> file, "Usage: %s [OPTIONS]" % sys.argv[0]
@@ -176,6 +180,13 @@ class _Options(dict):
                     print >> file, "%-34s %s" % (' ', line)
         print >> file
 
+    def add_parse_callback(self, callback):
+        self._parse_callbacks.append(stack_context.wrap(callback))
+
+    def run_parse_callbacks(self):
+        for callback in self._parse_callbacks:
+            callback()
+
 
 class _Option(object):
     def __init__(self, name, default=None, type=basestring, help=None, metavar=None,
@@ -332,65 +343,39 @@ def define(name, default=None, type=None, help=None, metavar=None,
                           metavar=metavar, multiple=multiple, group=group)
 
 
-def parse_command_line(args=None):
+def parse_command_line(args=None, final=True):
     """Parses all options given on the command line (defaults to sys.argv).
 
     Note that args[0] is ignored since it is the program name in sys.argv.
 
     We return a list of all arguments that are not parsed as options.
+
+    If ``final`` is ``False``, parse callbacks will not be run.
+    This is useful for applications that wish to combine configurations
+    from multiple sources.
     """
-    return options.parse_command_line(args)
+    return options.parse_command_line(args, final=final)
+
 
+def parse_config_file(path, final=True):
+    """Parses and loads the Python config file at the given path.
 
-def parse_config_file(path):
-    """Parses and loads the Python config file at the given path."""
-    return options.parse_config_file(path)
+    If ``final`` is ``False``, parse callbacks will not be run.
+    This is useful for applications that wish to combine configurations
+    from multiple sources.
+    """
+    return options.parse_config_file(path, final=final)
 
 
 def print_help(file=sys.stdout):
     """Prints all the command line options to stdout."""
     return options.print_help(file)
 
-
-def enable_pretty_logging(options=options):
-    """Turns on formatted logging output as configured.
-
-    This is called automatically by `parse_command_line`.
-    """
-    root_logger = logging.getLogger()
-    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)
-        channel.setFormatter(LogFormatter(color=False))
-        root_logger.addHandler(channel)
-
-    if (options.log_to_stderr or
-        (options.log_to_stderr is None and not root_logger.handlers)):
-        # Set up color if we are in a tty and curses is installed
-        channel = logging.StreamHandler()
-        channel.setFormatter(LogFormatter())
-        root_logger.addHandler(channel)
-
+def add_parse_callback(callback):
+    """Adds a parse callback, to be invoked when option parsing is done."""
+    options.add_parse_callback(callback)
 
 
 # Default options
 define("help", type=bool, help="show this help information")
-define("logging", default="info",
-       help=("Set the Python log level. If 'none', tornado won't touch the "
-             "logging configuration."),
-       metavar="debug|info|warning|error|none")
-define("log_to_stderr", type=bool, default=None,
-       help=("Send log output to stderr (colorized if possible). "
-             "By default use stderr if --log_file_prefix is not set and "
-             "no other logging is configured."))
-define("log_file_prefix", type=str, default=None, metavar="PATH",
-       help=("Path prefix for log files. "
-             "Note that if you are running multiple tornado processes, "
-             "log_file_prefix must be different for each of them (e.g. "
-             "include the port number)"))
-define("log_file_max_size", type=int, default=100 * 1000 * 1000,
-       help="max size of log files before rollover")
-define("log_file_num_backups", type=int, default=10,
-       help="number of log files to keep")
+define_logging_options(options)
index b235416cf04ec0e5bccd3c30aaf90e6188f23039..e9d65a60e7d354946164415f52cbaa14fe404c36 100644 (file)
@@ -9,7 +9,6 @@ class OptionsTest(unittest.TestCase):
         self.options = _Options()
         define = self.options.define
         # these are currently required
-        define("logging", default="none")
         define("help", default=False)
 
         define("port", default=80)
@@ -17,3 +16,23 @@ class OptionsTest(unittest.TestCase):
     def test_parse_command_line(self):
         self.options.parse_command_line(["main.py", "--port=443"])
         self.assertEqual(self.options.port, 443)
+
+    def test_parse_callbacks(self):
+        self.called = False
+        def callback():
+            self.called = True
+        self.options.add_parse_callback(callback)
+
+        # non-final parse doesn't run callbacks
+        self.options.parse_command_line(["main.py"], final=False)
+        self.assertFalse(self.called)
+
+        # final parse does
+        self.options.parse_command_line(["main.py"])
+        self.assertTrue(self.called)
+
+        # callbacks can be run more than once on the same options
+        # object if there are multiple final parses
+        self.called = False
+        self.options.parse_command_line(["main.py"])
+        self.assertTrue(self.called)
index 699aad58a56786c6c12f013237e637b115b77828..da9ba30f9b2f74da9180288cdb424fd2dc67c887 100644 (file)
@@ -81,3 +81,11 @@ In progress
 * New function `IOLoop.current` returns the ``IOLoop`` that is running
   on the current thread (as opposed to `IOLoop.instance`, which returns a
   specific thread's (usually the main thread's) IOLoop).
+* `tornado.options.parse_config_file` now configures logging automatically
+  by default, in the same way that `parse_command_line` does.
+* New function `tornado.options.add_parse_callback` schedules a callback
+  to be run after the command line or config file has been parsed.  The
+  keyword argument ``final=False`` can be used on either parsing function
+  to supress these callbacks.
+* Function `tornado.options.enable_pretty_logging` has been moved to the
+  `tornado.log` module.