Skip to content
This repository
Browse code

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.
  • Loading branch information...
commit fc6f1d5e6bf231fd00dd2e118ac175d3df90526b 1 parent fe0182d
Ben Darnell authored September 30, 2012
52  tornado/log.py
@@ -140,3 +140,55 @@ def format(self, record):
140 140
         if record.exc_text:
141 141
             formatted = formatted.rstrip() + "\n" + record.exc_text
142 142
         return formatted.replace("\n", "\n    ")
  143
+
  144
+def enable_pretty_logging(options=None):
  145
+    """Turns on formatted logging output as configured.
  146
+
  147
+    This is called automaticaly by `tornado.options.parse_command_line`
  148
+    and `tornado.options.parse_config_file`.
  149
+    """
  150
+    if options is None:
  151
+        from tornado.options import options
  152
+    if options.logging == 'none':
  153
+        return
  154
+    root_logger = logging.getLogger()
  155
+    root_logger.setLevel(getattr(logging, options.logging.upper()))
  156
+    if options.log_file_prefix:
  157
+        channel = logging.handlers.RotatingFileHandler(
  158
+            filename=options.log_file_prefix,
  159
+            maxBytes=options.log_file_max_size,
  160
+            backupCount=options.log_file_num_backups)
  161
+        channel.setFormatter(LogFormatter(color=False))
  162
+        root_logger.addHandler(channel)
  163
+
  164
+    if (options.log_to_stderr or
  165
+        (options.log_to_stderr is None and not root_logger.handlers)):
  166
+        # Set up color if we are in a tty and curses is installed
  167
+        channel = logging.StreamHandler()
  168
+        channel.setFormatter(LogFormatter())
  169
+        root_logger.addHandler(channel)
  170
+
  171
+
  172
+def define_logging_options(options=None):
  173
+    if options is None:
  174
+        # late import to prevent cycle
  175
+        from tornado.options import options
  176
+    options.define("logging", default="info",
  177
+           help=("Set the Python log level. If 'none', tornado won't touch the "
  178
+                 "logging configuration."),
  179
+           metavar="debug|info|warning|error|none")
  180
+    options.define("log_to_stderr", type=bool, default=None,
  181
+           help=("Send log output to stderr (colorized if possible). "
  182
+                 "By default use stderr if --log_file_prefix is not set and "
  183
+                 "no other logging is configured."))
  184
+    options.define("log_file_prefix", type=str, default=None, metavar="PATH",
  185
+           help=("Path prefix for log files. "
  186
+                 "Note that if you are running multiple tornado processes, "
  187
+                 "log_file_prefix must be different for each of them (e.g. "
  188
+                 "include the port number)"))
  189
+    options.define("log_file_max_size", type=int, default=100 * 1000 * 1000,
  190
+           help="max size of log files before rollover")
  191
+    options.define("log_file_num_backups", type=int, default=10,
  192
+           help="number of log files to keep")
  193
+
  194
+    options.add_parse_callback(enable_pretty_logging)
91  tornado/options.py
@@ -51,15 +51,14 @@ def connect():
51 51
 from __future__ import absolute_import, division, with_statement
52 52
 
53 53
 import datetime
54  
-import logging
55  
-import logging.handlers
56 54
 import re
57 55
 import sys
58 56
 import os
59 57
 import textwrap
60 58
 
61 59
 from tornado.escape import _unicode
62  
-from tornado.log import LogFormatter
  60
+from tornado.log import define_logging_options
  61
+from tornado import stack_context
63 62
 
64 63
 
65 64
 class Error(Exception):
@@ -73,6 +72,10 @@ class _Options(dict):
73 72
     Normally accessed via static functions in the `tornado.options` module,
74 73
     which reference a global instance.
75 74
     """
  75
+    def __init__(self):
  76
+        super(_Options, self).__init__()
  77
+        self.__dict__['_parse_callbacks'] = []
  78
+
76 79
     def __getattr__(self, name):
77 80
         if isinstance(self.get(name), _Option):
78 81
             return self[name].value()
@@ -106,7 +109,7 @@ def define(self, name, default=None, type=None, help=None, metavar=None,
106 109
                              type=type, help=help, metavar=metavar,
107 110
                              multiple=multiple, group_name=group_name)
108 111
 
109  
-    def parse_command_line(self, args=None):
  112
+    def parse_command_line(self, args=None, final=True):
110 113
         if args is None:
111 114
             args = sys.argv
112 115
         remaining = []
@@ -135,20 +138,21 @@ def parse_command_line(self, args=None):
135 138
             print_help()
136 139
             sys.exit(0)
137 140
 
138  
-        # Set up log level and pretty console logging by default
139  
-        if self.logging != 'none':
140  
-            logging.getLogger().setLevel(getattr(logging, self.logging.upper()))
141  
-            enable_pretty_logging()
  141
+        if final:
  142
+            self.run_parse_callbacks()
142 143
 
143 144
         return remaining
144 145
 
145  
-    def parse_config_file(self, path):
  146
+    def parse_config_file(self, path, final=True):
146 147
         config = {}
147 148
         execfile(path, config, config)
148 149
         for name in config:
149 150
             if name in self:
150 151
                 self[name].set(config[name])
151 152
 
  153
+        if final:
  154
+            self.run_parse_callbacks()
  155
+
152 156
     def print_help(self, file=sys.stdout):
153 157
         """Prints all the command line options to stdout."""
154 158
         print >> file, "Usage: %s [OPTIONS]" % sys.argv[0]
@@ -176,6 +180,13 @@ def print_help(self, file=sys.stdout):
176 180
                     print >> file, "%-34s %s" % (' ', line)
177 181
         print >> file
178 182
 
  183
+    def add_parse_callback(self, callback):
  184
+        self._parse_callbacks.append(stack_context.wrap(callback))
  185
+
  186
+    def run_parse_callbacks(self):
  187
+        for callback in self._parse_callbacks:
  188
+            callback()
  189
+
179 190
 
180 191
 class _Option(object):
181 192
     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,
332 343
                           metavar=metavar, multiple=multiple, group=group)
333 344
 
334 345
 
335  
-def parse_command_line(args=None):
  346
+def parse_command_line(args=None, final=True):
336 347
     """Parses all options given on the command line (defaults to sys.argv).
337 348
 
338 349
     Note that args[0] is ignored since it is the program name in sys.argv.
339 350
 
340 351
     We return a list of all arguments that are not parsed as options.
  352
+
  353
+    If ``final`` is ``False``, parse callbacks will not be run.
  354
+    This is useful for applications that wish to combine configurations
  355
+    from multiple sources.
341 356
     """
342  
-    return options.parse_command_line(args)
  357
+    return options.parse_command_line(args, final=final)
  358
+
343 359
 
  360
+def parse_config_file(path, final=True):
  361
+    """Parses and loads the Python config file at the given path.
344 362
 
345  
-def parse_config_file(path):
346  
-    """Parses and loads the Python config file at the given path."""
347  
-    return options.parse_config_file(path)
  363
+    If ``final`` is ``False``, parse callbacks will not be run.
  364
+    This is useful for applications that wish to combine configurations
  365
+    from multiple sources.
  366
+    """
  367
+    return options.parse_config_file(path, final=final)
348 368
 
349 369
 
350 370
 def print_help(file=sys.stdout):
351 371
     """Prints all the command line options to stdout."""
352 372
     return options.print_help(file)
353 373
 
354  
-
355  
-def enable_pretty_logging(options=options):
356  
-    """Turns on formatted logging output as configured.
357  
-
358  
-    This is called automatically by `parse_command_line`.
359  
-    """
360  
-    root_logger = logging.getLogger()
361  
-    if options.log_file_prefix:
362  
-        channel = logging.handlers.RotatingFileHandler(
363  
-            filename=options.log_file_prefix,
364  
-            maxBytes=options.log_file_max_size,
365  
-            backupCount=options.log_file_num_backups)
366  
-        channel.setFormatter(LogFormatter(color=False))
367  
-        root_logger.addHandler(channel)
368  
-
369  
-    if (options.log_to_stderr or
370  
-        (options.log_to_stderr is None and not root_logger.handlers)):
371  
-        # Set up color if we are in a tty and curses is installed
372  
-        channel = logging.StreamHandler()
373  
-        channel.setFormatter(LogFormatter())
374  
-        root_logger.addHandler(channel)
375  
-
  374
+def add_parse_callback(callback):
  375
+    """Adds a parse callback, to be invoked when option parsing is done."""
  376
+    options.add_parse_callback(callback)
376 377
 
377 378
 
378 379
 # Default options
379 380
 define("help", type=bool, help="show this help information")
380  
-define("logging", default="info",
381  
-       help=("Set the Python log level. If 'none', tornado won't touch the "
382  
-             "logging configuration."),
383  
-       metavar="debug|info|warning|error|none")
384  
-define("log_to_stderr", type=bool, default=None,
385  
-       help=("Send log output to stderr (colorized if possible). "
386  
-             "By default use stderr if --log_file_prefix is not set and "
387  
-             "no other logging is configured."))
388  
-define("log_file_prefix", type=str, default=None, metavar="PATH",
389  
-       help=("Path prefix for log files. "
390  
-             "Note that if you are running multiple tornado processes, "
391  
-             "log_file_prefix must be different for each of them (e.g. "
392  
-             "include the port number)"))
393  
-define("log_file_max_size", type=int, default=100 * 1000 * 1000,
394  
-       help="max size of log files before rollover")
395  
-define("log_file_num_backups", type=int, default=10,
396  
-       help="number of log files to keep")
  381
+define_logging_options(options)
21  tornado/test/options_test.py
@@ -9,7 +9,6 @@ def setUp(self):
9 9
         self.options = _Options()
10 10
         define = self.options.define
11 11
         # these are currently required
12  
-        define("logging", default="none")
13 12
         define("help", default=False)
14 13
 
15 14
         define("port", default=80)
@@ -17,3 +16,23 @@ def setUp(self):
17 16
     def test_parse_command_line(self):
18 17
         self.options.parse_command_line(["main.py", "--port=443"])
19 18
         self.assertEqual(self.options.port, 443)
  19
+
  20
+    def test_parse_callbacks(self):
  21
+        self.called = False
  22
+        def callback():
  23
+            self.called = True
  24
+        self.options.add_parse_callback(callback)
  25
+
  26
+        # non-final parse doesn't run callbacks
  27
+        self.options.parse_command_line(["main.py"], final=False)
  28
+        self.assertFalse(self.called)
  29
+
  30
+        # final parse does
  31
+        self.options.parse_command_line(["main.py"])
  32
+        self.assertTrue(self.called)
  33
+
  34
+        # callbacks can be run more than once on the same options
  35
+        # object if there are multiple final parses
  36
+        self.called = False
  37
+        self.options.parse_command_line(["main.py"])
  38
+        self.assertTrue(self.called)
8  website/sphinx/releases/next.rst
Source Rendered
@@ -81,3 +81,11 @@ In progress
81 81
 * New function `IOLoop.current` returns the ``IOLoop`` that is running
82 82
   on the current thread (as opposed to `IOLoop.instance`, which returns a
83 83
   specific thread's (usually the main thread's) IOLoop).
  84
+* `tornado.options.parse_config_file` now configures logging automatically
  85
+  by default, in the same way that `parse_command_line` does.
  86
+* New function `tornado.options.add_parse_callback` schedules a callback
  87
+  to be run after the command line or config file has been parsed.  The
  88
+  keyword argument ``final=False`` can be used on either parsing function
  89
+  to supress these callbacks.
  90
+* Function `tornado.options.enable_pretty_logging` has been moved to the
  91
+  `tornado.log` module.

0 notes on commit fc6f1d5

Please sign in to comment.
Something went wrong with that request. Please try again.