Index: third_party/cherrypy/_cplogging.py |
=================================================================== |
--- third_party/cherrypy/_cplogging.py (revision 0) |
+++ third_party/cherrypy/_cplogging.py (revision 0) |
@@ -0,0 +1,440 @@ |
+""" |
+Simple config |
+============= |
+ |
+Although CherryPy uses the :mod:`Python logging module <logging>`, it does so |
+behind the scenes so that simple logging is simple, but complicated logging |
+is still possible. "Simple" logging means that you can log to the screen |
+(i.e. console/stdout) or to a file, and that you can easily have separate |
+error and access log files. |
+ |
+Here are the simplified logging settings. You use these by adding lines to |
+your config file or dict. You should set these at either the global level or |
+per application (see next), but generally not both. |
+ |
+ * ``log.screen``: Set this to True to have both "error" and "access" messages |
+ printed to stdout. |
+ * ``log.access_file``: Set this to an absolute filename where you want |
+ "access" messages written. |
+ * ``log.error_file``: Set this to an absolute filename where you want "error" |
+ messages written. |
+ |
+Many events are automatically logged; to log your own application events, call |
+:func:`cherrypy.log`. |
+ |
+Architecture |
+============ |
+ |
+Separate scopes |
+--------------- |
+ |
+CherryPy provides log managers at both the global and application layers. |
+This means you can have one set of logging rules for your entire site, |
+and another set of rules specific to each application. The global log |
+manager is found at :func:`cherrypy.log`, and the log manager for each |
+application is found at :attr:`app.log<cherrypy._cptree.Application.log>`. |
+If you're inside a request, the latter is reachable from |
+``cherrypy.request.app.log``; if you're outside a request, you'll have to obtain |
+a reference to the ``app``: either the return value of |
+:func:`tree.mount()<cherrypy._cptree.Tree.mount>` or, if you used |
+:func:`quickstart()<cherrypy.quickstart>` instead, via ``cherrypy.tree.apps['/']``. |
+ |
+By default, the global logs are named "cherrypy.error" and "cherrypy.access", |
+and the application logs are named "cherrypy.error.2378745" and |
+"cherrypy.access.2378745" (the number is the id of the Application object). |
+This means that the application logs "bubble up" to the site logs, so if your |
+application has no log handlers, the site-level handlers will still log the |
+messages. |
+ |
+Errors vs. Access |
+----------------- |
+ |
+Each log manager handles both "access" messages (one per HTTP request) and |
+"error" messages (everything else). Note that the "error" log is not just for |
+errors! The format of access messages is highly formalized, but the error log |
+isn't--it receives messages from a variety of sources (including full error |
+tracebacks, if enabled). |
+ |
+ |
+Custom Handlers |
+=============== |
+ |
+The simple settings above work by manipulating Python's standard :mod:`logging` |
+module. So when you need something more complex, the full power of the standard |
+module is yours to exploit. You can borrow or create custom handlers, formats, |
+filters, and much more. Here's an example that skips the standard FileHandler |
+and uses a RotatingFileHandler instead: |
+ |
+:: |
+ |
+ #python |
+ log = app.log |
+ |
+ # Remove the default FileHandlers if present. |
+ log.error_file = "" |
+ log.access_file = "" |
+ |
+ maxBytes = getattr(log, "rot_maxBytes", 10000000) |
+ backupCount = getattr(log, "rot_backupCount", 1000) |
+ |
+ # Make a new RotatingFileHandler for the error log. |
+ fname = getattr(log, "rot_error_file", "error.log") |
+ h = handlers.RotatingFileHandler(fname, 'a', maxBytes, backupCount) |
+ h.setLevel(DEBUG) |
+ h.setFormatter(_cplogging.logfmt) |
+ log.error_log.addHandler(h) |
+ |
+ # Make a new RotatingFileHandler for the access log. |
+ fname = getattr(log, "rot_access_file", "access.log") |
+ h = handlers.RotatingFileHandler(fname, 'a', maxBytes, backupCount) |
+ h.setLevel(DEBUG) |
+ h.setFormatter(_cplogging.logfmt) |
+ log.access_log.addHandler(h) |
+ |
+ |
+The ``rot_*`` attributes are pulled straight from the application log object. |
+Since "log.*" config entries simply set attributes on the log object, you can |
+add custom attributes to your heart's content. Note that these handlers are |
+used ''instead'' of the default, simple handlers outlined above (so don't set |
+the "log.error_file" config entry, for example). |
+""" |
+ |
+import datetime |
+import logging |
+# Silence the no-handlers "warning" (stderr write!) in stdlib logging |
+logging.Logger.manager.emittedNoHandlerWarning = 1 |
+logfmt = logging.Formatter("%(message)s") |
+import os |
+import sys |
+ |
+import cherrypy |
+from cherrypy import _cperror |
+from cherrypy._cpcompat import ntob, py3k |
+ |
+ |
+class NullHandler(logging.Handler): |
+ """A no-op logging handler to silence the logging.lastResort handler.""" |
+ |
+ def handle(self, record): |
+ pass |
+ |
+ def emit(self, record): |
+ pass |
+ |
+ def createLock(self): |
+ self.lock = None |
+ |
+ |
+class LogManager(object): |
+ """An object to assist both simple and advanced logging. |
+ |
+ ``cherrypy.log`` is an instance of this class. |
+ """ |
+ |
+ appid = None |
+ """The id() of the Application object which owns this log manager. If this |
+ is a global log manager, appid is None.""" |
+ |
+ error_log = None |
+ """The actual :class:`logging.Logger` instance for error messages.""" |
+ |
+ access_log = None |
+ """The actual :class:`logging.Logger` instance for access messages.""" |
+ |
+ if py3k: |
+ access_log_format = \ |
+ '{h} {l} {u} {t} "{r}" {s} {b} "{f}" "{a}"' |
+ else: |
+ access_log_format = \ |
+ '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"' |
+ |
+ logger_root = None |
+ """The "top-level" logger name. |
+ |
+ This string will be used as the first segment in the Logger names. |
+ The default is "cherrypy", for example, in which case the Logger names |
+ will be of the form:: |
+ |
+ cherrypy.error.<appid> |
+ cherrypy.access.<appid> |
+ """ |
+ |
+ def __init__(self, appid=None, logger_root="cherrypy"): |
+ self.logger_root = logger_root |
+ self.appid = appid |
+ if appid is None: |
+ self.error_log = logging.getLogger("%s.error" % logger_root) |
+ self.access_log = logging.getLogger("%s.access" % logger_root) |
+ else: |
+ self.error_log = logging.getLogger("%s.error.%s" % (logger_root, appid)) |
+ self.access_log = logging.getLogger("%s.access.%s" % (logger_root, appid)) |
+ self.error_log.setLevel(logging.INFO) |
+ self.access_log.setLevel(logging.INFO) |
+ |
+ # Silence the no-handlers "warning" (stderr write!) in stdlib logging |
+ self.error_log.addHandler(NullHandler()) |
+ self.access_log.addHandler(NullHandler()) |
+ |
+ cherrypy.engine.subscribe('graceful', self.reopen_files) |
+ |
+ def reopen_files(self): |
+ """Close and reopen all file handlers.""" |
+ for log in (self.error_log, self.access_log): |
+ for h in log.handlers: |
+ if isinstance(h, logging.FileHandler): |
+ h.acquire() |
+ h.stream.close() |
+ h.stream = open(h.baseFilename, h.mode) |
+ h.release() |
+ |
+ def error(self, msg='', context='', severity=logging.INFO, traceback=False): |
+ """Write the given ``msg`` to the error log. |
+ |
+ This is not just for errors! Applications may call this at any time |
+ to log application-specific information. |
+ |
+ If ``traceback`` is True, the traceback of the current exception |
+ (if any) will be appended to ``msg``. |
+ """ |
+ if traceback: |
+ msg += _cperror.format_exc() |
+ self.error_log.log(severity, ' '.join((self.time(), context, msg))) |
+ |
+ def __call__(self, *args, **kwargs): |
+ """An alias for ``error``.""" |
+ return self.error(*args, **kwargs) |
+ |
+ def access(self): |
+ """Write to the access log (in Apache/NCSA Combined Log format). |
+ |
+ See http://httpd.apache.org/docs/2.0/logs.html#combined for format |
+ details. |
+ |
+ CherryPy calls this automatically for you. Note there are no arguments; |
+ it collects the data itself from |
+ :class:`cherrypy.request<cherrypy._cprequest.Request>`. |
+ |
+ Like Apache started doing in 2.0.46, non-printable and other special |
+ characters in %r (and we expand that to all parts) are escaped using |
+ \\xhh sequences, where hh stands for the hexadecimal representation |
+ of the raw byte. Exceptions from this rule are " and \\, which are |
+ escaped by prepending a backslash, and all whitespace characters, |
+ which are written in their C-style notation (\\n, \\t, etc). |
+ """ |
+ request = cherrypy.serving.request |
+ remote = request.remote |
+ response = cherrypy.serving.response |
+ outheaders = response.headers |
+ inheaders = request.headers |
+ if response.output_status is None: |
+ status = "-" |
+ else: |
+ status = response.output_status.split(ntob(" "), 1)[0] |
+ if py3k: |
+ status = status.decode('ISO-8859-1') |
+ |
+ atoms = {'h': remote.name or remote.ip, |
+ 'l': '-', |
+ 'u': getattr(request, "login", None) or "-", |
+ 't': self.time(), |
+ 'r': request.request_line, |
+ 's': status, |
+ 'b': dict.get(outheaders, 'Content-Length', '') or "-", |
+ 'f': dict.get(inheaders, 'Referer', ''), |
+ 'a': dict.get(inheaders, 'User-Agent', ''), |
+ } |
+ if py3k: |
+ for k, v in atoms.items(): |
+ if not isinstance(v, str): |
+ v = str(v) |
+ v = v.replace('"', '\\"').encode('utf8') |
+ # Fortunately, repr(str) escapes unprintable chars, \n, \t, etc |
+ # and backslash for us. All we have to do is strip the quotes. |
+ v = repr(v)[2:-1] |
+ |
+ # in python 3.0 the repr of bytes (as returned by encode) |
+ # uses double \'s. But then the logger escapes them yet, again |
+ # resulting in quadruple slashes. Remove the extra one here. |
+ v = v.replace('\\\\', '\\') |
+ |
+ # Escape double-quote. |
+ atoms[k] = v |
+ |
+ try: |
+ self.access_log.log(logging.INFO, self.access_log_format.format(**atoms)) |
+ except: |
+ self(traceback=True) |
+ else: |
+ for k, v in atoms.items(): |
+ if isinstance(v, unicode): |
+ v = v.encode('utf8') |
+ elif not isinstance(v, str): |
+ v = str(v) |
+ # Fortunately, repr(str) escapes unprintable chars, \n, \t, etc |
+ # and backslash for us. All we have to do is strip the quotes. |
+ v = repr(v)[1:-1] |
+ # Escape double-quote. |
+ atoms[k] = v.replace('"', '\\"') |
+ |
+ try: |
+ self.access_log.log(logging.INFO, self.access_log_format % atoms) |
+ except: |
+ self(traceback=True) |
+ |
+ def time(self): |
+ """Return now() in Apache Common Log Format (no timezone).""" |
+ now = datetime.datetime.now() |
+ monthnames = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', |
+ 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'] |
+ month = monthnames[now.month - 1].capitalize() |
+ return ('[%02d/%s/%04d:%02d:%02d:%02d]' % |
+ (now.day, month, now.year, now.hour, now.minute, now.second)) |
+ |
+ def _get_builtin_handler(self, log, key): |
+ for h in log.handlers: |
+ if getattr(h, "_cpbuiltin", None) == key: |
+ return h |
+ |
+ |
+ # ------------------------- Screen handlers ------------------------- # |
+ |
+ def _set_screen_handler(self, log, enable, stream=None): |
+ h = self._get_builtin_handler(log, "screen") |
+ if enable: |
+ if not h: |
+ if stream is None: |
+ stream=sys.stderr |
+ h = logging.StreamHandler(stream) |
+ h.setFormatter(logfmt) |
+ h._cpbuiltin = "screen" |
+ log.addHandler(h) |
+ elif h: |
+ log.handlers.remove(h) |
+ |
+ def _get_screen(self): |
+ h = self._get_builtin_handler |
+ has_h = h(self.error_log, "screen") or h(self.access_log, "screen") |
+ return bool(has_h) |
+ |
+ def _set_screen(self, newvalue): |
+ self._set_screen_handler(self.error_log, newvalue, stream=sys.stderr) |
+ self._set_screen_handler(self.access_log, newvalue, stream=sys.stdout) |
+ screen = property(_get_screen, _set_screen, |
+ doc="""Turn stderr/stdout logging on or off. |
+ |
+ If you set this to True, it'll add the appropriate StreamHandler for |
+ you. If you set it to False, it will remove the handler. |
+ """) |
+ |
+ # -------------------------- File handlers -------------------------- # |
+ |
+ def _add_builtin_file_handler(self, log, fname): |
+ h = logging.FileHandler(fname) |
+ h.setFormatter(logfmt) |
+ h._cpbuiltin = "file" |
+ log.addHandler(h) |
+ |
+ def _set_file_handler(self, log, filename): |
+ h = self._get_builtin_handler(log, "file") |
+ if filename: |
+ if h: |
+ if h.baseFilename != os.path.abspath(filename): |
+ h.close() |
+ log.handlers.remove(h) |
+ self._add_builtin_file_handler(log, filename) |
+ else: |
+ self._add_builtin_file_handler(log, filename) |
+ else: |
+ if h: |
+ h.close() |
+ log.handlers.remove(h) |
+ |
+ def _get_error_file(self): |
+ h = self._get_builtin_handler(self.error_log, "file") |
+ if h: |
+ return h.baseFilename |
+ return '' |
+ def _set_error_file(self, newvalue): |
+ self._set_file_handler(self.error_log, newvalue) |
+ error_file = property(_get_error_file, _set_error_file, |
+ doc="""The filename for self.error_log. |
+ |
+ If you set this to a string, it'll add the appropriate FileHandler for |
+ you. If you set it to ``None`` or ``''``, it will remove the handler. |
+ """) |
+ |
+ def _get_access_file(self): |
+ h = self._get_builtin_handler(self.access_log, "file") |
+ if h: |
+ return h.baseFilename |
+ return '' |
+ def _set_access_file(self, newvalue): |
+ self._set_file_handler(self.access_log, newvalue) |
+ access_file = property(_get_access_file, _set_access_file, |
+ doc="""The filename for self.access_log. |
+ |
+ If you set this to a string, it'll add the appropriate FileHandler for |
+ you. If you set it to ``None`` or ``''``, it will remove the handler. |
+ """) |
+ |
+ # ------------------------- WSGI handlers ------------------------- # |
+ |
+ def _set_wsgi_handler(self, log, enable): |
+ h = self._get_builtin_handler(log, "wsgi") |
+ if enable: |
+ if not h: |
+ h = WSGIErrorHandler() |
+ h.setFormatter(logfmt) |
+ h._cpbuiltin = "wsgi" |
+ log.addHandler(h) |
+ elif h: |
+ log.handlers.remove(h) |
+ |
+ def _get_wsgi(self): |
+ return bool(self._get_builtin_handler(self.error_log, "wsgi")) |
+ |
+ def _set_wsgi(self, newvalue): |
+ self._set_wsgi_handler(self.error_log, newvalue) |
+ wsgi = property(_get_wsgi, _set_wsgi, |
+ doc="""Write errors to wsgi.errors. |
+ |
+ If you set this to True, it'll add the appropriate |
+ :class:`WSGIErrorHandler<cherrypy._cplogging.WSGIErrorHandler>` for you |
+ (which writes errors to ``wsgi.errors``). |
+ If you set it to False, it will remove the handler. |
+ """) |
+ |
+ |
+class WSGIErrorHandler(logging.Handler): |
+ "A handler class which writes logging records to environ['wsgi.errors']." |
+ |
+ def flush(self): |
+ """Flushes the stream.""" |
+ try: |
+ stream = cherrypy.serving.request.wsgi_environ.get('wsgi.errors') |
+ except (AttributeError, KeyError): |
+ pass |
+ else: |
+ stream.flush() |
+ |
+ def emit(self, record): |
+ """Emit a record.""" |
+ try: |
+ stream = cherrypy.serving.request.wsgi_environ.get('wsgi.errors') |
+ except (AttributeError, KeyError): |
+ pass |
+ else: |
+ try: |
+ msg = self.format(record) |
+ fs = "%s\n" |
+ import types |
+ if not hasattr(types, "UnicodeType"): #if no unicode support... |
+ stream.write(fs % msg) |
+ else: |
+ try: |
+ stream.write(fs % msg) |
+ except UnicodeError: |
+ stream.write(fs % msg.encode("UTF-8")) |
+ self.flush() |
+ except: |
+ self.handleError(record) |
Property changes on: third_party/cherrypy/_cplogging.py |
___________________________________________________________________ |
Added: svn:eol-style |
+ LF |