implement SyslogFilter
authorTero Marttila <terom@fixme.fi>
Fri, 05 Feb 2010 21:16:19 +0200
changeset 53 21ab25ffa1e8
parent 52 13fc80450862
child 54 395182b7ea0f
implement SyslogFilter
fixbot/logwatch/filters.py
fixbot/logwatch/message.py
fixbot/logwatch/sources.py
--- a/fixbot/logwatch/filters.py	Fri Feb 05 20:38:44 2010 +0200
+++ b/fixbot/logwatch/filters.py	Fri Feb 05 21:16:19 2010 +0200
@@ -13,15 +13,22 @@
 
     def test (self, line) :
         """
-            Match against the given line, and return one of:
+            Match against the given line. See match() for return codes
+        """
 
+        raise NotImplementedError()
+    
+    def match (self, msg) :
+        """
+            Match against the given SyslogMessage, and return one of:
                 None        - filter did not match, continue
                 False       - filter matched, line should be dropped
                     (type, <str>)
                             - filter matched, pass formatted output
         """
-
-        raise NotImplementedError()
+        
+        # default to a full-line match
+        return self.test(str(msg))
 
 class FullFilter (BaseFilter) :
     """
@@ -37,7 +44,7 @@
         A filter that drops every line matching a given regexp
     """
     
-    def __init__ (self, pattern, flags=None) :
+    def __init__ (self, pattern, flags=0) :
         # don't need an event_type
 
         self.regexp = re.compile(pattern, flags)
@@ -79,6 +86,69 @@
             # match as-is
             return self.event_type, line
 
+class SyslogFilter (BaseFilter) :
+    """
+        A more advanced filter that can match against fields in the syslog message.
+    """
+
+    def __init__ (self, label, pattern=None, program=None, drop=False, format=None, re_flags=0) :
+        """
+            Filter using the given criteria:
+    
+            label           - label match output with given label
+
+            pattern         - match message content against given regexp
+            program         - (optional) case-insensitive match against message tag's program component
+                                May also be False to indicate no tag
+
+            drop            - drop this message if this matches
+            format          - (optional) format output with given format string
+
+            re_flags        - (optional) flags for regular expression
+        """
+
+        # XXX: super(SyslogFilter, self).__init__(label)
+        self.label = label
+        
+        # store
+        self.regexp = re.compile(pattern, re_flags)
+        self.program = program
+
+        self.drop = drop
+        self.format = format
+
+    def match (self, msg) :
+        """
+            Evaluate match on given message
+        """
+        
+        # use the SyslogMessage's match method
+        match = msg.match(self.regexp, self.program)
+        
+        # handle result
+        if match is False :
+            # nack
+            return None
+        
+        elif self.drop :
+            # halt processing
+            return False
+
+        elif self.format :
+            # the messages properties
+            params = msg.properties()
+
+            # the regexp'd matched params
+            params.update(match)
+
+            # formatted output
+            return self.label, self.format % params
+
+        else :
+            # boring output
+            return self.label, str(msg)
+
+
 # matches a timestamp prefix
 _timestamp = "\w{3} [0-9 ]\d \d{2}:\d{2}:\d{2}"
 
@@ -94,12 +164,18 @@
 )
 
 # match sudo invocations, reformatting them nicely
-sudo = SimpleFilter(
-    "sudo",
-    "(?P<hostname>\S+)\s+sudo:\s*(?P<username>\S+) : TTY=(?P<tty>\S+) ; PWD=(?P<pwd>.+?) ; USER=(?P<target_user>\S+) ; COMMAND=(?P<command>.*)",
-    "%(username)s:%(tty)s - %(target_user)s@%(hostname)s:%(pwd)s - %(command)r"
+sudo = SyslogFilter('sudo',
+    program = "sudo",
+    pattern = "^\s*(?P<username>\S+) : TTY=(?P<tty>\S+) ; PWD=(?P<pwd>.+?) ; USER=(?P<target_user>\S+) ; COMMAND=(?P<command>.*)",
+    format  = "%(username)s:%(tty)s - %(target_user)s@%(hostname)s:%(pwd)s - %(command)r",
 )
 
+#sudo = SimpleFilter(
+#    "sudo",
+#    "(?P<hostname>\S+)\s+sudo:\s*(?P<username>\S+) : TTY=(?P<tty>\S+) ; PWD=(?P<pwd>.+?) ; USER=(?P<target_user>\S+) ; COMMAND=(?P<command>.*)",
+#    "%(username)s:%(tty)s - %(target_user)s@%(hostname)s:%(pwd)s - %(command)r"
+#)
+
 # match accepted ssh logins
 ssh = SimpleFilter(
     "ssh",
--- a/fixbot/logwatch/message.py	Fri Feb 05 20:38:44 2010 +0200
+++ b/fixbot/logwatch/message.py	Fri Feb 05 21:16:19 2010 +0200
@@ -71,7 +71,7 @@
         +   r"(?P<timestamp>\w{3} [0-9 ]\d \d{2}:\d{2}:\d{2}) (?P<hostname>\S+)"
 
             # the message, including possible tag/pid
-        +   r" (?P<message>(?P<tag>(?P<program>[^:\]]+)(?:\[(?P<pid>\d+)\])?: )?(?P<text>.+))\n?"
+        +   r" (?P<message>(?P<tag>(?P<program>[^:\]]+)(?:\[(?P<pid>\d+)\])?: )?(?P<text>.*))\n?"
     )
 
     # strptime format of timestamp
@@ -158,7 +158,65 @@
         
         # the raw line as matched
         self.raw = match.group(0)
+    
+    def properties (self) :
+        """
+            Return a dict containing the attributes specified for this message
+        """
+        
+        # XXX: ugh... doesn't do @property
+        return dict(
+            facility        = self.facility,
+            priority        = self.priority,
+            timestamp       = self.timestamp,
+            hostname        = self.hostname,
+            tag             = self.tag,
+            program         = self.program,
+            pid             = self.pid,
+            text            = self.text,
+            message         = self.message,
+        )
 
+    def match (self, regexp=None, program=None) :
+        """
+            Evaluate a match against this message using the given criteria
+                
+                regexp          - match regexp against the message's contents
+                program         - match program component of tag against given value, may also be False
+        """
+
+        params = {}
+        
+        ## program
+        if program is False :
+            # only match if no tag
+            if self.program is not None :
+                return False
+
+        elif program is not None :
+            # case-insensitive match
+            if self.program.lower() != program.lower() :
+                return False
+        
+        else :
+            # params
+            params['program'] = self.program
+
+        ## pattern
+        if regexp is not None :
+            # apply regexp
+            match = regexp.match(self.text)
+
+            if not match :
+                # no match
+                return False
+            
+            # good, params
+            params.update(match.groupdict())
+        
+        # we appear to have a match
+        return params
+        
     def __str__ (self) :
         """
             Format to default format
--- a/fixbot/logwatch/sources.py	Fri Feb 05 20:38:44 2010 +0200
+++ b/fixbot/logwatch/sources.py	Fri Feb 05 21:16:19 2010 +0200
@@ -81,7 +81,7 @@
 
         for filter in self.filters :
             # let the filter process the line
-            out = filter.test(line)
+            out = filter.match(msg)
 
             if out :
                 # unpack