# -*- test-case-name: mv3d.test.util.test_log -*- # Copyright (C) 2012 Mortal Coil Games # See LICENSE for details. """ Classes related to making use of a log client @author: mike """ from time import time from twisted.python.log import deferr, addObserver, removeObserver from twisted.python.failure import Failure from mv3d.util.persist import Persistable, Integer, Text from mv3d.net.pb import Copyable, Cacheable from twisted.internet.defer import gatherResults from twisted.spread import pb import datetime from mv3d.util.guide import ChangeNotifier class LogMessage(Persistable, Copyable, ChangeNotifier): """ Defines a single log message. It can be sent across the network or persisted in a database. """ timestamp = Integer(createIndex=True, transmit=True) sourceLoc = Text(createIndex=True, transmit=True) logLevel = Integer(createIndex=True, transmit=True) system = Text(createIndex=True, transmit=True) message = Text(transmit=True) def __init__(self): Persistable.__init__(self) ChangeNotifier.__init__(self) @classmethod def fromEventDict(cls, eventDict): """ Convert an eventDict into one or more LogMessages returned as a list. """ if eventDict.has_key("failure"): fail = eventDict["failure"] if isinstance(fail, Failure): fail = fail.getTraceback() eventDict = eventDict.copy() eventDict["message"] = fail.split("\n") messages = [] for line in eventDict["message"]: msg = cls() msg.message = line msg.timestamp = eventDict.get("time", time()) msg.logLevel = eventDict.get("logLevel", 0) msg.system = eventDict.get("system", None) msg.sourceLoc = eventDict.get("sourceLoc") messages.append(msg) return messages def __str__(self): return "%s\t%s\t%d\t%s\t%s" % (self.date, self.sourceLoc, self.logLevel, self.system, self.message) def _getDate(self): """ Convert the timestamp to a date. """ return datetime.datetime.fromtimestamp(self.timestamp).strftime("%c") date = property(_getDate) class LogServiceObserver(object): """ This is the observer to install in order to send all messages to a central LogService. """ logService = None def __init__(self, conductor, logServiceLoc, defaultSourceLoc=None): self.initDeferred = conductor.getService(logServiceLoc) self.defaultSourceLoc = defaultSourceLoc or "pb://%s" % conductor.host self.queuedMessages = [] def setService(svc): for eventDict in self.queuedMessages: self(eventDict) self.logService = svc self.initDeferred.addCallback(setService) self.initDeferred.addErrback(deferr) def __call__(self, eventDict): """ An incoming message! """ if self.logService is None: self.queuedMessages.append(eventDict) return dlist = [] for msg in LogMessage.fromEventDict(eventDict): if not msg.sourceLoc: msg.sourceLoc = self.defaultSourceLoc dlist.append(self.logService.log(msg)) if not dlist: return return gatherResults([dfrd for dfrd in dlist if dfrd is not None]) def start(self): """ Start this observer """ addObserver(self) def stop(self): """ Stop the observer """ removeObserver(self) class LogSubscription(Cacheable): """ Subscribe to logs based on a filter. """ allowedState = ["subscriptionID"] filter = None def __init__(self, filter="invalid"): Cacheable.__init__(self) if filter != "invalid": self.filter = filter self.subscriptionID = id(self) self.listeners = [] def log(self, logMessage): """ Give this subscription a log message to filter and optionally send. """ if self.filter is None or (self.filter is not None and logMessage.matches(self.filter)): self.observe_log(logMessage) return self.updateAllClients("log", logMessage) def addListener(self, callback): """ Add a listener to all log events that go through this subscription. """ self.listeners.append(callback) def removeListener(self, callback): """ Removes a previously added listener callback. """ self.listeners.remove(callback) def unsubscribe(self, service): """ Unsubscribe from future messages """ return service.unsubscribe(self.subscriptionID) def observe_log(self, logMessage): """ Called when there's a log message to broadcast """ for listener in self.listeners: listener(logMessage) pb.setUnjellyableForClass(LogMessage, LogMessage) pb.setUnjellyableForClass(LogSubscription, LogSubscription)