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 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, appid)) 170 self.access_log = logging.getLogger("%s.access.%s" % (logger_root, appid)) 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)
441