mirror of
https://github.com/SickGear/SickGear.git
synced 2024-12-11 05:33:37 +00:00
250 lines
9 KiB
Python
250 lines
9 KiB
Python
"""CherryPy logging."""
|
|
|
|
import datetime
|
|
import logging
|
|
# Silence the no-handlers "warning" (stderr write!) in stdlib logging
|
|
logging.Logger.manager.emittedNoHandlerWarning = 1
|
|
logfmt = logging.Formatter("%(message)s")
|
|
import os
|
|
import sys
|
|
|
|
import cherrypy
|
|
from cherrypy import _cperror
|
|
|
|
|
|
class LogManager(object):
|
|
|
|
appid = None
|
|
error_log = None
|
|
access_log = None
|
|
access_log_format = \
|
|
'%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'
|
|
|
|
def __init__(self, appid=None, logger_root="cherrypy"):
|
|
self.logger_root = logger_root
|
|
self.appid = appid
|
|
if appid is None:
|
|
self.error_log = logging.getLogger("%s.error" % logger_root)
|
|
self.access_log = logging.getLogger("%s.access" % logger_root)
|
|
else:
|
|
self.error_log = logging.getLogger("%s.error.%s" % (logger_root, appid))
|
|
self.access_log = logging.getLogger("%s.access.%s" % (logger_root, appid))
|
|
self.error_log.setLevel(logging.INFO)
|
|
self.access_log.setLevel(logging.INFO)
|
|
cherrypy.engine.subscribe('graceful', self.reopen_files)
|
|
|
|
def reopen_files(self):
|
|
"""Close and reopen all file handlers."""
|
|
for log in (self.error_log, self.access_log):
|
|
for h in log.handlers:
|
|
if isinstance(h, logging.FileHandler):
|
|
h.acquire()
|
|
h.stream.close()
|
|
h.stream = open(h.baseFilename, h.mode)
|
|
h.release()
|
|
|
|
def error(self, msg='', context='', severity=logging.INFO, traceback=False):
|
|
"""Write to the error log.
|
|
|
|
This is not just for errors! Applications may call this at any time
|
|
to log application-specific information.
|
|
"""
|
|
if traceback:
|
|
msg += _cperror.format_exc()
|
|
self.error_log.log(severity, ' '.join((self.time(), context, msg)))
|
|
|
|
def __call__(self, *args, **kwargs):
|
|
"""Write to the error log.
|
|
|
|
This is not just for errors! Applications may call this at any time
|
|
to log application-specific information.
|
|
"""
|
|
return self.error(*args, **kwargs)
|
|
|
|
def access(self):
|
|
"""Write to the access log (in Apache/NCSA Combined Log format).
|
|
|
|
Like Apache started doing in 2.0.46, non-printable and other special
|
|
characters in %r (and we expand that to all parts) are escaped using
|
|
\\xhh sequences, where hh stands for the hexadecimal representation
|
|
of the raw byte. Exceptions from this rule are " and \\, which are
|
|
escaped by prepending a backslash, and all whitespace characters,
|
|
which are written in their C-style notation (\\n, \\t, etc).
|
|
"""
|
|
request = cherrypy.serving.request
|
|
remote = request.remote
|
|
response = cherrypy.serving.response
|
|
outheaders = response.headers
|
|
inheaders = request.headers
|
|
if response.output_status is None:
|
|
status = "-"
|
|
else:
|
|
status = response.output_status.split(" ", 1)[0]
|
|
|
|
atoms = {'h': remote.name or remote.ip,
|
|
'l': '-',
|
|
'u': getattr(request, "login", None) or "-",
|
|
't': self.time(),
|
|
'r': request.request_line,
|
|
's': status,
|
|
'b': dict.get(outheaders, 'Content-Length', '') or "-",
|
|
'f': dict.get(inheaders, 'Referer', ''),
|
|
'a': dict.get(inheaders, 'User-Agent', ''),
|
|
}
|
|
for k, v in atoms.items():
|
|
if isinstance(v, unicode):
|
|
v = v.encode('utf8')
|
|
elif not isinstance(v, str):
|
|
v = str(v)
|
|
# Fortunately, repr(str) escapes unprintable chars, \n, \t, etc
|
|
# and backslash for us. All we have to do is strip the quotes.
|
|
v = repr(v)[1:-1]
|
|
# Escape double-quote.
|
|
atoms[k] = v.replace('"', '\\"')
|
|
|
|
try:
|
|
self.access_log.log(logging.INFO, self.access_log_format % atoms)
|
|
except:
|
|
self(traceback=True)
|
|
|
|
def time(self):
|
|
"""Return now() in Apache Common Log Format (no timezone)."""
|
|
now = datetime.datetime.now()
|
|
monthnames = ['jan', 'feb', 'mar', 'apr', 'may', 'jun',
|
|
'jul', 'aug', 'sep', 'oct', 'nov', 'dec']
|
|
month = monthnames[now.month - 1].capitalize()
|
|
return ('[%02d/%s/%04d:%02d:%02d:%02d]' %
|
|
(now.day, month, now.year, now.hour, now.minute, now.second))
|
|
|
|
def _get_builtin_handler(self, log, key):
|
|
for h in log.handlers:
|
|
if getattr(h, "_cpbuiltin", None) == key:
|
|
return h
|
|
|
|
|
|
# ------------------------- Screen handlers ------------------------- #
|
|
|
|
def _set_screen_handler(self, log, enable, stream=None):
|
|
h = self._get_builtin_handler(log, "screen")
|
|
if enable:
|
|
if not h:
|
|
if stream is None:
|
|
stream = sys.stderr
|
|
h = logging.StreamHandler(stream)
|
|
h.setFormatter(logfmt)
|
|
h._cpbuiltin = "screen"
|
|
log.addHandler(h)
|
|
elif h:
|
|
log.handlers.remove(h)
|
|
|
|
def _get_screen(self):
|
|
h = self._get_builtin_handler
|
|
has_h = h(self.error_log, "screen") or h(self.access_log, "screen")
|
|
return bool(has_h)
|
|
|
|
def _set_screen(self, newvalue):
|
|
self._set_screen_handler(self.error_log, newvalue, stream=sys.stderr)
|
|
self._set_screen_handler(self.access_log, newvalue, stream=sys.stdout)
|
|
screen = property(_get_screen, _set_screen,
|
|
doc="If True, error and access will print to stderr.")
|
|
|
|
|
|
# -------------------------- File handlers -------------------------- #
|
|
|
|
def _add_builtin_file_handler(self, log, fname):
|
|
h = logging.FileHandler(fname)
|
|
h.setFormatter(logfmt)
|
|
h._cpbuiltin = "file"
|
|
log.addHandler(h)
|
|
|
|
def _set_file_handler(self, log, filename):
|
|
h = self._get_builtin_handler(log, "file")
|
|
if filename:
|
|
if h:
|
|
if h.baseFilename != os.path.abspath(filename):
|
|
h.close()
|
|
log.handlers.remove(h)
|
|
self._add_builtin_file_handler(log, filename)
|
|
else:
|
|
self._add_builtin_file_handler(log, filename)
|
|
else:
|
|
if h:
|
|
h.close()
|
|
log.handlers.remove(h)
|
|
|
|
def _get_error_file(self):
|
|
h = self._get_builtin_handler(self.error_log, "file")
|
|
if h:
|
|
return h.baseFilename
|
|
return ''
|
|
def _set_error_file(self, newvalue):
|
|
self._set_file_handler(self.error_log, newvalue)
|
|
error_file = property(_get_error_file, _set_error_file,
|
|
doc="The filename for self.error_log.")
|
|
|
|
def _get_access_file(self):
|
|
h = self._get_builtin_handler(self.access_log, "file")
|
|
if h:
|
|
return h.baseFilename
|
|
return ''
|
|
def _set_access_file(self, newvalue):
|
|
self._set_file_handler(self.access_log, newvalue)
|
|
access_file = property(_get_access_file, _set_access_file,
|
|
doc="The filename for self.access_log.")
|
|
|
|
|
|
# ------------------------- WSGI handlers ------------------------- #
|
|
|
|
def _set_wsgi_handler(self, log, enable):
|
|
h = self._get_builtin_handler(log, "wsgi")
|
|
if enable:
|
|
if not h:
|
|
h = WSGIErrorHandler()
|
|
h.setFormatter(logfmt)
|
|
h._cpbuiltin = "wsgi"
|
|
log.addHandler(h)
|
|
elif h:
|
|
log.handlers.remove(h)
|
|
|
|
def _get_wsgi(self):
|
|
return bool(self._get_builtin_handler(self.error_log, "wsgi"))
|
|
|
|
def _set_wsgi(self, newvalue):
|
|
self._set_wsgi_handler(self.error_log, newvalue)
|
|
wsgi = property(_get_wsgi, _set_wsgi,
|
|
doc="If True, error messages will be sent to wsgi.errors.")
|
|
|
|
|
|
class WSGIErrorHandler(logging.Handler):
|
|
"A handler class which writes logging records to environ['wsgi.errors']."
|
|
|
|
def flush(self):
|
|
"""Flushes the stream."""
|
|
try:
|
|
stream = cherrypy.serving.request.wsgi_environ.get('wsgi.errors')
|
|
except (AttributeError, KeyError):
|
|
pass
|
|
else:
|
|
stream.flush()
|
|
|
|
def emit(self, record):
|
|
"""Emit a record."""
|
|
try:
|
|
stream = cherrypy.serving.request.wsgi_environ.get('wsgi.errors')
|
|
except (AttributeError, KeyError):
|
|
pass
|
|
else:
|
|
try:
|
|
msg = self.format(record)
|
|
fs = "%s\n"
|
|
import types
|
|
if not hasattr(types, "UnicodeType"): #if no unicode support...
|
|
stream.write(fs % msg)
|
|
else:
|
|
try:
|
|
stream.write(fs % msg)
|
|
except UnicodeError:
|
|
stream.write(fs % msg.encode("UTF-8"))
|
|
self.flush()
|
|
except:
|
|
self.handleError(record)
|