From: Ben Darnell Date: Sun, 30 Sep 2012 22:05:29 +0000 (-0700) Subject: Introduce parse callbacks in the options module. X-Git-Tag: v3.0.0~257 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fc6f1d5e6bf231fd00dd2e118ac175d3df90526b;p=thirdparty%2Ftornado.git Introduce parse callbacks in the options module. This is a generalization of the existing magic for logging. Logging configuration is now moved to log.py. --- diff --git a/tornado/log.py b/tornado/log.py index 988af3ac4..7a4a8332f 100644 --- a/tornado/log.py +++ b/tornado/log.py @@ -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) diff --git a/tornado/options.py b/tornado/options.py index 1f15050e1..c5677c290 100644 --- a/tornado/options.py +++ b/tornado/options.py @@ -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) diff --git a/tornado/test/options_test.py b/tornado/test/options_test.py index b235416cf..e9d65a60e 100644 --- a/tornado/test/options_test.py +++ b/tornado/test/options_test.py @@ -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) diff --git a/website/sphinx/releases/next.rst b/website/sphinx/releases/next.rst index 699aad58a..da9ba30f9 100644 --- a/website/sphinx/releases/next.rst +++ b/website/sphinx/releases/next.rst @@ -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.