pvl/syslog/rule.py
author Tero Marttila <terom@paivola.fi>
Sun, 15 Jun 2014 19:43:13 +0300
changeset 29 8fcb140f1ee0
parent 2 5a8a32cbc944
permissions -rw-r--r--
pvl.args: load default config from /etc/pvl/package/module.conf, if specified to parse(...)
from pvl.syslog.filter import SyslogFilter

import re

import optparse, sys
import configobj

import logging; log = logging.getLogger('pvl.syslog.rule')

def parser (parser) :
    """
        Optparse option group.
    """

    syslog_rules = optparse.OptionGroup(parser, "Syslog rules")
    
    syslog_rules.add_option('--syslog-rules', metavar='FILE',
            help="Load syslog rules from file")

    return syslog_rules

def apply (options) :
    """
        Build SyslogRules from options.
    """
    
    if options.syslog_rules :
        return SyslogRule.load(open(options.syslog_rules))

    else :
        return SyslogRule('default', formats={ 'text': '{msg}' })

def merge (*dicts, **kwargs) :
    return dict((k, v) for d in (dicts + (kwargs, )) for k, v in d.iteritems())

# TODO: combine SyslogRule/Rules into one heirarchial SyslogRule -type?
class SyslogRule (object) :
    """
        A named SyslogFilter with sub-rules.
    """

    @classmethod
    def load (cls, file) :
        """
            Load SyslogRule from file.
        """

        config = configobj.ConfigObj(file)

        return cls.config_section(file.name, config)
    
    @classmethod
    def config_section (cls, name, section) :
        """
            Recursively load Syslogrules from config section.
        """

        rules = [cls.config_section(subsection, section[subsection]) for subsection in section.sections]
        attrs = dict((name, section[name]) for name in section.scalars)
         
        try :
            return cls.config(name, rules, **attrs)

        except ValueError as ex :
            raise ValueError("[%s] %s" % (name, ex))

    @classmethod
    def config_filters (cls, program=None, facility=None, pattern=None, **filters) :
        """
            Return filter expression from given attr/value in config.
        """

        # XXX: get rid of these special cases
        if facility :
            yield 'facility', facility # glob

        if program :
            yield 'prog', program # glob

        if pattern :
            filters['msg'] = pattern
        
        # generic
        for attr, value in filters.iteritems() :
            try :
                # regex
                yield attr, re.compile(value)
            
            except re.error as ex :
                raise ValueError("%s: %s" % (attr, ex))

    @classmethod
    def config (cls, name, rules=None, format=None, irk=None, **filters) :
        """
            Build SyslogRule from config options
        """

        if format is not None :
            format = { 'text': format }
        else :
            format = { }

        if irk :
            format['irk'] = irk
        
        filters = dict(cls.config_filters(**filters))

        filter = SyslogFilter(filters)
        
        log.debug("%s: %s %s", name, rules, filter)

        return cls(name, rules, filter, format)

    def __init__ (self, name, rules=None, filter=None, formats=None) : 
        self.name = name
        self.rules = rules or [] # sub-rules
        self.filter = filter # SyslogFilter
        self.formats = formats or {}

    def match (self, item) :
        """
            Match item against our filter, returning match-dict (empty?) or None.
        """
        
        if self.filter :
            # filter
            matches = self.filter.filter(item)

        else :
            # match all, we probably have sub-rules that we're interested in
            return { }

        log.debug("%s: %s", self, matches)

        if matches :
            return matches
        else :
            # no match
            return None

    def format (self, item) :
        """
            Apply our output formats to given base apply, yielding (unique) attr, value tuples.
        """
        
        for attr, format in self.formats.iteritems() :
            value = format.format(**item)

            log.debug("%s: %s: %s", self, attr, value)

            yield attr, value

    def apply (self, item) :
        """
            Recursively match item against ourself and sub-rules. Returns applied output.

            Matches are passed down the tree, and applies are passed up.
        """

        log.debug("%s", self)

        # match rule -> matches
        matches = self.match(item)

        if matches is None :
            # skip
            return None, None, None
        
        # merge matches down
        item = merge(item, matches)

        # recursive sub-rules -> apply
        for rule in self.rules :
            try :
                # pass matches down
                match, rules, apply = rule.apply(item)

            except Exception as ex :
                log.exception("%s -> %s: %r", self, rule, item)
                continue # XXX: skip?

            if apply :
                # pass apply up
                break
        else :
            # self-match
            match, rules, apply = item, [], { }
        
        rules.append(self)

        # formats?
        if self.formats :
            # merge apply up
            apply = merge(dict(self.format(item)), apply)
        
        log.debug("%s: %s", '/'.join(str(rule) for rule in rules), apply)
        
        return match, rules, apply

    def __iter__ (self, items) :
        """
            Apply items against our rules, yielding any matches.
        """

        for item in items :
            match, rules, apply = self.apply(item)

            if apply :
                yield apply
 
    def __str__ (self) :
        return self.name
    
    def __repr__ (self) :
        return 'SyslogRule({self.name}, ...)'.format(self=self)