pvl.syslog.rule: refactor SyslogRule
authorTero Marttila <terom@fixme.fi>
Fri, 04 Jan 2013 20:51:12 +0200
changeset 55 cbdd49b76f16
parent 54 9c82a068f8f9
child 56 ff184e09ceb9
pvl.syslog.rule: refactor SyslogRule
bin/pvl.verkko-syslog
etc/syslog.conf
pvl/syslog/rule.py
--- a/bin/pvl.verkko-syslog	Fri Jan 04 20:50:31 2013 +0200
+++ b/bin/pvl.verkko-syslog	Fri Jan 04 20:51:12 2013 +0200
@@ -68,23 +68,18 @@
   
     log.info("Process syslog messages...")
     for item in syslog :
-        if rules :
-            match = rules.apply(item)
-        else :
-            match = None, item['msg']
+        match = rules.apply(item)
 
         if not match :
             continue
 
-        tag, line = match
-        
-        log.info("%s", line)
+        log.info("%s", match)
 
         # TODO: map tag -> target?
         if target :
-            target(line)
+            target(match)
         else :
-            print tag, line
+            print match
     
     # done
     log.info("Exiting...")
--- a/etc/syslog.conf	Fri Jan 04 20:50:31 2013 +0200
+++ b/etc/syslog.conf	Fri Jan 04 20:51:12 2013 +0200
@@ -1,17 +1,18 @@
+format      = {host} {msg}
+
+[sudo]
+program     = sudo
+pattern     = ^\s*(?P<username>\S+) : TTY=(?P<tty>\S+) ; PWD=(?P<pwd>.+?) ; USER=(?P<target_user>\S+) ; (?:ENV=(?P<env>.+?) ; )?COMMAND=(?P<command>.*)
+
+format      = {login}:{tty} - {user}@{host}:{pwd} - {command!r}
+
+#pattern     = \s*(?P<login>\S+) : TTY=(?P<tty>\S+)\s; PWD=(?P<pwd>.+?)\s; USER=(?P<user>\S+)\s;( COMMAND=(?P<command>.*)
+
 [puppet_readshadow]
 program     = sudo
 pattern     = \s*(?P<login>puppet) : TTY=(?P<tty>\S+)\s; PWD=(?P<pwd>.+?)\s; USER=(?P<user>root)\s; COMMAND=(?P<command>/usr/bin/getent shadow \w+)
 format      = 
 
-[sudo]
-program     = sudo
-pattern     = \s*(?P<login>\S+) : TTY=(?P<tty>\S+)\s; PWD=(?P<pwd>.+?)\s; USER=(?P<user>\S+)\s; COMMAND=(?P<command>.*)
-format      = {login}:{tty} - {user}@{host}:{pwd} - {command!r}
-
-[sudo_env]
-program     = sudo
-pattern     = \s*(?P<login>\S+) : TTY=(?P<tty>\S+) ; PWD=(?P<pwd>.+?) ; USER=(?P<user>\S+) ; ENV=(?P<env>.+?) ; COMMAND=(?P<command>.*)
-format      = {login}:{tty} - {user}@{host}:{pwd} - {env} {command!r}
 
 [ssh]
 program     = sshd
@@ -24,11 +25,6 @@
 [su_nobody]
 program     = su
 pattern     = Successful su for nobody by root|\+ \?\?\? root:nobody
-#flags      = re.IGNORECASE
 
 [puppet]
 program     = puppet
-format      = {host} {msg}
-
-[all]
-format      = {host} {msg}
--- a/pvl/syslog/rule.py	Fri Jan 04 20:50:31 2013 +0200
+++ b/pvl/syslog/rule.py	Fri Jan 04 20:51:12 2013 +0200
@@ -3,12 +3,10 @@
 import re
 
 import optparse, sys
+import configobj
 
 import logging; log = logging.getLogger('pvl.syslog.rule')
 
-# XXX: ConfigParser kinda sucks
-import ConfigParser
-
 def parser (parser) :
     """
         Optparse option group.
@@ -27,58 +25,114 @@
     """
     
     if options.syslog_rules :
-        return SyslogRules.load(open(options.syslog_rules))
+        return SyslogRule.load(open(options.syslog_rules))
 
     else :
-        return None
+        return SyslogRule('default')
 
 # TODO: combine SyslogRule/Rules into one heirarchial SyslogRule -type?
 class SyslogRule (object) :
     """
-        A rule matches syslog lines, and formats them.
-            
-            tag         - apply given tag to matches
+        A named SyslogFilter with sub-rules.
     """
 
-    def __init__ (self, tag, program=None, pattern=None, format=None, flags=0) :
-        log.debug("%s: %s", tag, pattern)
-        
-        if pattern :
-            pattern = re.compile(pattern, flags)
+    @classmethod
+    def load (cls, file) :
+        """
+            Load SyslogRule from file.
+        """
 
-        self.filter = SyslogFilter(prog=program)
+        config = configobj.ConfigObj(file)
+        
+        rules = [SyslogRule.config(section, **dict(config[section])) for section in config.sections]
 
-        self.tag = tag
-        self.format = format
-        self.pattern = pattern
+        return cls(file.name, rules)
 
+    @classmethod
+    def config (cls, name, format=None, program=None, pattern=None, **filters) :
+        """
+            Build SyslogRule from config options
+        """
+
+        if pattern :
+            pattern = re.compile(pattern)
+
+        # XXX: rules/sub-sections support?
+        rules = ()
+
+        filters = dict(
+            (cls.ATTRS.get(attr, attr), re.compile(regex)) for attr, regex in filters.iteritems()
+        )
+
+        filter = SyslogFilter(prog=program, msg=pattern, **filters)
+
+        return cls(name, rules, filter)
+
+    def __init__ (self, name, rules=None, filter=None) :
+        log.debug("%s: %s", name, filter)
+        
+        self.name = name
+        self.rules = rules or [] # sub-rules
+        self.filter = filter # SyslogFilter
+
+    def match (self, item) :
+        """
+            Match item against our filter, applying any matches.
+        """
+        
+        if self.filter :
+            # filter
+            matches = self.filter.filter(item)
+
+            if not matches :
+                # ignore
+                return None
+        else :
+            # match all
+            matches = True
+
+        # apply
+        item['rule'] = self
+
+        if matches is True :
+            # no-op filter
+            pass
+        else :
+            log.debug("%s: %s", self, matches)
+            item.update(matches)
+        
+        # XXX: copy, not mutate?
+        return item
+    
+    # TODO: __call__?
     def apply (self, item) :
         """
-            Apply rule against given item.
-
-            Returns
-                None        - skip
-                False       - drop
-                (tag, line) - output
+            Match item against ourselfs, apply against any sub-rules, and return XXX
         """
-        
-        # filter
-        match = self.filter.filter(item)
 
-        if not match :
-            # ignore
-            return None
-        
-        if self.pattern :
-            match = self.pattern.match(item['msg'])
+        item = self.match(item)
 
-            if not match :
-                # ignore
-                return None
+        if not item :
+            return
+
+        # sub-rules
+        for rule in self.rules :
+            try :
+                match = rule.apply(item)
+
+            except Exception as ex :
+                log.exception("rule %s: %r", rule, item)
+                return # XXX: skip?
+
+            if match is None :
+                continue
+            else :
+                item = match
             
-            # apply
-            item.update(match.groupdict())
-
+        log.debug("%s: %s", self, item)
+       
+        # TODO: routing/formatting
+        """
         if self.tag is False :
             # drop
             return False
@@ -86,54 +140,11 @@
         if self.format :
             # return
             return self.tag, self.format.format(**item)
+        """
         
-    def __str__ (self) :
-        return self.tag
-
-class SyslogRules (object) :
-    """
-        Apply a set of rules against lines.
-    """
-
-    @classmethod
-    def load (cls, file) :
-        """
-            Load rules from file.
-        """
-        config = ConfigParser.RawConfigParser()
-        config.readfp(file)
-        
-        # XXX: ordered in python2.7, unordered in python2.6 !
-        rules = [SyslogRule(section, **dict(config.items(section))) for section in config.sections()]
-
-        return cls(rules)
+        return item
 
-    def __init__ (self, rules) :
-        self.rules = rules
-
-    def apply (self, item) :
-        """
-            Apply item against our rules, returning the first match (False/tag-line).
-        """
-
-        for rule in self.rules :
-            try :
-                match = rule.apply(item)
-
-            except Exception as ex :
-                log.exception("rule %s: %r", rule, item)
-                continue
-
-            log.debug("%s: %s", rule, match)
-
-            if match is None :
-                continue
-            else :
-                break
-        
-        return match
-
-    def process (self, items) :
+    def __iter__ (self, items) :
         """
             Apply items against our rules, yielding any matches.
         """
@@ -143,5 +154,10 @@
 
             if match :
                 yield match
-
-
+ 
+    def __str__ (self) :
+        return self.name
+    
+    def __repr__ (self) :
+        return 'SyslogRule({self.name}, ...)'.format(self=self)
+