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
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
116 """A no-op logging handler to silence the logging.lastResort handler."""
117
120
121 - def emit(self, record):
123
126
127
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"):
179
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
206
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
252
253 v = repr(v)[2:-1]
254
255
256
257
258 v = v.replace('\\\\', '\\')
259
260
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
274
275 v = repr(v)[1:-1]
276
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
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
294 for h in log.handlers:
295 if getattr(h, "_cpbuiltin", None) == key:
296 return h
297
298
299
300
313
318
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
330
332 h = logging.FileHandler(fname)
333 h.setFormatter(logfmt)
334 h._cpbuiltin = "file"
335 log.addHandler(h)
336
351
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
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
381
392
395
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
409 "A handler class which writes logging records to environ['wsgi.errors']."
410
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"):
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