Use wsgi.errors to for errors

This commit is contained in:
Unrud 2018-08-16 07:59:56 +02:00
parent 54b9995e22
commit 24815255be
3 changed files with 111 additions and 42 deletions

View File

@ -204,19 +204,23 @@ class ThreadedHTTPSServer(socketserver.ThreadingMixIn, HTTPSServer):
return super().process_request_thread(request, client_address) return super().process_request_thread(request, client_address)
class ServerHandler(wsgiref.simple_server.ServerHandler):
def log_exception(self, exc_info):
logger.error("An exception occurred during request: %s",
exc_info[1], exc_info=exc_info)
class RequestHandler(wsgiref.simple_server.WSGIRequestHandler): class RequestHandler(wsgiref.simple_server.WSGIRequestHandler):
"""HTTP requests handler.""" """HTTP requests handler."""
def __init__(self, *args, **kwargs): def log_request(self, code="-", size="-"):
# Store exception for logging """Disable request logging."""
self.error_stream = io.StringIO()
super().__init__(*args, **kwargs)
def get_stderr(self): def log_error(self, format, *args):
return self.error_stream msg = format % args
logger.error("An error occurred during request: %s" % msg)
def log_message(self, *args, **kwargs):
"""Disable inner logging management."""
def get_environ(self): def get_environ(self):
env = super().get_environ() env = super().get_environ()
@ -228,12 +232,24 @@ class RequestHandler(wsgiref.simple_server.WSGIRequestHandler):
return env return env
def handle(self): def handle(self):
super().handle() """Copy of WSGIRequestHandler.handle with different ServerHandler"""
# Log exception
error = self.error_stream.getvalue().strip("\n") self.raw_requestline = self.rfile.readline(65537)
if error: if len(self.raw_requestline) > 65536:
logger.error( self.requestline = ''
"An unhandled exception occurred during request:\n%s" % error) self.request_version = ''
self.command = ''
self.send_error(414)
return
if not self.parse_request():
return
handler = ServerHandler(
self.rfile, self.wfile, self.get_stderr(), self.get_environ()
)
handler.request_handler = self
handler.run(self.server.get_app())
class Application: class Application:
@ -323,6 +339,7 @@ class Application:
return read_allowed_items, write_allowed_items return read_allowed_items, write_allowed_items
def __call__(self, environ, start_response): def __call__(self, environ, start_response):
with log.register_stream(environ["wsgi.errors"]):
try: try:
status, headers, answers = self._handle_request(environ) status, headers, answers = self._handle_request(environ)
except Exception as e: except Exception as e:
@ -340,7 +357,8 @@ class Application:
answer = answer.encode("ascii") answer = answer.encode("ascii")
status = "%d %s" % ( status = "%d %s" % (
status, client.responses.get(status, "Unknown")) status, client.responses.get(status, "Unknown"))
headers = [("Content-Length", str(len(answer)))] + list(headers) headers = [
("Content-Length", str(len(answer)))] + list(headers)
answers = [answer] answers = [answer]
start_response(status, headers) start_response(status, headers)
return answers return answers
@ -990,12 +1008,13 @@ _application_config_path = None
_application_lock = threading.Lock() _application_lock = threading.Lock()
def _init_application(config_path): def _init_application(config_path, wsgi_errors):
global _application, _application_config_path global _application, _application_config_path
with _application_lock: with _application_lock:
if _application is not None: if _application is not None:
return return
log.setup() log.setup()
with log.register_stream(wsgi_errors):
_application_config_path = config_path _application_config_path = config_path
configuration = config.load([config_path] if config_path else [], configuration = config.load([config_path] if config_path else [],
ignore_missing_paths=False) ignore_missing_paths=False)
@ -1007,7 +1026,7 @@ def application(environ, start_response):
config_path = environ.get("RADICALE_CONFIG", config_path = environ.get("RADICALE_CONFIG",
os.environ.get("RADICALE_CONFIG")) os.environ.get("RADICALE_CONFIG"))
if _application is None: if _application is None:
_init_application(config_path) _init_application(config_path, environ["wsgi.errors"])
if _application_config_path != config_path: if _application_config_path != config_path:
raise ValueError("RADICALE_CONFIG must not change: %s != %s" % raise ValueError("RADICALE_CONFIG must not change: %s != %s" %
(repr(config_path), repr(_application_config_path))) (repr(config_path), repr(_application_config_path)))

View File

@ -22,6 +22,7 @@ http://docs.python.org/library/logging.config.html
""" """
import contextlib
import logging import logging
import sys import sys
import threading import threading
@ -43,16 +44,64 @@ class RemoveTracebackFilter(logging.Filter):
removeTracebackFilter = RemoveTracebackFilter() removeTracebackFilter = RemoveTracebackFilter()
class ThreadStreamsHandler(logging.Handler):
terminator = "\n"
def __init__(self, fallback_stream, fallback_handler):
super().__init__()
self._streams = {}
self.fallback_stream = fallback_stream
self.fallback_handler = fallback_handler
def setFormatter(self, form):
super().setFormatter(form)
self.fallback_handler.setFormatter(form)
def emit(self, record):
try:
stream = self._streams.get(threading.get_ident())
if stream is None:
self.fallback_handler.emit(record)
else:
msg = self.format(record)
stream.write(msg)
stream.write(self.terminator)
if hasattr(stream, "flush"):
stream.flush()
except Exception:
self.handleError(record)
@contextlib.contextmanager
def register_stream(self, stream):
if stream == self.fallback_stream:
yield
return
key = threading.get_ident()
self._streams[key] = stream
try:
yield
finally:
del self._streams[key]
def get_default_handler(): def get_default_handler():
handler = logging.StreamHandler(sys.stderr) handler = logging.StreamHandler(sys.stderr)
return handler return handler
@contextlib.contextmanager
def register_stream(stream):
"""Register global errors stream for the current thread."""
yield
def setup(): def setup():
"""Set global logging up.""" """Set global logging up."""
global register_stream, unregister_stream global register_stream, unregister_stream
handler = get_default_handler() handler = ThreadStreamsHandler(sys.stderr, get_default_handler())
logging.basicConfig(format=LOGGER_FORMAT, handlers=[handler]) logging.basicConfig(format=LOGGER_FORMAT, handlers=[handler])
register_stream = handler.register_stream
set_debug(True) set_debug(True)

View File

@ -43,6 +43,7 @@ class BaseTest:
data = data.encode("utf-8") data = data.encode("utf-8")
args["wsgi.input"] = BytesIO(data) args["wsgi.input"] = BytesIO(data)
args["CONTENT_LENGTH"] = str(len(data)) args["CONTENT_LENGTH"] = str(len(data))
args["wsgi.errors"] = sys.stderr
self.application._answer = self.application(args, self.start_response) self.application._answer = self.application(args, self.start_response)
return ( return (