[enh] Implement logging handler and queues for the API

This commit is contained in:
Jérôme Lebleu 2015-11-13 18:12:38 +01:00
parent 4dff1916a5
commit f4853f2f4f
2 changed files with 60 additions and 11 deletions

View file

@ -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',

View file

@ -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