mirror of
https://github.com/YunoHost/moulinette.git
synced 2024-09-03 20:06:31 +02:00
[enh] Implement logging handler and queues for the API
This commit is contained in:
parent
4dff1916a5
commit
f4853f2f4f
2 changed files with 60 additions and 11 deletions
|
@ -3,7 +3,6 @@
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import errno
|
import errno
|
||||||
import logging
|
|
||||||
import argparse
|
import argparse
|
||||||
from json import dumps as json_encode
|
from json import dumps as json_encode
|
||||||
|
|
||||||
|
@ -17,14 +16,42 @@ from moulinette.core import MoulinetteError, clean_session
|
||||||
from moulinette.interfaces import (
|
from moulinette.interfaces import (
|
||||||
BaseActionsMapParser, BaseInterface, ExtendedArgumentParser,
|
BaseActionsMapParser, BaseInterface, ExtendedArgumentParser,
|
||||||
)
|
)
|
||||||
|
from moulinette.utils import log
|
||||||
from moulinette.utils.serialize import JSONExtendedEncoder
|
from moulinette.utils.serialize import JSONExtendedEncoder
|
||||||
from moulinette.utils.text import random_ascii
|
from moulinette.utils.text import random_ascii
|
||||||
|
|
||||||
logger = logging.getLogger('moulinette.interface.api')
|
logger = log.getLogger('moulinette.interface.api')
|
||||||
|
|
||||||
|
|
||||||
# API helpers ----------------------------------------------------------
|
# API helpers ----------------------------------------------------------
|
||||||
|
|
||||||
|
class LogQueues(dict):
|
||||||
|
"""Map of session id to queue."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
class APIQueueHandler(log.Handler):
|
||||||
|
"""
|
||||||
|
A handler class which store logging records into a queue, to be used
|
||||||
|
and retrieved from the API.
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
log.Handler.__init__(self)
|
||||||
|
self.queues = LogQueues()
|
||||||
|
|
||||||
|
def emit(self, record):
|
||||||
|
sid = request.get_cookie('session.id')
|
||||||
|
try:
|
||||||
|
queue = self.queues[sid]
|
||||||
|
except KeyError:
|
||||||
|
# Session is not initialized, abandon.
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
# Put the message as a 2-tuple in the queue
|
||||||
|
queue.put_nowait((record.levelname.lower(), record.getMessage()))
|
||||||
|
# Put the current greenlet to sleep for 0 second in order to
|
||||||
|
# populate the new message in the queue
|
||||||
|
sleep(0)
|
||||||
|
|
||||||
class _HTTPArgumentParser(object):
|
class _HTTPArgumentParser(object):
|
||||||
"""Argument parser for HTTP requests
|
"""Argument parser for HTTP requests
|
||||||
|
|
||||||
|
@ -126,7 +153,7 @@ class _ActionsMapPlugin(object):
|
||||||
name = 'actionsmap'
|
name = 'actionsmap'
|
||||||
api = 2
|
api = 2
|
||||||
|
|
||||||
def __init__(self, actionsmap, use_websocket):
|
def __init__(self, actionsmap, use_websocket, log_queues={}):
|
||||||
# Connect signals to handlers
|
# Connect signals to handlers
|
||||||
msignals.set_handler('authenticate', self._do_authenticate)
|
msignals.set_handler('authenticate', self._do_authenticate)
|
||||||
if use_websocket:
|
if use_websocket:
|
||||||
|
@ -134,9 +161,9 @@ class _ActionsMapPlugin(object):
|
||||||
|
|
||||||
self.actionsmap = actionsmap
|
self.actionsmap = actionsmap
|
||||||
self.use_websocket = use_websocket
|
self.use_websocket = use_websocket
|
||||||
|
self.log_queues = log_queues
|
||||||
# TODO: Save and load secrets?
|
# TODO: Save and load secrets?
|
||||||
self.secrets = {}
|
self.secrets = {}
|
||||||
self.queues = {}
|
|
||||||
|
|
||||||
def setup(self, app):
|
def setup(self, app):
|
||||||
"""Setup plugin on the application
|
"""Setup plugin on the application
|
||||||
|
@ -308,11 +335,11 @@ class _ActionsMapPlugin(object):
|
||||||
"""
|
"""
|
||||||
s_id = request.get_cookie('session.id')
|
s_id = request.get_cookie('session.id')
|
||||||
try:
|
try:
|
||||||
queue = self.queues[s_id]
|
queue = self.log_queues[s_id]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# Create a new queue for the session
|
# Create a new queue for the session
|
||||||
queue = Queue()
|
queue = Queue()
|
||||||
self.queues[s_id] = queue
|
self.log_queues[s_id] = queue
|
||||||
|
|
||||||
wsock = request.environ.get('wsgi.websocket')
|
wsock = request.environ.get('wsgi.websocket')
|
||||||
if not wsock:
|
if not wsock:
|
||||||
|
@ -326,7 +353,7 @@ class _ActionsMapPlugin(object):
|
||||||
except TypeError:
|
except TypeError:
|
||||||
if item == StopIteration:
|
if item == StopIteration:
|
||||||
# Delete the current queue and break
|
# Delete the current queue and break
|
||||||
del self.queues[s_id]
|
del self.log_queues[s_id]
|
||||||
break
|
break
|
||||||
logger.exception("invalid item in the messages queue: %r", item)
|
logger.exception("invalid item in the messages queue: %r", item)
|
||||||
else:
|
else:
|
||||||
|
@ -358,7 +385,7 @@ class _ActionsMapPlugin(object):
|
||||||
finally:
|
finally:
|
||||||
# Close opened WebSocket by putting StopIteration in the queue
|
# Close opened WebSocket by putting StopIteration in the queue
|
||||||
try:
|
try:
|
||||||
queue = self.queues[request.get_cookie('session.id')]
|
queue = self.log_queues[request.get_cookie('session.id')]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
|
@ -396,7 +423,7 @@ class _ActionsMapPlugin(object):
|
||||||
"""
|
"""
|
||||||
s_id = request.get_cookie('session.id')
|
s_id = request.get_cookie('session.id')
|
||||||
try:
|
try:
|
||||||
queue = self.queues[s_id]
|
queue = self.log_queues[s_id]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -621,11 +648,20 @@ class Interface(BaseInterface):
|
||||||
- routes -- A dict of additional routes to add in the form of
|
- routes -- A dict of additional routes to add in the form of
|
||||||
{(method, path): callback}
|
{(method, path): callback}
|
||||||
- use_websocket -- Serve via WSGI to handle asynchronous responses
|
- use_websocket -- Serve via WSGI to handle asynchronous responses
|
||||||
|
- log_queues -- A LogQueues object or None to retrieve it from
|
||||||
|
registered logging handlers
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, actionsmap, routes={}, use_websocket=True):
|
def __init__(self, actionsmap, routes={}, use_websocket=True,
|
||||||
|
log_queues=None):
|
||||||
self.use_websocket = use_websocket
|
self.use_websocket = use_websocket
|
||||||
|
|
||||||
|
# Attempt to retrieve log queues from an APIQueueHandler
|
||||||
|
if log_queues is None:
|
||||||
|
handler = log.getHandlersByClass(APIQueueHandler, limit=1)
|
||||||
|
if handler:
|
||||||
|
log_queues = handler.queues
|
||||||
|
|
||||||
# TODO: Return OK to 'OPTIONS' xhr requests (l173)
|
# TODO: Return OK to 'OPTIONS' xhr requests (l173)
|
||||||
app = Bottle(autojson=True)
|
app = Bottle(autojson=True)
|
||||||
|
|
||||||
|
@ -648,7 +684,7 @@ class Interface(BaseInterface):
|
||||||
# Install plugins
|
# Install plugins
|
||||||
app.install(apiheader)
|
app.install(apiheader)
|
||||||
app.install(api18n)
|
app.install(api18n)
|
||||||
app.install(_ActionsMapPlugin(actionsmap, use_websocket))
|
app.install(_ActionsMapPlugin(actionsmap, use_websocket, log_queues))
|
||||||
|
|
||||||
# Append default routes
|
# Append default routes
|
||||||
# app.route(['/api', '/api/<category:re:[a-z]+>'], method='GET',
|
# app.route(['/api', '/api/<category:re:[a-z]+>'], method='GET',
|
||||||
|
|
|
@ -49,6 +49,19 @@ def configure_logging(logging_config=None):
|
||||||
if logging_config:
|
if logging_config:
|
||||||
dictConfig(logging_config)
|
dictConfig(logging_config)
|
||||||
|
|
||||||
|
def getHandlersByClass(classinfo, limit=0):
|
||||||
|
"""Retrieve registered handlers of a given class."""
|
||||||
|
handlers = []
|
||||||
|
for ref in logging._handlers.itervaluerefs():
|
||||||
|
o = ref()
|
||||||
|
if o is not None and isinstance(o, classinfo):
|
||||||
|
if limit == 1:
|
||||||
|
return o
|
||||||
|
handlers.append(o)
|
||||||
|
if limit != 0 and len(handlers) > limit:
|
||||||
|
return handlers[:limit-1]
|
||||||
|
return handlers
|
||||||
|
|
||||||
|
|
||||||
class MoulinetteLogger(Logger):
|
class MoulinetteLogger(Logger):
|
||||||
"""Custom logger class
|
"""Custom logger class
|
||||||
|
|
Loading…
Reference in a new issue