OLD | NEW |
(Empty) | |
| 1 """ |
| 2 Simple config |
| 3 ============= |
| 4 |
| 5 Although CherryPy uses the :mod:`Python logging module <logging>`, it does so |
| 6 behind the scenes so that simple logging is simple, but complicated logging |
| 7 is still possible. "Simple" logging means that you can log to the screen |
| 8 (i.e. console/stdout) or to a file, and that you can easily have separate |
| 9 error and access log files. |
| 10 |
| 11 Here are the simplified logging settings. You use these by adding lines to |
| 12 your config file or dict. You should set these at either the global level or |
| 13 per application (see next), but generally not both. |
| 14 |
| 15 * ``log.screen``: Set this to True to have both "error" and "access" messages |
| 16 printed to stdout. |
| 17 * ``log.access_file``: Set this to an absolute filename where you want |
| 18 "access" messages written. |
| 19 * ``log.error_file``: Set this to an absolute filename where you want "error" |
| 20 messages written. |
| 21 |
| 22 Many events are automatically logged; to log your own application events, call |
| 23 :func:`cherrypy.log`. |
| 24 |
| 25 Architecture |
| 26 ============ |
| 27 |
| 28 Separate scopes |
| 29 --------------- |
| 30 |
| 31 CherryPy provides log managers at both the global and application layers. |
| 32 This means you can have one set of logging rules for your entire site, |
| 33 and another set of rules specific to each application. The global log |
| 34 manager is found at :func:`cherrypy.log`, and the log manager for each |
| 35 application is found at :attr:`app.log<cherrypy._cptree.Application.log>`. |
| 36 If you're inside a request, the latter is reachable from |
| 37 ``cherrypy.request.app.log``; if you're outside a request, you'll have to obtain |
| 38 a reference to the ``app``: either the return value of |
| 39 :func:`tree.mount()<cherrypy._cptree.Tree.mount>` or, if you used |
| 40 :func:`quickstart()<cherrypy.quickstart>` instead, via ``cherrypy.tree.apps['/']
``. |
| 41 |
| 42 By default, the global logs are named "cherrypy.error" and "cherrypy.access", |
| 43 and the application logs are named "cherrypy.error.2378745" and |
| 44 "cherrypy.access.2378745" (the number is the id of the Application object). |
| 45 This means that the application logs "bubble up" to the site logs, so if your |
| 46 application has no log handlers, the site-level handlers will still log the |
| 47 messages. |
| 48 |
| 49 Errors vs. Access |
| 50 ----------------- |
| 51 |
| 52 Each log manager handles both "access" messages (one per HTTP request) and |
| 53 "error" messages (everything else). Note that the "error" log is not just for |
| 54 errors! The format of access messages is highly formalized, but the error log |
| 55 isn't--it receives messages from a variety of sources (including full error |
| 56 tracebacks, if enabled). |
| 57 |
| 58 |
| 59 Custom Handlers |
| 60 =============== |
| 61 |
| 62 The simple settings above work by manipulating Python's standard :mod:`logging` |
| 63 module. So when you need something more complex, the full power of the standard |
| 64 module is yours to exploit. You can borrow or create custom handlers, formats, |
| 65 filters, and much more. Here's an example that skips the standard FileHandler |
| 66 and uses a RotatingFileHandler instead: |
| 67 |
| 68 :: |
| 69 |
| 70 #python |
| 71 log = app.log |
| 72 |
| 73 # Remove the default FileHandlers if present. |
| 74 log.error_file = "" |
| 75 log.access_file = "" |
| 76 |
| 77 maxBytes = getattr(log, "rot_maxBytes", 10000000) |
| 78 backupCount = getattr(log, "rot_backupCount", 1000) |
| 79 |
| 80 # Make a new RotatingFileHandler for the error log. |
| 81 fname = getattr(log, "rot_error_file", "error.log") |
| 82 h = handlers.RotatingFileHandler(fname, 'a', maxBytes, backupCount) |
| 83 h.setLevel(DEBUG) |
| 84 h.setFormatter(_cplogging.logfmt) |
| 85 log.error_log.addHandler(h) |
| 86 |
| 87 # Make a new RotatingFileHandler for the access log. |
| 88 fname = getattr(log, "rot_access_file", "access.log") |
| 89 h = handlers.RotatingFileHandler(fname, 'a', maxBytes, backupCount) |
| 90 h.setLevel(DEBUG) |
| 91 h.setFormatter(_cplogging.logfmt) |
| 92 log.access_log.addHandler(h) |
| 93 |
| 94 |
| 95 The ``rot_*`` attributes are pulled straight from the application log object. |
| 96 Since "log.*" config entries simply set attributes on the log object, you can |
| 97 add custom attributes to your heart's content. Note that these handlers are |
| 98 used ''instead'' of the default, simple handlers outlined above (so don't set |
| 99 the "log.error_file" config entry, for example). |
| 100 """ |
| 101 |
| 102 import datetime |
| 103 import logging |
| 104 # Silence the no-handlers "warning" (stderr write!) in stdlib logging |
| 105 logging.Logger.manager.emittedNoHandlerWarning = 1 |
| 106 logfmt = logging.Formatter("%(message)s") |
| 107 import os |
| 108 import sys |
| 109 |
| 110 import cherrypy |
| 111 from cherrypy import _cperror |
| 112 from cherrypy._cpcompat import ntob, py3k |
| 113 |
| 114 |
| 115 class NullHandler(logging.Handler): |
| 116 """A no-op logging handler to silence the logging.lastResort handler.""" |
| 117 |
| 118 def handle(self, record): |
| 119 pass |
| 120 |
| 121 def emit(self, record): |
| 122 pass |
| 123 |
| 124 def createLock(self): |
| 125 self.lock = None |
| 126 |
| 127 |
| 128 class LogManager(object): |
| 129 """An object to assist both simple and advanced logging. |
| 130 |
| 131 ``cherrypy.log`` is an instance of this class. |
| 132 """ |
| 133 |
| 134 appid = None |
| 135 """The id() of the Application object which owns this log manager. If this |
| 136 is a global log manager, appid is None.""" |
| 137 |
| 138 error_log = None |
| 139 """The actual :class:`logging.Logger` instance for error messages.""" |
| 140 |
| 141 access_log = None |
| 142 """The actual :class:`logging.Logger` instance for access messages.""" |
| 143 |
| 144 if py3k: |
| 145 access_log_format = \ |
| 146 '{h} {l} {u} {t} "{r}" {s} {b} "{f}" "{a}"' |
| 147 else: |
| 148 access_log_format = \ |
| 149 '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"' |
| 150 |
| 151 logger_root = None |
| 152 """The "top-level" logger name. |
| 153 |
| 154 This string will be used as the first segment in the Logger names. |
| 155 The default is "cherrypy", for example, in which case the Logger names |
| 156 will be of the form:: |
| 157 |
| 158 cherrypy.error.<appid> |
| 159 cherrypy.access.<appid> |
| 160 """ |
| 161 |
| 162 def __init__(self, appid=None, logger_root="cherrypy"): |
| 163 self.logger_root = logger_root |
| 164 self.appid = appid |
| 165 if appid is None: |
| 166 self.error_log = logging.getLogger("%s.error" % logger_root) |
| 167 self.access_log = logging.getLogger("%s.access" % logger_root) |
| 168 else: |
| 169 self.error_log = logging.getLogger("%s.error.%s" % (logger_root, app
id)) |
| 170 self.access_log = logging.getLogger("%s.access.%s" % (logger_root, a
ppid)) |
| 171 self.error_log.setLevel(logging.INFO) |
| 172 self.access_log.setLevel(logging.INFO) |
| 173 |
| 174 # Silence the no-handlers "warning" (stderr write!) in stdlib logging |
| 175 self.error_log.addHandler(NullHandler()) |
| 176 self.access_log.addHandler(NullHandler()) |
| 177 |
| 178 cherrypy.engine.subscribe('graceful', self.reopen_files) |
| 179 |
| 180 def reopen_files(self): |
| 181 """Close and reopen all file handlers.""" |
| 182 for log in (self.error_log, self.access_log): |
| 183 for h in log.handlers: |
| 184 if isinstance(h, logging.FileHandler): |
| 185 h.acquire() |
| 186 h.stream.close() |
| 187 h.stream = open(h.baseFilename, h.mode) |
| 188 h.release() |
| 189 |
| 190 def error(self, msg='', context='', severity=logging.INFO, traceback=False): |
| 191 """Write the given ``msg`` to the error log. |
| 192 |
| 193 This is not just for errors! Applications may call this at any time |
| 194 to log application-specific information. |
| 195 |
| 196 If ``traceback`` is True, the traceback of the current exception |
| 197 (if any) will be appended to ``msg``. |
| 198 """ |
| 199 if traceback: |
| 200 msg += _cperror.format_exc() |
| 201 self.error_log.log(severity, ' '.join((self.time(), context, msg))) |
| 202 |
| 203 def __call__(self, *args, **kwargs): |
| 204 """An alias for ``error``.""" |
| 205 return self.error(*args, **kwargs) |
| 206 |
| 207 def access(self): |
| 208 """Write to the access log (in Apache/NCSA Combined Log format). |
| 209 |
| 210 See http://httpd.apache.org/docs/2.0/logs.html#combined for format |
| 211 details. |
| 212 |
| 213 CherryPy calls this automatically for you. Note there are no arguments; |
| 214 it collects the data itself from |
| 215 :class:`cherrypy.request<cherrypy._cprequest.Request>`. |
| 216 |
| 217 Like Apache started doing in 2.0.46, non-printable and other special |
| 218 characters in %r (and we expand that to all parts) are escaped using |
| 219 \\xhh sequences, where hh stands for the hexadecimal representation |
| 220 of the raw byte. Exceptions from this rule are " and \\, which are |
| 221 escaped by prepending a backslash, and all whitespace characters, |
| 222 which are written in their C-style notation (\\n, \\t, etc). |
| 223 """ |
| 224 request = cherrypy.serving.request |
| 225 remote = request.remote |
| 226 response = cherrypy.serving.response |
| 227 outheaders = response.headers |
| 228 inheaders = request.headers |
| 229 if response.output_status is None: |
| 230 status = "-" |
| 231 else: |
| 232 status = response.output_status.split(ntob(" "), 1)[0] |
| 233 if py3k: |
| 234 status = status.decode('ISO-8859-1') |
| 235 |
| 236 atoms = {'h': remote.name or remote.ip, |
| 237 'l': '-', |
| 238 'u': getattr(request, "login", None) or "-", |
| 239 't': self.time(), |
| 240 'r': request.request_line, |
| 241 's': status, |
| 242 'b': dict.get(outheaders, 'Content-Length', '') or "-", |
| 243 'f': dict.get(inheaders, 'Referer', ''), |
| 244 'a': dict.get(inheaders, 'User-Agent', ''), |
| 245 } |
| 246 if py3k: |
| 247 for k, v in atoms.items(): |
| 248 if not isinstance(v, str): |
| 249 v = str(v) |
| 250 v = v.replace('"', '\\"').encode('utf8') |
| 251 # Fortunately, repr(str) escapes unprintable chars, \n, \t, etc |
| 252 # and backslash for us. All we have to do is strip the quotes. |
| 253 v = repr(v)[2:-1] |
| 254 |
| 255 # in python 3.0 the repr of bytes (as returned by encode) |
| 256 # uses double \'s. But then the logger escapes them yet, again |
| 257 # resulting in quadruple slashes. Remove the extra one here. |
| 258 v = v.replace('\\\\', '\\') |
| 259 |
| 260 # Escape double-quote. |
| 261 atoms[k] = v |
| 262 |
| 263 try: |
| 264 self.access_log.log(logging.INFO, self.access_log_format.format(
**atoms)) |
| 265 except: |
| 266 self(traceback=True) |
| 267 else: |
| 268 for k, v in atoms.items(): |
| 269 if isinstance(v, unicode): |
| 270 v = v.encode('utf8') |
| 271 elif not isinstance(v, str): |
| 272 v = str(v) |
| 273 # Fortunately, repr(str) escapes unprintable chars, \n, \t, etc |
| 274 # and backslash for us. All we have to do is strip the quotes. |
| 275 v = repr(v)[1:-1] |
| 276 # Escape double-quote. |
| 277 atoms[k] = v.replace('"', '\\"') |
| 278 |
| 279 try: |
| 280 self.access_log.log(logging.INFO, self.access_log_format % atoms
) |
| 281 except: |
| 282 self(traceback=True) |
| 283 |
| 284 def time(self): |
| 285 """Return now() in Apache Common Log Format (no timezone).""" |
| 286 now = datetime.datetime.now() |
| 287 monthnames = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', |
| 288 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'] |
| 289 month = monthnames[now.month - 1].capitalize() |
| 290 return ('[%02d/%s/%04d:%02d:%02d:%02d]' % |
| 291 (now.day, month, now.year, now.hour, now.minute, now.second)) |
| 292 |
| 293 def _get_builtin_handler(self, log, key): |
| 294 for h in log.handlers: |
| 295 if getattr(h, "_cpbuiltin", None) == key: |
| 296 return h |
| 297 |
| 298 |
| 299 # ------------------------- Screen handlers ------------------------- # |
| 300 |
| 301 def _set_screen_handler(self, log, enable, stream=None): |
| 302 h = self._get_builtin_handler(log, "screen") |
| 303 if enable: |
| 304 if not h: |
| 305 if stream is None: |
| 306 stream=sys.stderr |
| 307 h = logging.StreamHandler(stream) |
| 308 h.setFormatter(logfmt) |
| 309 h._cpbuiltin = "screen" |
| 310 log.addHandler(h) |
| 311 elif h: |
| 312 log.handlers.remove(h) |
| 313 |
| 314 def _get_screen(self): |
| 315 h = self._get_builtin_handler |
| 316 has_h = h(self.error_log, "screen") or h(self.access_log, "screen") |
| 317 return bool(has_h) |
| 318 |
| 319 def _set_screen(self, newvalue): |
| 320 self._set_screen_handler(self.error_log, newvalue, stream=sys.stderr) |
| 321 self._set_screen_handler(self.access_log, newvalue, stream=sys.stdout) |
| 322 screen = property(_get_screen, _set_screen, |
| 323 doc="""Turn stderr/stdout logging on or off. |
| 324 |
| 325 If you set this to True, it'll add the appropriate StreamHandler for |
| 326 you. If you set it to False, it will remove the handler. |
| 327 """) |
| 328 |
| 329 # -------------------------- File handlers -------------------------- # |
| 330 |
| 331 def _add_builtin_file_handler(self, log, fname): |
| 332 h = logging.FileHandler(fname) |
| 333 h.setFormatter(logfmt) |
| 334 h._cpbuiltin = "file" |
| 335 log.addHandler(h) |
| 336 |
| 337 def _set_file_handler(self, log, filename): |
| 338 h = self._get_builtin_handler(log, "file") |
| 339 if filename: |
| 340 if h: |
| 341 if h.baseFilename != os.path.abspath(filename): |
| 342 h.close() |
| 343 log.handlers.remove(h) |
| 344 self._add_builtin_file_handler(log, filename) |
| 345 else: |
| 346 self._add_builtin_file_handler(log, filename) |
| 347 else: |
| 348 if h: |
| 349 h.close() |
| 350 log.handlers.remove(h) |
| 351 |
| 352 def _get_error_file(self): |
| 353 h = self._get_builtin_handler(self.error_log, "file") |
| 354 if h: |
| 355 return h.baseFilename |
| 356 return '' |
| 357 def _set_error_file(self, newvalue): |
| 358 self._set_file_handler(self.error_log, newvalue) |
| 359 error_file = property(_get_error_file, _set_error_file, |
| 360 doc="""The filename for self.error_log. |
| 361 |
| 362 If you set this to a string, it'll add the appropriate FileHandler for |
| 363 you. If you set it to ``None`` or ``''``, it will remove the handler. |
| 364 """) |
| 365 |
| 366 def _get_access_file(self): |
| 367 h = self._get_builtin_handler(self.access_log, "file") |
| 368 if h: |
| 369 return h.baseFilename |
| 370 return '' |
| 371 def _set_access_file(self, newvalue): |
| 372 self._set_file_handler(self.access_log, newvalue) |
| 373 access_file = property(_get_access_file, _set_access_file, |
| 374 doc="""The filename for self.access_log. |
| 375 |
| 376 If you set this to a string, it'll add the appropriate FileHandler for |
| 377 you. If you set it to ``None`` or ``''``, it will remove the handler. |
| 378 """) |
| 379 |
| 380 # ------------------------- WSGI handlers ------------------------- # |
| 381 |
| 382 def _set_wsgi_handler(self, log, enable): |
| 383 h = self._get_builtin_handler(log, "wsgi") |
| 384 if enable: |
| 385 if not h: |
| 386 h = WSGIErrorHandler() |
| 387 h.setFormatter(logfmt) |
| 388 h._cpbuiltin = "wsgi" |
| 389 log.addHandler(h) |
| 390 elif h: |
| 391 log.handlers.remove(h) |
| 392 |
| 393 def _get_wsgi(self): |
| 394 return bool(self._get_builtin_handler(self.error_log, "wsgi")) |
| 395 |
| 396 def _set_wsgi(self, newvalue): |
| 397 self._set_wsgi_handler(self.error_log, newvalue) |
| 398 wsgi = property(_get_wsgi, _set_wsgi, |
| 399 doc="""Write errors to wsgi.errors. |
| 400 |
| 401 If you set this to True, it'll add the appropriate |
| 402 :class:`WSGIErrorHandler<cherrypy._cplogging.WSGIErrorHandler>` for you |
| 403 (which writes errors to ``wsgi.errors``). |
| 404 If you set it to False, it will remove the handler. |
| 405 """) |
| 406 |
| 407 |
| 408 class WSGIErrorHandler(logging.Handler): |
| 409 "A handler class which writes logging records to environ['wsgi.errors']." |
| 410 |
| 411 def flush(self): |
| 412 """Flushes the stream.""" |
| 413 try: |
| 414 stream = cherrypy.serving.request.wsgi_environ.get('wsgi.errors') |
| 415 except (AttributeError, KeyError): |
| 416 pass |
| 417 else: |
| 418 stream.flush() |
| 419 |
| 420 def emit(self, record): |
| 421 """Emit a record.""" |
| 422 try: |
| 423 stream = cherrypy.serving.request.wsgi_environ.get('wsgi.errors') |
| 424 except (AttributeError, KeyError): |
| 425 pass |
| 426 else: |
| 427 try: |
| 428 msg = self.format(record) |
| 429 fs = "%s\n" |
| 430 import types |
| 431 if not hasattr(types, "UnicodeType"): #if no unicode support... |
| 432 stream.write(fs % msg) |
| 433 else: |
| 434 try: |
| 435 stream.write(fs % msg) |
| 436 except UnicodeError: |
| 437 stream.write(fs % msg.encode("UTF-8")) |
| 438 self.flush() |
| 439 except: |
| 440 self.handleError(record) |
OLD | NEW |