Pass all attributes from logging.LogRecord via dddp.logs collection.

This commit is contained in:
Tyson Clugg 2015-09-25 11:32:04 +10:00
parent f15efc0930
commit 05e3356b93
3 changed files with 49 additions and 59 deletions

View file

@ -1,10 +1,12 @@
from dddp import THREAD_LOCAL as this
from dddp.api import API, Publication
from dddp.logging import LOGS_NAME
from django.contrib import auth
class Logs(Publication):
name = LOGS_NAME
users = auth.get_user_model()
def get_queries(self):

View file

@ -3,31 +3,65 @@ from __future__ import absolute_import, print_function
import datetime
import logging
import traceback
from dddp import THREAD_LOCAL as this, meteor_random_id, ADDED
LOGS_NAME = 'dddp.logs'
def stacklines_or_none(exc_info):
"""Return list of stack text lines or None."""
if exc_info is None:
return None
return traceback.format_exception(*exc_info)
class DDPHandler(logging.Handler):
"""Logging handler that streams log events via DDP to the current client."""
formatter = logging.BASIC_FORMAT
def emit(self, record):
"""Emit a formatted log record via DDP."""
if getattr(this, 'subs', {}).get('Logs', False):
if getattr(this, 'subs', {}).get(LOGS_NAME, False):
self.format(record)
this.send({
'msg': ADDED,
'collection': 'logs',
'id': meteor_random_id('/collection/logs'),
'collection': LOGS_NAME,
'id': meteor_random_id('/collection/%s' % LOGS_NAME),
'fields': {
'created': datetime.datetime.fromtimestamp(record.created),
'name': record.name,
'levelno': record.levelno,
'levelname': record.levelname,
# 'pathname': record.pathname,
# 'lineno': record.lineno,
'msg': record.msg,
'args': record.args,
# 'exc_info': record.exc_info,
# 'funcName': record.funcName,
attr: {
# typecasting methods for specific attributes
'args': lambda args: [repr(arg) for arg in args],
'created': datetime.datetime.fromtimestamp,
'exc_info': stacklines_or_none,
}.get(
attr,
lambda val: val # default typecasting method
)(getattr(record, attr, None))
for attr in (
'args',
'asctime',
'created',
'exc_info',
'filename',
'funcName',
'levelname',
'levelno',
'lineno',
'module',
'msecs',
'message',
'name',
'pathname',
'process',
'processName',
'relativeCreated',
'thread',
'threadName',
)
},
})

View file

@ -1,46 +0,0 @@
"""Django DDP utils for DDP messaging."""
from copy import deepcopy
from dddp import THREAD_LOCAL as this, REMOVED
try:
from django.db.models.expressions import ExpressionNode
except AttributeError:
ExpressionNode = None
def obj_change_as_msg(obj, msg):
"""Generate a DDP msg for obj with specified msg type."""
if ExpressionNode is None:
exps = False
else:
# check for F expressions
exps = [
name for name, val in vars(obj).items()
if isinstance(val, ExpressionNode)
]
if exps:
# clone and update obj with values but only for the expression fields
obj = deepcopy(obj)
# Django 1.8 makes obj._meta public --> pylint: disable=W0212
for name, val in obj._meta.model.objects.values(*exps).get(pk=obj.pk):
setattr(obj, name, val)
# run serialization now that all fields are "concrete" (not F expressions)
serializer = this.serializer
data = serializer.serialize([obj])[0]
# collection name is <app>.<model>
name = data['model']
# cast ID as string
if not isinstance(data['pk'], basestring):
data['pk'] = '%d' % data['pk']
payload = {
'msg': msg,
'collection': name,
'id': data['pk'],
}
if msg != REMOVED:
payload['fields'] = data['fields']
return (name, payload)