Linux altar53.supremepanel53.com 4.18.0-553.8.1.lve.el8.x86_64 #1 SMP Thu Jul 4 16:24:39 UTC 2024 x86_64
/ lib/ python2.7/ site-packages/ pynag/ Parsers/ |
|
# -*- coding: utf-8 -*- """Module for parsing and searching for log entries.""" from __future__ import absolute_import import os import re import time import pynag.Parsers.main import pynag.Utils.states from six.moves import filter class LogFiles(object): """ Parses Logfiles defined in nagios.cfg and allows easy access to its content Content is stored in python-friendly arrays of dicts. Output should be more or less compatible with mk_livestatus log output """ def __init__(self, maincfg=None): main_config = pynag.Parsers.main.MainConfig(maincfg) self.log_file = main_config.get('log_file') self.log_archive_path = main_config.get('log_archive_path') def get_log_entries(self, start_time=None, end_time=None, strict=True, search=None, **kwargs): """ Get Parsed log entries for given timeperiod. Args: start_time: unix timestamp. if None, return all entries from today end_time: If specified, only fetch log entries older than this (unix timestamp) strict: If True, only return entries between start_time and end_time, if False, then return entries that belong to same log files as given timeset search: If provided, only return log entries that contain this string (case insensitive) kwargs: All extra arguments are provided as filter on the log entries. f.e. host_name="localhost" Returns: List of dicts """ now = time.time() if end_time is None: end_time = now if start_time is None: if 'filename' in kwargs: start_time = 1 else: seconds_in_a_day = 60 * 60 * 24 seconds_today = end_time % seconds_in_a_day # midnight of today start_time = end_time - seconds_today start_time = int(start_time) end_time = int(end_time) logfiles = self.get_logfiles() if 'filename' in kwargs: logfiles = [x for x in logfiles if x == kwargs.get('filename')] # If start time was provided, skip all files that we last modified # before start_time if start_time: logfiles = [x for x in logfiles if start_time <= os.stat(x).st_mtime] # Log entries are returned in ascending order, which is the opposite of # what get_logfiles returns. logfiles.reverse() result = [] for log_file in logfiles: entries = self._parse_log_file(filename=log_file) if len(entries) == 0: continue first_entry = entries[0] last_entry = entries[-1] if first_entry['time'] > end_time: continue # If strict, filter entries to only include the ones in the timespan if strict is True: entries = [x for x in entries if x['time'] >= start_time and x['time'] <= end_time] # If search string provided, filter the string if search is not None: entries = [x for x in entries if x['message'].lower().find(search.lower()) > -1] for k, v in kwargs.items(): entries = [x for x in entries if x.get(k) == v] result += entries if start_time is None or int(start_time) >= int(first_entry.get('time')): continue # Now, logfiles should in MOST cases come sorted for us. # However we rely on modification time of files and if it is off, # We want to make sure log entries are coming in the correct order. # The following sort should not impact performance in the typical use case. result.sort(key=lambda x: x.get('time')) return result def get_logfiles(self): """ Returns a list with the fullpath to every log file used by nagios. Lists are sorted by modification times. Newest logfile is at the front of the list so usually nagios.log comes first, followed by archivelogs Returns: List of strings """ logfiles = [] for filename in os.listdir(self.log_archive_path): full_path = "%s/%s" % (self.log_archive_path, filename) if not os.path.isfile(full_path): continue logfiles.append(full_path) logfiles.append(self.log_file) # Sort the logfiles by modification time, newest file at the front compare_mtime = lambda a, b: os.stat(a).st_mtime < os.stat(b).st_mtime logfiles.sort(key=lambda x: int(os.stat(x).st_mtime)) # Newest logfiles go to the front of the list logfiles.reverse() return logfiles def get_flap_alerts(self, **kwargs): """ Same as :py:meth:`get_log_entries`, except return timeperiod transitions. Takes same parameters. """ return self.get_log_entries(class_name="timeperiod transition", **kwargs) def get_notifications(self, **kwargs): """ Same as :py:meth:`get_log_entries`, except return only notifications. Takes same parameters. """ return self.get_log_entries(class_name="notification", **kwargs) def get_state_history(self, start_time=None, end_time=None, host_name=None, strict=True, service_description=None): """ Returns a list of dicts, with the state history of hosts and services. Args: start_time: unix timestamp. if None, return all entries from today end_time: If specified, only fetch log entries older than this (unix timestamp) host_name: If provided, only return log entries that contain this string (case insensitive) service_description: If provided, only return log entries that contain this string (case insensitive) Returns: List of dicts with state history of hosts and services """ log_entries = self.get_log_entries(start_time=start_time, end_time=end_time, strict=strict, class_name='alerts') result = [] last_state = {} now = time.time() for line in log_entries: if 'state' not in line: continue line['duration'] = now - int(line.get('time')) if host_name is not None and host_name != line.get('host_name'): continue if service_description is not None and service_description != line.get('service_description'): continue if start_time is None: start_time = int(line.get('time')) short_name = "%s/%s" % (line['host_name'], line['service_description']) if short_name in last_state: last = last_state[short_name] last['end_time'] = line['time'] last['duration'] = last['end_time'] - last['time'] line['previous_state'] = last['state'] last_state[short_name] = line if strict is True: if start_time is not None and int(start_time) > int(line.get('time')): continue if end_time is not None and int(end_time) < int(line.get('time')): continue result.append(line) return result def _parse_log_file(self, filename=None): """ Parses one particular nagios logfile into arrays of dicts. Args: filename: Log file to be parsed. If is None, then log_file from nagios.cfg is used. Returns: A list of dicts containing all data from the log file """ if filename is None: filename = self.log_file result = [] for line in open(filename).readlines(): parsed_entry = self._parse_log_line(line) if parsed_entry != {}: parsed_entry['filename'] = filename result.append(parsed_entry) return result def _parse_log_line(self, line): """ Parse one particular line in nagios logfile and return a dict. Args: line: Line of the log file to be parsed. Returns: dict containing the information from the log file line. """ host = None service_description = None state = None check_attempt = None plugin_output = None contact = None m = re.search('^\[(.*?)\] (.*?): (.*)', line) if m is None: return {} line = line.strip() timestamp, logtype, options = m.groups() result = {} try: timestamp = int(timestamp) except ValueError: timestamp = 0 result['time'] = int(timestamp) result['type'] = logtype result['options'] = options result['message'] = line result['class'] = 0 # unknown result['class_name'] = 'unclassified' if logtype in ('CURRENT HOST STATE', 'CURRENT SERVICE STATE', 'SERVICE ALERT', 'HOST ALERT'): result['class'] = 1 result['class_name'] = 'alerts' if logtype.find('HOST') > -1: # This matches host current state: m = re.search('(.*?);(.*?);(.*);(.*?);(.*)', options) if m is None: return result host, state, hard, check_attempt, plugin_output = m.groups() service_description = None if logtype.find('SERVICE') > -1: m = re.search('(.*?);(.*?);(.*?);(.*?);(.*?);(.*)', options) if m is None: return result host, service_description, state, hard, check_attempt, plugin_output = m.groups() result['host_name'] = host result['service_description'] = service_description try: result['state'] = pynag.Utils.states.service_state_to_int(state) except pynag.Utils.states.UnknownState: result['state'] = pynag.Utils.states.UNKNOWN result['check_attempt'] = check_attempt result['plugin_output'] = plugin_output result['text'] = plugin_output elif "NOTIFICATION" in logtype: result['class'] = 3 result['class_name'] = 'notification' if logtype == 'SERVICE NOTIFICATION': m = re.search('(.*?);(.*?);(.*?);(.*?);(.*?);(.*)', options) if m is None: return result contact, host, service_description, state, command, plugin_output = m.groups() elif logtype == 'HOST NOTIFICATION': m = re.search('(.*?);(.*?);(.*?);(.*?);(.*)', options) if m is None: return result contact, host, state, command, plugin_output = m.groups() service_description = None result['contact_name'] = contact result['host_name'] = host result['service_description'] = service_description try: result['state'] = pynag.Utils.states.service_state_to_int(state) except pynag.Utils.states.UnknownState: result['state'] = pynag.Utils.states.UNKNOWN result['plugin_output'] = plugin_output result['text'] = plugin_output elif logtype == "EXTERNAL COMMAND": result['class'] = 5 result['class_name'] = 'command' m = re.search('(.*?);(.*)', options) if m is None: return result command_name, text = m.groups() result['command_name'] = command_name result['text'] = text elif logtype in ('PASSIVE SERVICE CHECK', 'PASSIVE HOST CHECK'): result['class'] = 4 result['class_name'] = 'passive' if logtype.find('HOST') > -1: # This matches host current state: m = re.search('(.*?);(.*?);(.*)', options) if m is None: return result host, state, plugin_output = m.groups() service_description = None if logtype.find('SERVICE') > -1: m = re.search('(.*?);(.*?);(.*?);(.*)', options) if m is None: return result host, service_description, state, plugin_output = m.groups() result['host_name'] = host result['service_description'] = service_description try: result['state'] = pynag.Utils.states.service_state_to_int(state) except pynag.Utils.states.UnknownState: result['state'] = pynag.Utils.states.UNKNOWN result['plugin_output'] = plugin_output result['text'] = plugin_output elif logtype in ('SERVICE FLAPPING ALERT', 'HOST FLAPPING ALERT'): result['class_name'] = 'flapping' elif logtype == 'TIMEPERIOD TRANSITION': result['class_name'] = 'timeperiod_transition' elif logtype == 'Warning': result['class_name'] = 'warning' result['state'] = "1" result['text'] = options if 'text' not in result: result['text'] = result['options'] result['log_class'] = result['class'] # since class is a python keyword return result