terom@2: import logging; log = logging.getLogger('pvl.syslog.filter') terom@2: terom@2: import re # XXX terom@2: import os.path, fnmatch terom@2: terom@2: class SyslogFilter (object) : terom@2: """ terom@2: Match syslog messages fields against given patterns. terom@2: """ terom@2: terom@2: @classmethod terom@2: def build (cls, **filters) : terom@2: """ terom@2: Match using given non-None fields. terom@2: """ terom@2: terom@2: # drop None's terom@2: return cls(dict((attr, regex) for attr, regex in filters.iteritems() if regex is not None)) terom@2: terom@2: def __init__ (self, filters) : terom@2: """ terom@2: Match using given { field: regex }. terom@2: """ terom@2: terom@2: self.filters = filters terom@2: terom@2: def match_glob (self, attr, glob, value=None) : terom@2: """ terom@2: Match prog as glob. terom@2: """ terom@2: terom@2: if not glob : terom@2: return { attr: value } terom@2: terom@2: if not value : terom@2: # require terom@2: return False terom@2: terom@2: # normalize terom@2: value = value.strip() terom@2: terom@2: # match terom@2: if fnmatch.fnmatch(value, glob) : terom@2: return { attr: value } terom@2: else : terom@2: return False terom@2: terom@2: match_facility = match_glob terom@2: terom@2: def match_prog (self, attr, glob, prog=None) : terom@2: """ terom@2: Match prog as glob. terom@2: """ terom@2: terom@2: if prog : terom@2: # normalize terom@2: prog = prog.strip().lower() terom@2: terom@2: if prog.startswith('/') : terom@2: # leaves postfix/* intact, but fixes /usr/bin/cron terom@2: _, prog = os.path.split(prog) terom@2: terom@2: # match terom@2: return self.match_glob(attr, glob, prog) terom@2: terom@2: REGEX_TYPE = type(re.compile('')) terom@2: terom@2: def match_regex (self, attr, regex, value=None) : terom@2: """ terom@2: Match given value against given pattern. terom@2: """ terom@2: terom@2: if not regex : terom@2: return { attr: value } terom@2: terom@2: if not value : terom@2: # XXX: optional = match empty string? terom@2: value = '' terom@2: else : terom@2: # normalize; XXX: unicode? terom@2: value = str(value).strip() terom@2: terom@2: # match terom@2: match = regex.match(value) terom@2: terom@2: if not match : terom@2: return False terom@2: terom@2: # as match-values terom@2: matches = { attr: match.group(0) } # whole match terom@2: matches.update(match.groupdict()) terom@2: terom@2: # TODO match.expand? terom@2: terom@2: return matches terom@2: terom@2: def filter (self, item) : terom@2: """ terom@2: Match given item. Returns any matched values (including regexp capture groups) across all fields. terom@2: """ terom@2: terom@2: match = None terom@2: matches = {} terom@2: terom@2: for attr in self.filters : terom@2: # filter terom@2: filter = self.filters[attr] terom@2: terom@2: # lookup match-func terom@2: match = getattr(self, 'match_{attr}'.format(attr=attr), None) terom@2: terom@2: if match : terom@2: pass terom@2: terom@2: elif isinstance(filter, self.REGEX_TYPE) : terom@2: match = self.match_regex terom@2: terom@2: else : terom@2: match = self.match_glob terom@2: terom@2: # apply match terom@2: if attr in item : terom@2: match = match(attr, filter, item[attr]) terom@2: else : terom@2: match = match(attr, filter) terom@2: terom@2: log.debug("%s: %s", attr, match) terom@2: terom@2: if match : terom@2: # match terom@2: matches.update(match) terom@2: terom@2: else : terom@2: # reject terom@2: return terom@2: terom@2: # test last match terom@2: if match is None : terom@2: # empty filter -> all None terom@2: return True terom@2: else : terom@2: return matches terom@2: terom@2: def process (self, items) : terom@2: for item in items: terom@2: match = self.filter(item) terom@2: terom@2: if match : terom@2: yield item terom@2: terom@2: __call__ = process terom@2: terom@2: def __nonzero__ (self) : terom@2: return bool(self.filters) terom@2: terom@2: def __repr__ (self) : terom@2: return repr(self.filters)