Download Install Tutorial Docs FAQ Tools WikiLicense Team IRC Planet Involvement Shop Book

root/trunk/cherrypy/_cplogging.py

Revision 1984 (checked in by fumanchu, 2 months ago)

Fix for #824 (_cplogging.LogManager?.access method not handling unicode in login names properly). While I was at it, I made the access log template configurable, moved the log tests from test_core into test_logging, and added a new logtest module.

  • Property svn:eol-style set to native
Line 
1 """CherryPy logging."""
2
3 import datetime
4 import logging
5 # Silence the no-handlers "warning" (stderr write!) in stdlib logging
6 logging.Logger.manager.emittedNoHandlerWarning = 1
7 logfmt = logging.Formatter("%(message)s")
8 import os
9 import rfc822
10 import sys
11
12 import cherrypy
13 from cherrypy import _cperror
14
15
16 class LogManager(object):
17    
18     appid = None
19     error_log = None
20     access_log = None
21     access_log_format = \
22         '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'
23    
24     def __init__(self, appid=None, logger_root="cherrypy"):
25         self.logger_root = logger_root
26         self.appid = appid
27         if appid is None:
28             self.error_log = logging.getLogger("%s.error" % logger_root)
29             self.access_log = logging.getLogger("%s.access" % logger_root)
30         else:
31             self.error_log = logging.getLogger("%s.error.%s" % (logger_root, appid))
32             self.access_log = logging.getLogger("%s.access.%s" % (logger_root, appid))
33         self.error_log.setLevel(logging.DEBUG)
34         self.access_log.setLevel(logging.INFO)
35         cherrypy.engine.subscribe('graceful', self.reopen_files)
36    
37     def reopen_files(self):
38         """Close and reopen all file handlers."""
39         for log in (self.error_log, self.access_log):
40             for h in log.handlers:
41                 if isinstance(h, logging.FileHandler):
42                     h.acquire()
43                     h.stream.close()
44                     h.stream = open(h.baseFilename, h.mode)
45                     h.release()
46    
47     def error(self, msg='', context='', severity=logging.INFO, traceback=False):
48         """Write to the error log.
49         
50         This is not just for errors! Applications may call this at any time
51         to log application-specific information.
52         """
53         if traceback:
54             msg += _cperror.format_exc()
55         self.error_log.log(severity, ' '.join((self.time(), context, msg)))
56    
57     def __call__(self, *args, **kwargs):
58         """Write to the error log.
59         
60         This is not just for errors! Applications may call this at any time
61         to log application-specific information.
62         """
63         return self.error(*args, **kwargs)
64    
65     def access(self):
66         """Write to the access log (in Apache/NCSA Combined Log format).
67         
68         Like Apache started doing in 2.0.46, non-printable and other special
69         characters in %r (and we expand that to all parts) are escaped using
70         \\xhh sequences, where hh stands for the hexadecimal representation
71         of the raw byte. Exceptions from this rule are " and \\, which are
72         escaped by prepending a backslash, and all whitespace characters,
73         which are written in their C-style notation (\\n, \\t, etc).
74         """
75         request = cherrypy.request
76         remote = request.remote
77         response = cherrypy.response
78         outheaders = response.headers
79         inheaders = request.headers
80        
81         atoms = {'h': remote.name or remote.ip,
82                  'l': '-',
83                  'u': getattr(request, "login", None) or "-",
84                  't': self.time(),
85                  'r': request.request_line,
86                  's': response.status.split(" ", 1)[0],
87                  'b': outheaders.get('Content-Length', '') or "-",
88                  'f': inheaders.get('Referer', ''),
89                  'a': inheaders.get('User-Agent', ''),
90                  }
91         for k, v in atoms.items():
92             if isinstance(v, unicode):
93                 v = v.encode('utf8')
94             elif not isinstance(v, str):
95                 v = str(v)
96             # Fortunately, repr(str) escapes unprintable chars, \n, \t, etc
97             # and backslash for us. All we have to do is strip the quotes.
98             v = repr(v)[1:-1]
99             # Escape double-quote.
100             atoms[k] = v.replace('"', '\\"')
101        
102         try:
103             self.access_log.log(logging.INFO, self.access_log_format % atoms)
104         except:
105             self(traceback=True)
106    
107     def time(self):
108         """Return now() in Apache Common Log Format (no timezone)."""
109         now = datetime.datetime.now()
110         month = rfc822._monthnames[now.month - 1].capitalize()
111         return ('[%02d/%s/%04d:%02d:%02d:%02d]' %
112                 (now.day, month, now.year, now.hour, now.minute, now.second))
113    
114     def _get_builtin_handler(self, log, key):
115         for h in log.handlers:
116             if getattr(h, "_cpbuiltin", None) == key:
117                 return h
118    
119    
120     # ------------------------- Screen handlers ------------------------- #
121    
122     def _set_screen_handler(self, log, enable, stream=None):
123         h = self._get_builtin_handler(log, "screen")
124         if enable:
125             if not h:
126                 if stream is None:
127                     stream=sys.stderr
128                 h = logging.StreamHandler(stream)
129                 h.setLevel(logging.DEBUG)
130                 h.setFormatter(logfmt)
131                 h._cpbuiltin = "screen"
132                 log.addHandler(h)
133         elif h:
134             log.handlers.remove(h)
135    
136     def _get_screen(self):
137         h = self._get_builtin_handler
138         has_h = h(self.error_log, "screen") or h(self.access_log, "screen")
139         return bool(has_h)
140    
141     def _set_screen(self, newvalue):
142         self._set_screen_handler(self.error_log, newvalue, stream=sys.stderr)
143         self._set_screen_handler(self.access_log, newvalue)
144     screen = property(_get_screen, _set_screen,
145                       doc="If True, error and access will print to stderr.")
146    
147    
148     # -------------------------- File handlers -------------------------- #
149    
150     def _add_builtin_file_handler(self, log, fname):
151         h = logging.FileHandler(fname)
152         h.setLevel(logging.DEBUG)
153         h.setFormatter(logfmt)
154         h._cpbuiltin = "file"
155         log.addHandler(h)
156    
157     def _set_file_handler(self, log, filename):
158         h = self._get_builtin_handler(log, "file")
159         if filename:
160             if h:
161                 if h.baseFilename != os.path.abspath(filename):
162                     h.close()
163                     log.handlers.remove(h)
164                     self._add_builtin_file_handler(log, filename)
165             else:
166                 self._add_builtin_file_handler(log, filename)
167         else:
168             if h:
169                 h.close()
170                 log.handlers.remove(h)
171    
172     def _get_error_file(self):
173         h = self._get_builtin_handler(self.error_log, "file")
174         if h:
175             return h.baseFilename
176         return ''
177     def _set_error_file(self, newvalue):
178         self._set_file_handler(self.error_log, newvalue)
179     error_file = property(_get_error_file, _set_error_file,
180                           doc="The filename for self.error_log.")
181    
182     def _get_access_file(self):
183         h = self._get_builtin_handler(self.access_log, "file")
184         if h:
185             return h.baseFilename
186         return ''
187     def _set_access_file(self, newvalue):
188         self._set_file_handler(self.access_log, newvalue)
189     access_file = property(_get_access_file, _set_access_file,
190                            doc="The filename for self.access_log.")
191    
192    
193     # ------------------------- WSGI handlers ------------------------- #
194    
195     def _set_wsgi_handler(self, log, enable):
196         h = self._get_builtin_handler(log, "wsgi")
197         if enable:
198             if not h:
199                 h = WSGIErrorHandler()
200                 h.setLevel(logging.DEBUG)
201                 h.setFormatter(logfmt)
202                 h._cpbuiltin = "wsgi"
203                 log.addHandler(h)
204         elif h:
205             log.handlers.remove(h)
206    
207     def _get_wsgi(self):
208         return bool(self._get_builtin_handler(self.error_log, "wsgi"))
209    
210     def _set_wsgi(self, newvalue):
211         self._set_wsgi_handler(self.error_log, newvalue)
212     wsgi = property(_get_wsgi, _set_wsgi,
213                       doc="If True, error messages will be sent to wsgi.errors.")
214
215
216 class WSGIErrorHandler(logging.Handler):
217     "A handler class which writes logging records to environ['wsgi.errors']."
218    
219     def flush(self):
220         """Flushes the stream."""
221         try:
222             stream = cherrypy.request.wsgi_environ.get('wsgi.errors')
223         except (AttributeError, KeyError):
224             pass
225         else:
226             stream.flush()
227    
228     def emit(self, record):
229         """Emit a record."""
230         try:
231             stream = cherrypy.request.wsgi_environ.get('wsgi.errors')
232         except (AttributeError, KeyError):
233             pass
234         else:
235             try:
236                 msg = self.format(record)
237                 fs = "%s\n"
238                 import types
239                 if not hasattr(types, "UnicodeType"): #if no unicode support...
240                     stream.write(fs % msg)
241                 else:
242                     try:
243                         stream.write(fs % msg)
244                     except UnicodeError:
245                         stream.write(fs % msg.encode("UTF-8"))
246                 self.flush()
247             except:
248                 self.handleError(record)
Note: See TracBrowser for help on using the browser.

Hosted by WebFaction

Log in as guest/cpguest to create tickets