<commit_before>import re
from checks.log_parser import LogParserCheck, LogParser
from util import namedtuple, get_hostname


class NagiosParsingError(Exception): pass
class InvalidDataTemplate(Exception): pass


class Nagios(LogParserCheck):

    key = "Nagios"

    def __init__(self, name, init_config, agentConfig):
        LogParserCheck.__init__(self, name, init_config, agentConfig)

    def create_event(self, event_data):
        timestamp, event_type, fields = event_data

        # FIXME Oli: kind of ugly to have to go through a named dict for this, and inefficient too
        # but couldn't think of anything smarter
        d = fields._asdict()
        d.update({'timestamp': timestamp, 'event_type': event_type, 'api_key': self.agentConfig.get('api_key', '')})
        # if host is localhost, turn that into the internal host name
        host = d.get('host', None)
        if host == "localhost":
            d["host"] = get_hostname(self.agentConfig)

        self.log.debug("Nagios event: %s" % (d))
        self.event(d)

    def check(self, instance):

        # Check arguments
        cfg_path = instance.get('cfg_file', None)
        log_path = instance.get('log_file', None)
        event_log = instance.get('event_log', True)
        perf_data = instance.get('perf_data', False)
        if cfg_path is None and log_path is None:
            raise Exception("Not checking nagios because 'cfg_file' is not set in nagios config")

        tags = instance.get('tags', None)

        instance_key = self._instance_key(instance)

        parsers = []
        if instance_key in self.parsers:
            parsers = self.parsers[instance_key]

        # Build our tail -f
        if len(parsers) == 0:
            nagios_config = {}
            if cfg_path:
                nagios_config = self.parse_nagios_config(cfg_path)

                if perf_data:
                    host_parser = NagiosHostPerfData(self.log, nagios_config)
                    service_parser = NagiosServicePerfData(self.log, nagios_config)

                    if not host_parser.tail and not service_parser.tail:
                        raise Exception("The Nagios check is configured to check performance data, but failed to parse any of the perfdata files. Please ensure that the file and file_template fields for at least one of host or service perfdata is filled in correctly in the nagios config file.")

                    if host_parser.tail:
                        parsers.append(host_parser)
                    if service_parser.tail:
                        parsers.append(service_parser)

            if event_log:
                log_file = nagios_config.get('log_file', log_path)
                if not log_file:
                    raise Exception("Nagios check is configured to parse the event log, but failed to do so. Please ensure the value for log_file is correct.")

                nagios_log_parser = NagiosLogParser(self.log, log_file)
                parsers.append(nagios_log_parser)

            self.parsers[instance_key] = parsers

        # read until the end of file
        for parser in parsers:
            parser.parse_file()
            events = parser._get_events()
            for event in events:
                self.create_event(event)

            metrics = parser._get_metrics()
            for metric in metrics:
                self.gauge(metric['metric'], float(metric['value']), tags=tags, hostname=metric['hostname'], device_name=metric['device_name'])

    @staticmethod
    def parse_agent_config(agentConfig):
        cfg_file = agentConfig.get('nagios_perf_cfg', None)
        log_file = agentConfig.get('nagios_log', None)

        if not log_file and not cfg_file:
            return False

        instance = {}

        if cfg_file:
            instance['cfg_file'] = cfg_file
            instance['perf_data'] = True

        if log_file:
            instance['log_file'] = log_file
            instance['event_log'] = True

        return {
            'instances': [instance]
        }

    @classmethod
    def parse_nagios_config(cls, filename):
        output = {}
        keys = [
            'log_file',
            'host_perfdata_file_template',
            'service_perfdata_file_template',
            'host_perfdata_file',
            'service_perfdata_file',
        ]

        f = open(filename)
        for line in f:
            line = line.strip()
            if not line:
                continue
            for key in keys:
                if line.startswith(key + '='):
                    eq_pos = line.find('=')
                    if eq_pos:
                        output[key] = line[eq_pos + 1:]
                        break
        f.close()
        return output


class NagiosLogParser(LogParser):

    # Event types we know about but decide to ignore in the parser
    IGNORE_EVENT_TYPES = []

    # fields order for each event type, as named tuples
    EVENT_FIELDS = {
        'CURRENT HOST STATE':       namedtuple('E_CurrentHostState', 'host, event_state, event_soft_hard, return_code, payload'),
        'CURRENT SERVICE STATE':    namedtuple('E_CurrentServiceState', 'host, check_name, event_state, event_soft_hard, return_code, payload'),
        'SERVICE ALERT':            namedtuple('E_ServiceAlert', 'host, check_name, event_state, event_soft_hard, return_code, payload'),
        'PASSIVE SERVICE CHECK':    namedtuple('E_PassiveServiceCheck', 'host, check_name, return_code, payload'),
        'HOST ALERT':               namedtuple('E_HostAlert', 'host, event_state, event_soft_hard, return_code, payload'),

        # [1305744274] SERVICE NOTIFICATION: ops;ip-10-114-237-165;Metric ETL;ACKNOWLEDGEMENT (CRITICAL);notify-service-by-email;HTTP CRITICAL: HTTP/1.1 503 Service Unavailable - 394 bytes in 0.010 second response time;datadog;alq
        'SERVICE NOTIFICATION':     namedtuple('E_ServiceNotification', 'contact, host, check_name, event_state, notification_type, payload'),

        # [1296509331] SERVICE FLAPPING ALERT: ip-10-114-97-27;cassandra JVM Heap;STARTED; Service appears to have started flapping (23.4% change >= 20.0% threshold)
        # [1296662511] SERVICE FLAPPING ALERT: ip-10-114-97-27;cassandra JVM Heap;STOPPED; Service appears to have stopped flapping (3.8% change < 5.0% threshold)
        'SERVICE FLAPPING ALERT':   namedtuple('E_FlappingAlert', 'host, check_name, flap_start_stop, payload'),
<commit_msg>ensure that the file opened to parse nagios config is properly closed<commit_after>import re
from checks.log_parser import LogParserCheck, LogParser
from util import namedtuple, get_hostname


class NagiosParsingError(Exception): pass
class InvalidDataTemplate(Exception): pass


class Nagios(LogParserCheck):

    key = "Nagios"

    def __init__(self, name, init_config, agentConfig):
        LogParserCheck.__init__(self, name, init_config, agentConfig)

    def create_event(self, event_data):
        timestamp, event_type, fields = event_data

        # FIXME Oli: kind of ugly to have to go through a named dict for this, and inefficient too
        # but couldn't think of anything smarter
        d = fields._asdict()
        d.update({'timestamp': timestamp, 'event_type': event_type, 'api_key': self.agentConfig.get('api_key', '')})
        # if host is localhost, turn that into the internal host name
        host = d.get('host', None)
        if host == "localhost":
            d["host"] = get_hostname(self.agentConfig)

        self.log.debug("Nagios event: %s" % (d))
        self.event(d)

    def check(self, instance):

        # Check arguments
        cfg_path = instance.get('cfg_file', None)
        log_path = instance.get('log_file', None)
        event_log = instance.get('event_log', True)
        perf_data = instance.get('perf_data', False)
        if cfg_path is None and log_path is None:
            raise Exception("Not checking nagios because 'cfg_file' is not set in nagios config")

        tags = instance.get('tags', None)

        instance_key = self._instance_key(instance)

        parsers = []
        if instance_key in self.parsers:
            parsers = self.parsers[instance_key]

        # Build our tail -f
        if len(parsers) == 0:
            nagios_config = {}
            if cfg_path:
                nagios_config = self.parse_nagios_config(cfg_path)

                if perf_data:
                    host_parser = NagiosHostPerfData(self.log, nagios_config)
                    service_parser = NagiosServicePerfData(self.log, nagios_config)

                    if not host_parser.tail and not service_parser.tail:
                        raise Exception("The Nagios check is configured to check performance data, but failed to parse any of the perfdata files. Please ensure that the file and file_template fields for at least one of host or service perfdata is filled in correctly in the nagios config file.")

                    if host_parser.tail:
                        parsers.append(host_parser)
                    if service_parser.tail:
                        parsers.append(service_parser)

            if event_log:
                log_file = nagios_config.get('log_file', log_path)
                if not log_file:
                    raise Exception("Nagios check is configured to parse the event log, but failed to do so. Please ensure the value for log_file is correct.")

                nagios_log_parser = NagiosLogParser(self.log, log_file)
                parsers.append(nagios_log_parser)

            self.parsers[instance_key] = parsers

        # read until the end of file
        for parser in parsers:
            parser.parse_file()
            events = parser._get_events()
            for event in events:
                self.create_event(event)

            metrics = parser._get_metrics()
            for metric in metrics:
                self.gauge(metric['metric'], float(metric['value']), tags=tags, hostname=metric['hostname'], device_name=metric['device_name'])

    @staticmethod
    def parse_agent_config(agentConfig):
        cfg_file = agentConfig.get('nagios_perf_cfg', None)
        log_file = agentConfig.get('nagios_log', None)

        if not log_file and not cfg_file:
            return False

        instance = {}

        if cfg_file:
            instance['cfg_file'] = cfg_file
            instance['perf_data'] = True

        if log_file:
            instance['log_file'] = log_file
            instance['event_log'] = True

        return {
            'instances': [instance]
        }

    @classmethod
    def parse_nagios_config(cls, filename):
        output = {}
        keys = [
            'log_file',
            'host_perfdata_file_template',
            'service_perfdata_file_template',
            'host_perfdata_file',
            'service_perfdata_file',
        ]

        f = None
        try:
            f = open(filename)
            for line in f:
                line = line.strip()
                if not line:
                    continue
                for key in keys:
                    if line.startswith(key + '='):
                        eq_pos = line.find('=')
                        if eq_pos:
                            output[key] = line[eq_pos + 1:]
                            break
        finally:
            if f is not None:
                f.close()
        return output


class NagiosLogParser(LogParser):

    # Event types we know about but decide to ignore in the parser
    IGNORE_EVENT_TYPES = []

    # fields order for each event type, as named tuples
    EVENT_FIELDS = {
        'CURRENT HOST STATE':       namedtuple('E_CurrentHostState', 'host, event_state, event_soft_hard, return_code, payload'),
        'CURRENT SERVICE STATE':    namedtuple('E_CurrentServiceState', 'host, check_name, event_state, event_soft_hard, return_code, payload'),
        'SERVICE ALERT':            namedtuple('E_ServiceAlert', 'host, check_name, event_state, event_soft_hard, return_code, payload'),
        'PASSIVE SERVICE CHECK':    namedtuple('E_PassiveServiceCheck', 'host, check_name, return_code, payload'),
        'HOST ALERT':               namedtuple('E_HostAlert', 'host, event_state, event_soft_hard, return_code, payload'),

        # [1305744274] SERVICE NOTIFICATION: ops;ip-10-114-237-165;Metric ETL;ACKNOWLEDGEMENT (CRITICAL);notify-service-by-email;HTTP CRITICAL: HTTP/1.1 503 Service Unavailable - 394 bytes in 0.010 second response time;datadog;alq
        'SERVICE NOTIFICATION':     namedtuple('E_ServiceNotification', 'contact, host, check_name, event_state, notification_type, payload'),

        # [1296509331] SERVICE FLAPPING ALERT: ip-10-114-97-27;cassandra JVM Heap;STARTED; Service appears to have started flapping (23.4% change >= 20.0% threshold)
        # [1296662511] SERVICE FLAPPING ALERT: ip-10-114-97-27;cassandra JVM Heap;STOPPED; Service appears to have stopped flapping (3.8% change < 5.0% threshold)
        'SERVICE FLAPPING ALERT':   namedtuple('E_FlappingAlert', 'host, check_name, flap_start_stop, payload'),

{'fraction_non_alphanumeric': 0.08898077953901525, 'fraction_numerical': 0.012711539934145035, 'mean_word_length': 2.313039066463724, 'pattern_counts': {'":': 2, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6392613', 'n_tokens_mistral': 3830, 'n_tokens_neox': 3609, 'n_words': 1014}