Robert Brewer avatar Robert Brewer committed bc16fc1

Much Better Logging (see #256). Apps now have their own access and error loggers (whose config entries must be at "/"), and the global access logger has been removed (although you can make one manually if you like).

Comments (0)

Files changed (5)

     return '%02d/%s/%04d:%02d:%02d:%02d' % (
         now.day, month, now.year, now.hour, now.minute, now.second)
 
-_logfmt = logging.Formatter("%(message)s")
-
-_access_log = logging.getLogger("cherrypy.access")
-_access_log.setLevel(logging.INFO)
-
-def _add_access_log_handler(handler):
-    if handler.level == logging.NOTSET:
-        handler.setLevel(logging.INFO)
-    if handler.formatter is None:
-        handler.setFormatter(_logfmt)
-    _access_log.addHandler(handler)
-
 def log_access():
     """Default method for logging access"""
     tmpl = '%(h)s %(l)s %(u)s [%(t)s] "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'
                 'f': request.headers.get('referer', ''),
                 'a': request.headers.get('user-agent', ''),
                 }
-        
-    # Create handlers if needed
-    if not _access_log.handlers:
-        if config.get('server.log_to_screen'):
-            _add_access_log_handler(logging.StreamHandler(sys.stdout))
-        fname = config.get('log_access_file', '')
-        if fname:
-            _add_access_log_handler(logging.FileHandler(fname))
-    
-    _access_log.log(logging.INFO, s)
+    try:
+        request.app.access_log.log(logging.INFO, s)
+    except:
+        log(traceback=True)
 
 
 _error_log = logging.getLogger("cherrypy.error")
 _error_log.setLevel(logging.DEBUG)
 
-def _add_error_log_handler(handler):
-    if handler.level == logging.NOTSET:
-        handler.setLevel(logging.DEBUG)
-    if handler.formatter is None:
-        handler.setFormatter(_logfmt)
-    _error_log.addHandler(handler)
-
 def _log_message(msg, context = '', severity = logging.DEBUG):
     """Default method for logging messages (error log).
     
     log application-specific information.
     """
     
-    # Create handlers if needed
-    if not _error_log.handlers:
-        if config.get('server.log_to_screen'):
-            _add_error_log_handler(logging.StreamHandler(sys.stdout))
-        fname = config.get('log_file', '')
-        if fname:
-            _add_error_log_handler(logging.FileHandler(fname))
-    
-    _error_log.log(severity, ' '.join((logtime(), context, msg)))
+    try:
+        log = request.app.error_log
+    except AttributeError:
+        log = _error_log
+    log.log(severity, ' '.join((logtime(), context, msg)))
 
 def log(msg='', context='', severity=logging.DEBUG, traceback=False):
     """Syntactic sugar for writing to the (error) log.
         func = getattr(mod, fname)
         func()
     
-    cherrypy.config.update({'global' : {'server.log_to_screen' : False}})
+    cherrypy.config.update({'global' : {'log_to_screen' : False}})
     
     if cherrypy.engine.state == cherrypy._cpengine.STOPPED:
         cherrypy.engine.start(blocking=False)
+import logging
+import sys
 
 from cherrypy import config
 
 
 class Application:
+    """A CherryPy Application."""
     
     def __init__(self, root, script_name="", conf=None):
+        self.access_log = log = logging.getLogger("cherrypy.access.%s" % id(self))
+        log.setLevel(logging.INFO)
+        
+        self.error_log = log = logging.getLogger("cherrypy.error.%s" % id(self))
+        log.setLevel(logging.DEBUG)
+        
         self.root = root
         self.script_name = script_name
         self.conf = {}
             self.merge(conf)
     
     def merge(self, conf):
+        """Merge the given config into self.config."""
         config.merge(self.conf, conf)
+        
+        # Create log handlers as specified in config.
+        rootconf = self.conf.get("/", {})
+        config._configure_builtin_logging(rootconf, self.access_log, "log_access_file")
+        config._configure_builtin_logging(rootconf, self.error_log)
     
     def guess_abs_path(self):
         """Guess the absolute URL from server.socket_host and script_name.
 
 
 class Tree:
-    """A registry of mounted applications at diverse points."""
+    """A registry of CherryPy applications, mounted at diverse points."""
     
     def __init__(self):
         self.apps = {}
 """Configuration system for CherryPy."""
 
 import ConfigParser
+import logging
+_logfmt = logging.Formatter("%(message)s")
 import os
+import sys
 
 import cherrypy
 from cherrypy.lib import autoreload, unrepr
 
 def reset():
     globalconf.clear()
-    globalconf.update(default_conf)
+    update(default_conf)
 
 def update(conf):
     """Update globalconf from a dict, file or filename."""
     if isinstance(conf.get("global", None), dict):
         conf = conf["global"]
     globalconf.update(conf)
+    
+    _configure_builtin_logging(globalconf, cherrypy._error_log)
 
+def _add_builtin_screen_handler(log):
+    h = logging.StreamHandler(sys.stdout)
+    h.setLevel(logging.DEBUG)
+    h.setFormatter(_logfmt)
+    h._cpbuiltin = "screen"
+    log.addHandler(h)
+
+def _add_builtin_file_handler(log, fname):
+    h = logging.FileHandler(fname)
+    h.setLevel(logging.DEBUG)
+    h.setFormatter(_logfmt)
+    h._cpbuiltin = "file"
+    log.addHandler(h)
+
+def _configure_builtin_logging(conf, log, filekey="log_file"):
+    """Create/destroy builtin log handlers as needed from conf."""
+    
+    existing = dict([(getattr(x, "_cpbuiltin", None), x)
+                     for x in log.handlers])
+    h = existing.get("screen")
+    screen = conf.get('log_to_screen')
+    if screen:
+        if not h:
+            _add_builtin_screen_handler(log)
+    elif h:
+        log.handlers.remove(h)
+    
+    h = existing.get("file")
+    fname = conf.get(filekey)
+    if fname:
+        if h:
+            if h.baseFilename != os.path.abspath(fname):
+                h.close()
+                log.handlers.remove(h)
+                _add_builtin_file_handler(log, fname)
+        else:
+            _add_builtin_file_handler(log, fname)
+    else:
+        if h:
+            h.close()
+            log.handlers.remove(h)
 
 def get(key, default=None):
     """Return the config value corresponding to key, or default."""

test/test_core.py

     
     cherrypy.config.update({
         'log_to_screen': False,
-        'log_access_file': log_access_file,
+        'log_file': log_file,
         'server.protocol_version': "HTTP/1.1",
         'environment': 'production',
         'show_tracebacks': True,
         'server.max_request_body_size': 200,
         'server.max_request_header_size': 500,
         })
-    # When run via test.py, the engine is started (and the loggers created)
-    # before the above config.update, so we do it again manually.
-    import logging
-    cherrypy._add_error_log_handler(logging.FileHandler(log_file))
-    
-    cherrypy.tree.mount(root)
+    cherrypy.tree.mount(root, conf={'/': {'log_access_file': log_access_file}})
 
 
 #                             Client-side code                             #
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.