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 If you are logging the access log and error log to the same source, then there
60 is a possibility that a specially crafted error message may replicate an access
61 log message as described in CWE-117. In this case it is the application
62 developer's responsibility to manually escape data before using CherryPy's log()
63 functionality, or they may create an application that is vulnerable to CWE-117.
64 This would be achieved by using a custom handler escape any special characters,
65 and attached as described below.
66
67 Custom Handlers
68 ===============
69
70 The simple settings above work by manipulating Python's standard :mod:`logging`
71 module. So when you need something more complex, the full power of the standard
72 module is yours to exploit. You can borrow or create custom handlers, formats,
73 filters, and much more. Here's an example that skips the standard FileHandler
74 and uses a RotatingFileHandler instead:
75
76 ::
77
78 #python
79 log = app.log
80
81 # Remove the default FileHandlers if present.
82 log.error_file = ""
83 log.access_file = ""
84
85 maxBytes = getattr(log, "rot_maxBytes", 10000000)
86 backupCount = getattr(log, "rot_backupCount", 1000)
87
88 # Make a new RotatingFileHandler for the error log.
89 fname = getattr(log, "rot_error_file", "error.log")
90 h = handlers.RotatingFileHandler(fname, 'a', maxBytes, backupCount)
91 h.setLevel(DEBUG)
92 h.setFormatter(_cplogging.logfmt)
93 log.error_log.addHandler(h)
94
95 # Make a new RotatingFileHandler for the access log.
96 fname = getattr(log, "rot_access_file", "access.log")
97 h = handlers.RotatingFileHandler(fname, 'a', maxBytes, backupCount)
98 h.setLevel(DEBUG)
99 h.setFormatter(_cplogging.logfmt)
100 log.access_log.addHandler(h)
101
102
103 The ``rot_*`` attributes are pulled straight from the application log object.
104 Since "log.*" config entries simply set attributes on the log object, you can
105 add custom attributes to your heart's content. Note that these handlers are
106 used ''instead'' of the default, simple handlers outlined above (so don't set
107 the "log.error_file" config entry, for example).
108 """
109
110 import datetime
111 import logging
112
113 logging.Logger.manager.emittedNoHandlerWarning = 1
114 logfmt = logging.Formatter("%(message)s")
115 import os
116 import sys
117
118 import cherrypy
119 from cherrypy import _cperror
120 from cherrypy._cpcompat import ntob, py3k
121
122
124
125 """A no-op logging handler to silence the logging.lastResort handler."""
126
129
130 - def emit(self, record):
132
135
136
138
139 """An object to assist both simple and advanced logging.
140
141 ``cherrypy.log`` is an instance of this class.
142 """
143
144 appid = None
145 """The id() of the Application object which owns this log manager. If this
146 is a global log manager, appid is None."""
147
148 error_log = None
149 """The actual :class:`logging.Logger` instance for error messages."""
150
151 access_log = None
152 """The actual :class:`logging.Logger` instance for access messages."""
153
154 if py3k:
155 access_log_format = \
156 '{h} {l} {u} {t} "{r}" {s} {b} "{f}" "{a}"'
157 else:
158 access_log_format = \
159 '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'
160
161 logger_root = None
162 """The "top-level" logger name.
163
164 This string will be used as the first segment in the Logger names.
165 The default is "cherrypy", for example, in which case the Logger names
166 will be of the form::
167
168 cherrypy.error.<appid>
169 cherrypy.access.<appid>
170 """
171
172 - def __init__(self, appid=None, logger_root="cherrypy"):
191
201
202 - def error(self, msg='', context='', severity=logging.INFO,
203 traceback=False):
204 """Write the given ``msg`` to the error log.
205
206 This is not just for errors! Applications may call this at any time
207 to log application-specific information.
208
209 If ``traceback`` is True, the traceback of the current exception
210 (if any) will be appended to ``msg``.
211 """
212 if traceback:
213 msg += _cperror.format_exc()
214 self.error_log.log(severity, ' '.join((self.time(), context, msg)))
215
219
221 """Write to the access log (in Apache/NCSA Combined Log format).
222
223 See the
224 `apache documentation <http://httpd.apache.org/docs/current/logs.html#combined>`_
225 for format details.
226
227 CherryPy calls this automatically for you. Note there are no arguments;
228 it collects the data itself from
229 :class:`cherrypy.request<cherrypy._cprequest.Request>`.
230
231 Like Apache started doing in 2.0.46, non-printable and other special
232 characters in %r (and we expand that to all parts) are escaped using
233 \\xhh sequences, where hh stands for the hexadecimal representation
234 of the raw byte. Exceptions from this rule are " and \\, which are
235 escaped by prepending a backslash, and all whitespace characters,
236 which are written in their C-style notation (\\n, \\t, etc).
237 """
238 request = cherrypy.serving.request
239 remote = request.remote
240 response = cherrypy.serving.response
241 outheaders = response.headers
242 inheaders = request.headers
243 if response.output_status is None:
244 status = "-"
245 else:
246 status = response.output_status.split(ntob(" "), 1)[0]
247 if py3k:
248 status = status.decode('ISO-8859-1')
249
250 atoms = {'h': remote.name or remote.ip,
251 'l': '-',
252 'u': getattr(request, "login", None) or "-",
253 't': self.time(),
254 'r': request.request_line,
255 's': status,
256 'b': dict.get(outheaders, 'Content-Length', '') or "-",
257 'f': dict.get(inheaders, 'Referer', ''),
258 'a': dict.get(inheaders, 'User-Agent', ''),
259 }
260 if py3k:
261 for k, v in atoms.items():
262 if not isinstance(v, str):
263 v = str(v)
264 v = v.replace('"', '\\"').encode('utf8')
265
266
267 v = repr(v)[2:-1]
268
269
270
271
272 v = v.replace('\\\\', '\\')
273
274
275 atoms[k] = v
276
277 try:
278 self.access_log.log(
279 logging.INFO, self.access_log_format.format(**atoms))
280 except:
281 self(traceback=True)
282 else:
283 for k, v in atoms.items():
284 if isinstance(v, unicode):
285 v = v.encode('utf8')
286 elif not isinstance(v, str):
287 v = str(v)
288
289
290 v = repr(v)[1:-1]
291
292 atoms[k] = v.replace('"', '\\"')
293
294 try:
295 self.access_log.log(
296 logging.INFO, self.access_log_format % atoms)
297 except:
298 self(traceback=True)
299
301 """Return now() in Apache Common Log Format (no timezone)."""
302 now = datetime.datetime.now()
303 monthnames = ['jan', 'feb', 'mar', 'apr', 'may', 'jun',
304 'jul', 'aug', 'sep', 'oct', 'nov', 'dec']
305 month = monthnames[now.month - 1].capitalize()
306 return ('[%02d/%s/%04d:%02d:%02d:%02d]' %
307 (now.day, month, now.year, now.hour, now.minute, now.second))
308
310 for h in log.handlers:
311 if getattr(h, "_cpbuiltin", None) == key:
312 return h
313
314
327
332
336 screen = property(_get_screen, _set_screen,
337 doc="""Turn stderr/stdout logging on or off.
338
339 If you set this to True, it'll add the appropriate StreamHandler for
340 you. If you set it to False, it will remove the handler.
341 """)
342
343
344
346 h = logging.FileHandler(fname)
347 h.setFormatter(logfmt)
348 h._cpbuiltin = "file"
349 log.addHandler(h)
350
365
371
374 error_file = property(_get_error_file, _set_error_file,
375 doc="""The filename for self.error_log.
376
377 If you set this to a string, it'll add the appropriate FileHandler for
378 you. If you set it to ``None`` or ``''``, it will remove the handler.
379 """)
380
386
389 access_file = property(_get_access_file, _set_access_file,
390 doc="""The filename for self.access_log.
391
392 If you set this to a string, it'll add the appropriate FileHandler for
393 you. If you set it to ``None`` or ``''``, it will remove the handler.
394 """)
395
396
397
408
411
414 wsgi = property(_get_wsgi, _set_wsgi,
415 doc="""Write errors to wsgi.errors.
416
417 If you set this to True, it'll add the appropriate
418 :class:`WSGIErrorHandler<cherrypy._cplogging.WSGIErrorHandler>` for you
419 (which writes errors to ``wsgi.errors``).
420 If you set it to False, it will remove the handler.
421 """)
422
423
425
426 "A handler class which writes logging records to environ['wsgi.errors']."
427
436
437 - def emit(self, record):
438 """Emit a record."""
439 try:
440 stream = cherrypy.serving.request.wsgi_environ.get('wsgi.errors')
441 except (AttributeError, KeyError):
442 pass
443 else:
444 try:
445 msg = self.format(record)
446 fs = "%s\n"
447 import types
448
449 if not hasattr(types, "UnicodeType"):
450 stream.write(fs % msg)
451 else:
452 try:
453 stream.write(fs % msg)
454 except UnicodeError:
455 stream.write(fs % msg.encode("UTF-8"))
456 self.flush()
457 except:
458 self.handleError(record)
459