From 05e3356b93c3fb134b6c8d1eb454ef438ab38122 Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Fri, 25 Sep 2015 11:32:04 +1000 Subject: [PATCH] Pass all attributes from `logging.LogRecord` via `dddp.logs` collection. --- dddp/ddp.py | 2 ++ dddp/logging.py | 60 ++++++++++++++++++++++++++++++++++++++----------- dddp/msg.py | 46 ------------------------------------- 3 files changed, 49 insertions(+), 59 deletions(-) delete mode 100644 dddp/msg.py diff --git a/dddp/ddp.py b/dddp/ddp.py index 0af0f11..da7d54d 100644 --- a/dddp/ddp.py +++ b/dddp/ddp.py @@ -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): diff --git a/dddp/logging.py b/dddp/logging.py index 2560558..dcf6945 100644 --- a/dddp/logging.py +++ b/dddp/logging.py @@ -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', + ) }, }) diff --git a/dddp/msg.py b/dddp/msg.py deleted file mode 100644 index 4029b5b..0000000 --- a/dddp/msg.py +++ /dev/null @@ -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 . - 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)