growlee: add syslog backend
authorMoritz Venn <moritz.venn@freaque.net>
Mon, 31 Jan 2011 18:35:05 +0000 (19:35 +0100)
committerMoritz Venn <moritz.venn@freaque.net>
Mon, 31 Jan 2011 18:35:05 +0000 (19:35 +0100)
only client end tested so far, feel free to send me bugreports ;-)

growlee/src/GrowleeConnection.py
growlee/src/Syslog.py [new file with mode: 0644]
growlee/src/plugin.py

index 43cbf79..e6c099a 100644 (file)
@@ -7,6 +7,10 @@ from twisted.internet import reactor
 from . import NOTIFICATIONID
 
 def emergencyDisable(*args, **kwargs):
+       if args:
+               try: args[0].printTraceback()
+               except Exception: pass
+
        global growleeConnection
        if growleeConnection:
                growleeConnection.stop()
@@ -67,9 +71,12 @@ class GrowleeConnection:
                        elif proto == "growl":
                                from GrowlTalk import GrowlTalkAbstraction
                                connection = GrowlTalkAbstraction(host)
-                       else: # proto == "snarl":
+                       elif proto == "snarl":
                                from SNP import SnarlNetworkProtocolAbstraction
                                connection = SnarlNetworkProtocolAbstraction(host)
+                       else: # proto == "syslog":
+                               from Syslog import SyslogAbstraction
+                               connection = SyslogAbstraction(host)
 
                        self.connections.append((connection, host))
 
diff --git a/growlee/src/Syslog.py b/growlee/src/Syslog.py
new file mode 100644 (file)
index 0000000..e4cd90c
--- /dev/null
@@ -0,0 +1,110 @@
+from twisted.internet.protocol import DatagramProtocol
+from twisted.internet import reactor
+from twisted.internet import reactor
+from time import strftime, strptime, localtime
+from os import uname
+
+from Screens.MessageBox import MessageBox
+from Tools import Notifications
+
+from GrowleeConnection import emergencyDisable
+from . import NOTIFICATIONID
+
+SYSLOG_UDP_PORT = 514
+
+FACILITY = {
+       'kern': 0, 'user': 1, 'mail': 2, 'daemon': 3,
+       'auth': 4, 'syslog': 5, 'lpr': 6, 'news': 7,
+       'uucp': 8, 'cron': 9, 'authpriv': 10, 'ftp': 11,
+       'local0': 16, 'local1': 17, 'local2': 18, 'local3': 19,
+       'local4': 20, 'local5': 21, 'local6': 22, 'local7': 23,
+}
+
+SEVERITY = {
+       'emerg': 0, 'alert':1, 'crit': 2, 'err': 3,
+       'warning': 4, 'notice': 5, 'info': 6, 'debug': 7
+}
+
+reverse = lambda map: dict(zip(map.values(), map.keys()))
+
+SEVERITYMAP = {
+       MessageBox.TYPE_YESNO: SEVERITY['debug'],
+       MessageBox.TYPE_INFO: SEVERITY['info'],
+       MessageBox.TYPE_WARNING: SEVERITY['warning'],
+       MessageBox.TYPE_ERROR: SEVERITY['err'],
+}
+
+class SyslogNetworkProtocol(DatagramProtocol):
+       addr = None
+       def __init__(self, host):
+               self.host = host
+
+       def gotIP(self, ip):
+               self.addr = (ip, SYSLOG_UDP_PORT)
+
+       def noIP(self, error):
+               print "--------------------------------", error
+               emergencyDisable()
+
+       def startProtocol(self):
+               reactor.resolve(self.host.address.value).addCallback(self.gotIP).addErrback(self.noIP)
+
+       def sendNotification(self, title='No title.', description='No message.', priority=0):
+               if not self.transport or not self.addr or not self.host.enable_outgoing.value:
+                       return
+
+               ltime = localtime()
+               day = strftime("%d", ltime)
+               if day[0] == "0":
+                       day = " " + day[1:]
+               value = strftime("%b %%s %H:%M:%S", ltime)
+               timestamp = value % (day,)
+               payload = "<%d>%s %s growlee: (%s) %s" % (FACILITY['local0'] * 8 + SEVERITYMAP[priority], timestamp, uname()[1], title, description.replace('\n', ' '),)
+               self.transport.write(payload, self.addr)
+
+       def datagramReceived(self, data, addr):
+               if not self.host.enable_incoming.value:
+                       return
+
+               Len = len(data)
+               if Len > 1024: # invalid according to rfc
+                       return
+
+               # read prio field
+               prio, data = data.split('>', 1)
+               prio = int(prio[1:])
+               facility, severity = divmod(prio, 8) # just the ids
+               #facility = reverse(FACILITY)[facility]
+               type = reverse(SEVERITYMAP).get(severity, MessageBox.TYPE_ERROR)
+
+               # parse remaining header
+               try:
+                       # try to parse timestamp to determine validity
+                       timestamp = strptime(payload[:15], '%b %d %H:%M:%S')
+               except ValueError:
+                       message = payload
+               else:
+                       hostname, payload = payload[16:].split(' ', 1)
+                       # NOTE: we could re-process timestamp to get a customized display format,
+                       # but lets just keep this for now
+                       message = hostname + '@' + payload[:15] + ':' + payload
+
+               Notifications.AddNotificationWithID(
+                       NOTIFICATIONID,
+                       MessageBox,
+                       text = message,
+                       type = type,
+                       timeout = 10, # XXX: un-hardcode timeout?
+                       close_on_any_key = True,
+               )
+
+class SyslogAbstraction:
+       def __init__(self, host):
+               self.syslog = SyslogNetworkProtocol(host)
+               self.serverPort = reactor.listenUDP(SYSLOG_UDP_PORT, self.syslog)
+
+       def sendNotification(self, title='No title.', description='No description.', priority=-1, timeout=-1):
+               self.syslog.sendNotification(title=title, description=description, priority=priority)
+
+       def stop(self):
+               return self.serverPort.stopListening()
index 428349a..423db9d 100644 (file)
@@ -27,7 +27,7 @@ def addHost(name):
        s.enable_outgoing = ConfigYesNo(default=False)
        s.address = ConfigText(fixed_size=False)
        s.password = ConfigPassword()
-       s.protocol = ConfigSelection(default="growl", choices=[("growl", "Growl"), ("snarl", "Snarl"), ("prowl", "Prowl")])
+       s.protocol = ConfigSelection(default="growl", choices=[("growl", "Growl"), ("snarl", "Snarl"), ("prowl", "Prowl"), ("syslog", "Syslog UDP")])
        s.level = ConfigSelection(default="-1", choices=[("-1", _("Low (Yes/No)")), ("0", _("Normal (Information)")), ("1", _("High (Warning)")), ("2", _("Highest (Emergency)"))])
        s.blacklist = ConfigSet(choices=[])
        config.plugins.growlee.hosts.append(s)