Package cherrypy :: Module _cplogging
[hide private]
[frames] | no frames]

Source Code for Module cherrypy._cplogging

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