rename parsers, better journald integration
[logparse.git] / logparse / util.py
index f1e2aa5d6bcf7c9e2d36678387d8675370e5d3b8..46efa3a8372a3a8ca64ad6d7e33861b0eeb33238 100644 (file)
@@ -6,17 +6,20 @@ This module provides the following methods:
     - `getlocaldomain`: get the current machine's domain name
     - `resolve`:        attempt to convert a local/public IP to hostname
     - `readlog`:        read contents of a log file from disk
+And the following classes:
+    - `LogPeriod`:      period to search logs (wrapper for datetime.timedelata)
 """
 
 from datetime import datetime, timedelta
 import copy
+from configparser import NoSectionError
 import ipaddress
 import logging
 import os
 from pkg_resources import Requirement, resource_filename
 import re
 import socket
-from systemd import journal
+from sys import exit
 
 from logparse import config, formatting
 from logparse.timeparse import timeparse
@@ -81,7 +84,8 @@ def resolve(ip, fqdn=None):        # try to resolve an ip to hostname
         hn = socket.gethostbyaddr(ip)[0] # resolve ip to hostname
     except socket.herror:
         # cannot resolve ip
-        logger.debug(ip + " cannot be found, might not exist anymore")
+        # LOGGING DISABLED TO AVOID SPAM
+#        logger.debug(ip + " cannot be found, might not exist anymore")
         return(ip)
     except Exception as err:
         logger.warning("Failed to resolve hostname for " + ip + ": " + str(err))
@@ -112,21 +116,105 @@ def readlog(path = None, mode = 'r'):
             try:
                 return open(path, mode).read()
             except IOError or OSError as e:
-                logger.warning("Error reading log at {0}: {1}"
+                logger.error("Error reading log at {0}: {1}"
                         .format(path, e.strerror))
                 return 1
 
 class LogPeriod:
+    """
+    Represents a time period for which logs should be parsed (this is given to
+    journald.seek_realtime). Uses timeparse.py by Will Roberts.
+    """
 
     def __init__(self, section):
-        if config.prefs.get(section.split("_")[0], "period"):
+        """
+        If no period is defined for the section config, it is inherited from
+        the global config. Failing that, the program will die.
+        """
+        try:
             self.startdate = datetime.now() \
                 - timeparse(config.prefs.get(section.split("_")[0], "period"))
             logger.debug("Parsing logs for {0} since {1}".format(
                 section, self.startdate.strftime(formatting.DATEFMT
                     + " " + formatting.TIMEFMT)))
             self.unique = True
-        else:
+        except (NoSectionError, TypeError):
+            logger.debug("No period defined in section {0} - inheriting "
+                    "global period".format(section))
             self.startdate = datetime.now() \
                 - timeparse(config.prefs.get("logparse", "period"))
             self.unique = False
+        except Exception as e:
+            logger.error("Could not find valid time period for parser {0}"
+                    " {1}".format(section, e))
+            return None
+
+    def compare(self, d, ephemeral=False) -> bool:
+        """
+        Compare the datetime `d` of a log record with the starting datetime of 
+        this LogPeriod and return a boolean result representing whether the 
+        log record is after the starting datetime. If either `self.startdate` 
+        or `d` are UTC-naive, an appropriate correction is attempted. The 
+        `ephemeral` boolean argument, if set to true, prevents permanent 
+        modification of `self.startdate` to match the UTC-awareness of the 
+        section's log records, in the case that `d` may represent an anomaly.
+        """
+        try:
+            # First attempt a direct comparison
+            return True if d > self.startdate else False
+
+        except TypeError as e:
+            # If the direct comparison fails, make sure that both objects have 
+            # a UTC offset
+
+            if d.tzinfo is None \
+                    or (d.tzinfo is not None
+                    and not d.tzinfo.utcoffset(d)):
+                # d has no timezone info,
+                # OR d has timezone info but no offset
+
+                if self.startdate.tzinfo is not None \
+                        and self.startdate.tzinfo.utcoffset(self.startdate):
+                    # If d is naive and self.startdate is aware, assume that
+                    # the timezone of d is the same as self.startdate by
+                    # making self.startdate naive
+
+                    logger.warning("{0} checking date {1}: {2}. Time "
+                            "of log record is naive. Inheriting UTC "
+                            "offset for logs from system date".format(
+                        e.__class__.__name__, d, e))
+                    if ephemeral:
+                        # Compare with UTC-naive version of self.startdate
+                        return True if d > self.startdate.replace(tzinfo=None) \
+                                else False
+                    else:
+                        # Remove UTC awareness for self.startdate
+                        self.startdate = self.startdate.replace(tzinfo=None)
+                        return True if d > self.startdate else False
+
+            elif self.startdate.tzinfo is None \
+                    or (self.startdate.tzinfo is not None
+                    and not self.startdate.tzinfo.utcoffset(self.startdate)):
+                # d has timezoneinfo and offset, but self.startdate has either 
+                # no timezone info, or timezone  info and no offset. In this 
+                # case, self.startdate inherits the offset of d.
+
+                logger.warning("{0} checking date {1}: {2}. Time of "
+                        "start date is naive. Inheriting UTC offset "
+                        "for date comparison from logs".format(
+                    e.__class__.__name__, d, e))
+
+                if ephemeral:
+                    # Compare with UTC-aware version of self.startdate
+                    return True if d > self.startdate.astimezone(d.tzinfo) \
+                            else False
+                else:
+                    # Add UTC awareness for self.startdate
+                    self.startdate = self.startdate.astimezone(d.tzinfo)
+                    return True if d > self.startdate else False
+                        
+            else:
+                # Other errors return false (effectively ignore this record)
+                logger.error("{0} comparing date {1}: {2}".format(
+                    e.__class__.__name__, d, e))
+                return False