Bundled cherrypy.
[smonitor.git] / monitor / cherrypy / _cplogging.py
blobd6ca979e9ff55a5226e0bee5e641e1614634d8d2
1 """
2 Simple config
3 =============
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.
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.
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.
22 Many events are automatically logged; to log your own application events, call
23 :func:`cherrypy.log`.
25 Architecture
26 ============
28 Separate scopes
29 ---------------
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['/']``.
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.
49 Errors vs. Access
50 -----------------
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).
59 Custom Handlers
60 ===============
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:
70 #python
71 log = app.log
73 # Remove the default FileHandlers if present.
74 log.error_file = ""
75 log.access_file = ""
77 maxBytes = getattr(log, "rot_maxBytes", 10000000)
78 backupCount = getattr(log, "rot_backupCount", 1000)
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)
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)
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).
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
110 import cherrypy
111 from cherrypy import _cperror
114 class LogManager(object):
115 """An object to assist both simple and advanced logging.
117 ``cherrypy.log`` is an instance of this class.
120 appid = None
121 """The id() of the Application object which owns this log manager. If this
122 is a global log manager, appid is None."""
124 error_log = None
125 """The actual :class:`logging.Logger` instance for error messages."""
127 access_log = None
128 """The actual :class:`logging.Logger` instance for access messages."""
130 access_log_format = \
131 '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'
133 logger_root = None
134 """The "top-level" logger name.
136 This string will be used as the first segment in the Logger names.
137 The default is "cherrypy", for example, in which case the Logger names
138 will be of the form::
140 cherrypy.error.<appid>
141 cherrypy.access.<appid>
144 def __init__(self, appid=None, logger_root="cherrypy"):
145 self.logger_root = logger_root
146 self.appid = appid
147 if appid is None:
148 self.error_log = logging.getLogger("%s.error" % logger_root)
149 self.access_log = logging.getLogger("%s.access" % logger_root)
150 else:
151 self.error_log = logging.getLogger("%s.error.%s" % (logger_root, appid))
152 self.access_log = logging.getLogger("%s.access.%s" % (logger_root, appid))
153 self.error_log.setLevel(logging.INFO)
154 self.access_log.setLevel(logging.INFO)
155 cherrypy.engine.subscribe('graceful', self.reopen_files)
157 def reopen_files(self):
158 """Close and reopen all file handlers."""
159 for log in (self.error_log, self.access_log):
160 for h in log.handlers:
161 if isinstance(h, logging.FileHandler):
162 h.acquire()
163 h.stream.close()
164 h.stream = open(h.baseFilename, h.mode)
165 h.release()
167 def error(self, msg='', context='', severity=logging.INFO, traceback=False):
168 """Write the given ``msg`` to the error log.
170 This is not just for errors! Applications may call this at any time
171 to log application-specific information.
173 If ``traceback`` is True, the traceback of the current exception
174 (if any) will be appended to ``msg``.
176 if traceback:
177 msg += _cperror.format_exc()
178 self.error_log.log(severity, ' '.join((self.time(), context, msg)))
180 def __call__(self, *args, **kwargs):
181 """An alias for ``error``."""
182 return self.error(*args, **kwargs)
184 def access(self):
185 """Write to the access log (in Apache/NCSA Combined Log format).
187 See http://httpd.apache.org/docs/2.0/logs.html#combined for format
188 details.
190 CherryPy calls this automatically for you. Note there are no arguments;
191 it collects the data itself from
192 :class:`cherrypy.request<cherrypy._cprequest.Request>`.
194 Like Apache started doing in 2.0.46, non-printable and other special
195 characters in %r (and we expand that to all parts) are escaped using
196 \\xhh sequences, where hh stands for the hexadecimal representation
197 of the raw byte. Exceptions from this rule are " and \\, which are
198 escaped by prepending a backslash, and all whitespace characters,
199 which are written in their C-style notation (\\n, \\t, etc).
201 request = cherrypy.serving.request
202 remote = request.remote
203 response = cherrypy.serving.response
204 outheaders = response.headers
205 inheaders = request.headers
206 if response.output_status is None:
207 status = "-"
208 else:
209 status = response.output_status.split(" ", 1)[0]
211 atoms = {'h': remote.name or remote.ip,
212 'l': '-',
213 'u': getattr(request, "login", None) or "-",
214 't': self.time(),
215 'r': request.request_line,
216 's': status,
217 'b': dict.get(outheaders, 'Content-Length', '') or "-",
218 'f': dict.get(inheaders, 'Referer', ''),
219 'a': dict.get(inheaders, 'User-Agent', ''),
221 for k, v in atoms.items():
222 if isinstance(v, unicode):
223 v = v.encode('utf8')
224 elif not isinstance(v, str):
225 v = str(v)
226 # Fortunately, repr(str) escapes unprintable chars, \n, \t, etc
227 # and backslash for us. All we have to do is strip the quotes.
228 v = repr(v)[1:-1]
229 # Escape double-quote.
230 atoms[k] = v.replace('"', '\\"')
232 try:
233 self.access_log.log(logging.INFO, self.access_log_format % atoms)
234 except:
235 self(traceback=True)
237 def time(self):
238 """Return now() in Apache Common Log Format (no timezone)."""
239 now = datetime.datetime.now()
240 monthnames = ['jan', 'feb', 'mar', 'apr', 'may', 'jun',
241 'jul', 'aug', 'sep', 'oct', 'nov', 'dec']
242 month = monthnames[now.month - 1].capitalize()
243 return ('[%02d/%s/%04d:%02d:%02d:%02d]' %
244 (now.day, month, now.year, now.hour, now.minute, now.second))
246 def _get_builtin_handler(self, log, key):
247 for h in log.handlers:
248 if getattr(h, "_cpbuiltin", None) == key:
249 return h
252 # ------------------------- Screen handlers ------------------------- #
254 def _set_screen_handler(self, log, enable, stream=None):
255 h = self._get_builtin_handler(log, "screen")
256 if enable:
257 if not h:
258 if stream is None:
259 stream=sys.stderr
260 h = logging.StreamHandler(stream)
261 h.setFormatter(logfmt)
262 h._cpbuiltin = "screen"
263 log.addHandler(h)
264 elif h:
265 log.handlers.remove(h)
267 def _get_screen(self):
268 h = self._get_builtin_handler
269 has_h = h(self.error_log, "screen") or h(self.access_log, "screen")
270 return bool(has_h)
272 def _set_screen(self, newvalue):
273 self._set_screen_handler(self.error_log, newvalue, stream=sys.stderr)
274 self._set_screen_handler(self.access_log, newvalue, stream=sys.stdout)
275 screen = property(_get_screen, _set_screen,
276 doc="""Turn stderr/stdout logging on or off.
278 If you set this to True, it'll add the appropriate StreamHandler for
279 you. If you set it to False, it will remove the handler.
280 """)
282 # -------------------------- File handlers -------------------------- #
284 def _add_builtin_file_handler(self, log, fname):
285 h = logging.FileHandler(fname)
286 h.setFormatter(logfmt)
287 h._cpbuiltin = "file"
288 log.addHandler(h)
290 def _set_file_handler(self, log, filename):
291 h = self._get_builtin_handler(log, "file")
292 if filename:
293 if h:
294 if h.baseFilename != os.path.abspath(filename):
295 h.close()
296 log.handlers.remove(h)
297 self._add_builtin_file_handler(log, filename)
298 else:
299 self._add_builtin_file_handler(log, filename)
300 else:
301 if h:
302 h.close()
303 log.handlers.remove(h)
305 def _get_error_file(self):
306 h = self._get_builtin_handler(self.error_log, "file")
307 if h:
308 return h.baseFilename
309 return ''
310 def _set_error_file(self, newvalue):
311 self._set_file_handler(self.error_log, newvalue)
312 error_file = property(_get_error_file, _set_error_file,
313 doc="""The filename for self.error_log.
315 If you set this to a string, it'll add the appropriate FileHandler for
316 you. If you set it to ``None`` or ``''``, it will remove the handler.
317 """)
319 def _get_access_file(self):
320 h = self._get_builtin_handler(self.access_log, "file")
321 if h:
322 return h.baseFilename
323 return ''
324 def _set_access_file(self, newvalue):
325 self._set_file_handler(self.access_log, newvalue)
326 access_file = property(_get_access_file, _set_access_file,
327 doc="""The filename for self.access_log.
329 If you set this to a string, it'll add the appropriate FileHandler for
330 you. If you set it to ``None`` or ``''``, it will remove the handler.
331 """)
333 # ------------------------- WSGI handlers ------------------------- #
335 def _set_wsgi_handler(self, log, enable):
336 h = self._get_builtin_handler(log, "wsgi")
337 if enable:
338 if not h:
339 h = WSGIErrorHandler()
340 h.setFormatter(logfmt)
341 h._cpbuiltin = "wsgi"
342 log.addHandler(h)
343 elif h:
344 log.handlers.remove(h)
346 def _get_wsgi(self):
347 return bool(self._get_builtin_handler(self.error_log, "wsgi"))
349 def _set_wsgi(self, newvalue):
350 self._set_wsgi_handler(self.error_log, newvalue)
351 wsgi = property(_get_wsgi, _set_wsgi,
352 doc="""Write errors to wsgi.errors.
354 If you set this to True, it'll add the appropriate
355 :class:`WSGIErrorHandler<cherrypy._cplogging.WSGIErrorHandler>` for you
356 (which writes errors to ``wsgi.errors``).
357 If you set it to False, it will remove the handler.
358 """)
361 class WSGIErrorHandler(logging.Handler):
362 "A handler class which writes logging records to environ['wsgi.errors']."
364 def flush(self):
365 """Flushes the stream."""
366 try:
367 stream = cherrypy.serving.request.wsgi_environ.get('wsgi.errors')
368 except (AttributeError, KeyError):
369 pass
370 else:
371 stream.flush()
373 def emit(self, record):
374 """Emit a record."""
375 try:
376 stream = cherrypy.serving.request.wsgi_environ.get('wsgi.errors')
377 except (AttributeError, KeyError):
378 pass
379 else:
380 try:
381 msg = self.format(record)
382 fs = "%s\n"
383 import types
384 if not hasattr(types, "UnicodeType"): #if no unicode support...
385 stream.write(fs % msg)
386 else:
387 try:
388 stream.write(fs % msg)
389 except UnicodeError:
390 stream.write(fs % msg.encode("UTF-8"))
391 self.flush()
392 except:
393 self.handleError(record)