1""" 2Commonly used general functions. 3 4This module provides the following methods: 5 - `hostname`: get the current machine's hostname 6 - `getlocaldomain`: get the current machine's domain name 7 - `resolve`: attempt to convert a local/public IP to hostname 8 - `readlog`: read contents of a log file from disk 9And the following classes: 10 - `LogPeriod`: period to search logs (wrapper for datetime.timedelata) 11""" 12 13from datetime import datetime, timedelta 14import copy 15from configparser import NoSectionError 16import ipaddress 17import logging 18import os 19from pkg_resources import Requirement, resource_filename 20import re 21import socket 22from sys import exit 23 24from logparse import config, formatting 25from logparse.timeparse import timeparse 26 27 28logger = logging.getLogger(__name__) 29 30 31defhostname(path):# get the hostname of current server 32""" 33 Get the hostname of the current machine using the file supplied in the 34 `hostname-path` config option. 35 """ 36 37 hnfile =open(path,'r') 38 hn = re.search('^(\w*)\n*', hnfile.read()).group(1) 39return hn 40 41 42defgetlocaldomain():# get the parent fqdn of current server 43""" 44 Get parent domain name (possibly FQDN) of the current machine. Note: if 45 `socket.fetfqdn()` returns localhost, make sure the first entry in the 46 hostname file includes the FQDN. 47 """ 48 49 domain = socket.getfqdn().split('.',1) 50iflen(domain) !=2: 51 logger.warning("Could not get domain of this server, only hostname. " 52"Please consider updating the hostname file at{0}".format( 53 config.prefs.get("logparse","hostname-path"))) 54return'localdomain' 55else: 56return domain[-1] 57 58 59defresolve(ip, fqdn=None):# try to resolve an ip to hostname 60""" 61 Attempt to resolve an IP into a hostname or FQDN. 62 Possible values for fqdn: 63 - fqdn show full hostname and domain 64 - fqdn-implicit show hostname and domain unless local 65 - host-only only show hostname 66 - ip never resolve anything 67 Note resolve-domains settings defined in individual sections of the config 68 take priority over the global config (this is enforced in parser modules) 69 """ 70 71if not fqdn: 72 fqdn = config.prefs.get("logparse","resolve-domains") 73 74if fqdn =='ip': 75return(ip) 76 77try: 78 ip_obj = ipaddress.ip_address(ip) 79exceptValueErroras err: 80 logger.debug("Invalid format: "+str(err)) 81return ip 82 83try: 84 hn = socket.gethostbyaddr(ip)[0]# resolve ip to hostname 85except socket.herror: 86# cannot resolve ip 87# LOGGING DISABLED TO AVOID SPAM 88# logger.debug(ip + " cannot be found, might not exist anymore") 89return(ip) 90exceptExceptionas err: 91 logger.warning("Failed to resolve hostname for "+ ip +": "+str(err)) 92return(ip)# return ip if no hostname exists 93 94if(fqdn =="host-only")or(fqdn =="fqdn-implicit"and ip_obj.is_private): 95return hn.split('.')[0] 96if fqdn =='fqdn'or fqdn =='fqdn-implicit': 97return hn 98return hn 99 100 101 102defreadlog(path =None, mode ='r'): 103""" 104 Read a logfile from disk and return string 105 """ 106 107if(path ==None): 108 logger.error("No path provided") 109return1 110else: 111if(os.path.isfile(path)is False): 112 logger.error("Log at{0}was requested but does not exist" 113.format(path)) 114return'' 115else: 116try: 117returnopen(path, mode).read() 118exceptIOErrororOSErroras e: 119 logger.error("Error reading log at{0}:{1}" 120.format(path, e.strerror)) 121return1 122 123class LogPeriod: 124""" 125 Represents a time period for which logs should be parsed (this is given to 126 journald.seek_realtime). Uses timeparse.py by Will Roberts. 127 """ 128 129def__init__(self, section): 130""" 131 If no period is defined for the section config, it is inherited from 132 the global config. Failing that, the program will die. 133 """ 134try: 135 self.startdate = datetime.now() \ 136-timeparse(config.prefs.get(section.split("_")[0],"period")) 137 logger.debug("Parsing logs for{0}since{1}".format( 138 section, self.startdate.strftime(formatting.DATEFMT 139+" "+ formatting.TIMEFMT))) 140 self.unique =True 141except(NoSectionError,TypeError): 142 logger.debug("No period defined in section{0}- inheriting " 143"global period".format(section)) 144 self.startdate = datetime.now() \ 145-timeparse(config.prefs.get("logparse","period")) 146 self.unique =False 147exceptExceptionas e: 148 logger.error("Could not find valid time period for parser{0}" 149"{1}".format(section, e)) 150return None 151 152defcompare(self, d, ephemeral=False) ->bool: 153""" 154 Compare the datetime `d` of a log record with the starting datetime of 155 this LogPeriod and return a boolean result representing whether the 156 log record is after the starting datetime. If either `self.startdate` 157 or `d` are UTC-naive, an appropriate correction is attempted. The 158 `ephemeral` boolean argument, if set to true, prevents permanent 159 modification of `self.startdate` to match the UTC-awareness of the 160 section's log records, in the case that `d` may represent an anomaly. 161 """ 162try: 163# First attempt a direct comparison 164return True if d > self.startdate else False 165 166exceptTypeErroras e: 167# If the direct comparison fails, make sure that both objects have 168# a UTC offset 169 170if d.tzinfo is None \ 171or(d.tzinfo is not None 172and not d.tzinfo.utcoffset(d)): 173# d has no timezone info, 174# OR d has timezone info but no offset 175 176if self.startdate.tzinfo is not None \ 177and self.startdate.tzinfo.utcoffset(self.startdate): 178# If d is naive and self.startdate is aware, assume that 179# the timezone of d is the same as self.startdate by 180# making self.startdate naive 181 182 logger.warning("{0}checking date{1}:{2}. Time " 183"of log record is naive. Inheriting UTC " 184"offset for logs from system date".format( 185 e.__class__.__name__, d, e)) 186if ephemeral: 187# Compare with UTC-naive version of self.startdate 188return True if d > self.startdate.replace(tzinfo=None) \ 189else False 190else: 191# Remove UTC awareness for self.startdate 192 self.startdate = self.startdate.replace(tzinfo=None) 193return True if d > self.startdate else False 194 195elif self.startdate.tzinfo is None \ 196or(self.startdate.tzinfo is not None 197and not self.startdate.tzinfo.utcoffset(self.startdate)): 198# d has timezoneinfo and offset, but self.startdate has either 199# no timezone info, or timezone info and no offset. In this 200# case, self.startdate inherits the offset of d. 201 202 logger.warning("{0}checking date{1}:{2}. Time of " 203"start date is naive. Inheriting UTC offset " 204"for date comparison from logs".format( 205 e.__class__.__name__, d, e)) 206 207if ephemeral: 208# Compare with UTC-aware version of self.startdate 209return True if d > self.startdate.astimezone(d.tzinfo) \ 210else False 211else: 212# Add UTC awareness for self.startdate 213 self.startdate = self.startdate.astimezone(d.tzinfo) 214return True if d > self.startdate else False 215 216else: 217# Other errors return false (effectively ignore this record) 218 logger.error("{0}comparing date{1}:{2}".format( 219 e.__class__.__name__, d, e)) 220return False