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
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
117
118 """A no-op logging handler to silence the logging.lastResort handler."""
119
122
123 - def emit(self, record):
125
128
129
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"):
184
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
212
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
259
260 v = repr(v)[2:-1]
261
262
263
264
265 v = v.replace('\\\\', '\\')
266
267
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
282
283 v = repr(v)[1:-1]
284
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
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
303 for h in log.handlers:
304 if getattr(h, "_cpbuiltin", None) == key:
305 return h
306
307
320
325
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
337
339 h = logging.FileHandler(fname)
340 h.setFormatter(logfmt)
341 h._cpbuiltin = "file"
342 log.addHandler(h)
343
358
364
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
379
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
390
401
404
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
418
419 "A handler class which writes logging records to environ['wsgi.errors']."
420
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
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