better log formatting and limit lines to 80 char
authorAndrew Lorimer <andrew@charles.cortex>
Fri, 20 Sep 2019 12:08:15 +0000 (22:08 +1000)
committerAndrew Lorimer <andrew@charles.cortex>
Fri, 20 Sep 2019 12:08:15 +0000 (22:08 +1000)
logparse/__init__.py
logparse/formatting.py
logparse/interface.py
logparse/load_parsers.py
logparse/parsers/systemctl.py
logparse/util.py
setup.py
index 36a590d89f25b9c59edc2fa352faec9441b65c97..41c6100ceb4a17f25dce2533088a439d04525a9e 100644 (file)
@@ -1,2 +1,99 @@
+"""
+This file sets up logging for the entire logparse package. Custom log formatter
+and handler classes are specified below and the logger is supplied to all
+subsequent modules, including parsers.
+"""
+
 __version__ = '2.0'
 __name__ = 'logparse'
+
+
+from copy import copy
+import logging
+import logging.handlers
+
+
+# Standard shell escape codes
+ESC = {
+        "reset": "\033[0m",
+        "color": "\033[1;%dm",
+        "bold": "\033[1m",
+        "underlined": "\033[4m"
+    }
+
+# Standard shell colour codes (30..39 are the foreground colors)
+DEFAULT = 39
+BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN = range(30, 37)
+
+# Map colours to log levels (used for level text only)
+COLORS = {
+        10: BLUE,       # debug
+        20: DEFAULT,    # info
+        30: YELLOW,     # warning
+        40: RED,        # error
+        50: RED         # critical
+    }
+
+# Template for formatting log messages
+FORMAT = ("{bold}%(name)-15s{reset}  %(levelname)-18s  %(message)s "
+        "({bold}%(filename)s{reset}:%(lineno)d)")
+
+
+class ColoredFormatter(logging.Formatter):
+    """
+    Custom implementation of a log formatter to apply standard shell colour
+    escape sequences depending on the log level. The original record is copied
+    so as to not interfere with subsequent handlings (i.e. the syslog handler).
+    """
+
+    def __init__(self, msg):
+        logging.Formatter.__init__(self, msg)
+
+    def format(self, record):
+        temprecord = copy(record)
+        levelname = temprecord.levelname
+        if temprecord.levelno in COLORS:
+            levelname_color = ESC["color"] % (COLORS[temprecord.levelno]) \
+                + levelname + ESC["reset"]
+            temprecord.levelname = levelname_color
+        temprecord.name = record.name.replace(__name__ + ".", "")
+        return logging.Formatter.format(self, temprecord)
+
+
+class ColoredLogger(logging.Logger):
+    """
+    Custom implementation of a logger object using the `ColoredFormatter` class
+    above. This class also includes a syslog handler to direct a minimal amount
+    of output to /dev/log.
+    """
+
+    message = FORMAT.format(**ESC)
+
+    def __init__(self, name):
+        """
+        Initialise the logger for the entire package. This is done here so that
+        the configuration is applied to all child modules. A syslog handler
+        is also initialised, with a min level of INFO so that journald doesn't
+        get spammed with debug messages..
+        """
+
+        logging.Logger.__init__(self, name)
+
+        color_formatter = ColoredFormatter(self.message)
+
+        syslog_handler = logging.handlers.SysLogHandler(address = '/dev/log')
+        syslog_handler.setLevel(logging.INFO)
+        syslog_handler.setFormatter(logging.Formatter(
+            fmt='{}[%(process)d]: (%(module)s) %(message)s'.format(__name__)))
+
+        console = logging.StreamHandler()
+        console.setFormatter(color_formatter)
+
+        self.addHandler(console)
+        self.addHandler(syslog_handler)
+        return
+
+
+# Initialise logger object
+logging.setLoggerClass(ColoredLogger)
+logger = logging.getLogger()
index 00ea9e45f663d71836bcc1a336d0eb0c4e3c2c08..8528e1b262413bc00dd477b62b7a8298ff7da47b 100644 (file)
@@ -1,4 +1,3 @@
-#!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
 """   
@@ -19,6 +18,7 @@ import logparse
 from logparse import interface, util, mail, config
 
 import logging
+logger = None
 logger = logging.getLogger(__name__)
 
 
@@ -54,14 +54,17 @@ def init_var():
         if interface.argparser.parse_args().no_write:
             css_path = os.path.relpath(css_path, ".")
         elif interface.argparser.parse_args().destination:
-            css_path = os.path.relpath(css_path, interface.argparser.parse_args().destination())
+            css_path = os.path.relpath(
+                    css_path, interface.argparser.parse_args().destination())
         elif config.prefs.get("logparse", "output"):
-            css_path = os.path.relpath(css_path, config.prefs.get("logparse", "output"))
+            css_path = os.path.relpath(
+                    css_path, config.prefs.get("logparse", "output"))
     VARSUBST = {
         "title": config.prefs.get("logparse", "title"),
         "date": interface.start.strftime(DATEFMT),
         "time": interface.start.strftime(TIMEFMT),
-        "hostname": util.hostname(config.prefs.get("logparse", "hostname-path")),
+        "hostname": util.hostname(config.prefs.get(
+            "logparse", "hostname-path")),
         "version": logparse.__version__,
         "css": css_path
     }
@@ -104,7 +107,8 @@ class Output:
 
         print()
         if lines:
-            line = PlaintextLine(linewidth=config.prefs.getint("plain", "linewidth"), double=True)
+            line = PlaintextLine(linewidth=
+                    config.prefs.getint("plain", "linewidth"), double=True)
             print(line.draw())
         print(self.content)
         if lines:
@@ -114,7 +118,8 @@ class Output:
 
 class PlaintextOutput(Output):
     """
-    Processes & outputs data in a plaintext form which can be read with cat or plaintext email.
+    Processes & outputs data in a plaintext form which can be read with cat or
+    plaintext email.
     """
 
     def __init__(self, linewidth=80):
@@ -126,7 +131,10 @@ class PlaintextOutput(Output):
         """
         Print details with some primitive formatting
         """
-        box = PlaintextBox(content=Template("$title $version on $hostname\n\n$time $date").safe_substitute(VARSUBST), vpadding=2, hpadding="\t\t", linewidth=self.linewidth)
+        box = PlaintextBox(content=
+                Template("$title $version on $hostname\n\n$time $date")
+                .safe_substitute(VARSUBST),
+                vpadding=2, hpadding="\t\t", linewidth=self.linewidth)
         line = PlaintextLine(self.linewidth)
         self.append(box.draw() + line.draw())
 
@@ -143,9 +151,12 @@ class PlaintextOutput(Output):
         This should be run by interface.py after every instance of parse_log().
         """
 
-        self.append(PlaintextBox(content=section.title, double=False, fullwidth=False, vpadding=0, hpadding=" ").draw())
+        self.append(PlaintextBox(
+            content=section.title, double=False,
+            fullwidth=False, vpadding=0, hpadding=" ").draw())
         if section.period and section.period.unique:
-            self.append("\n(since {0})".format(section.period.startdate.strftime(DATEFMT + " " + TIMEFMT)))
+            self.append("\n(since {0})".format(
+                section.period.startdate.strftime(DATEFMT + " " + TIMEFMT)))
         self.append('\n'*2)
         for data in section.data:
             self.append(self._fmt_data(data.subtitle, data.items))
@@ -218,7 +229,9 @@ class HtmlOutput(Output):
         """
 
         if not self._embedded:
-            self._embedded = mail.mailprep(re.sub(".*" + re.escape(VARSUBST['css']) + ".*\n", "", self.content), css)
+            self._embedded = mail.mailprep(re.sub(
+                ".*" + re.escape(VARSUBST['css']) + ".*\n", "", self.content),
+                css)
         return self._embedded
 
     def write_embedded(self, destination = ""):
@@ -261,8 +274,9 @@ class HtmlOutput(Output):
 
     def append_section(self, section):
         """
-        Call the appropriate methods to generate HTML tags for a section (provided by a parser).
-        This should be run by interface.py after every instance of parse_log().
+        Call the appropriate methods to generate HTML tags for a section
+        (provided by a parser). This should be run by interface.py after every
+        instance of parse_log().
         """
 
         self.append(opentag('div', 1, section.title, 'section'))
@@ -304,10 +318,12 @@ class HtmlOutput(Output):
             logger.debug("Received data " + str(data))
             subtitle += ':'
             if (len(data) == 1):
-                return tag('p', False, subtitle + ' ' + data[0], cl="severity-" + str(severity))
+                return tag('p', False, subtitle + ' ' + data[0],
+                        cl="severity-" + str(severity))
             else:
                 output = ""
-                output += tag('p', False, subtitle, cl="severity-" + str(severity))
+                output += tag('p', False, subtitle,
+                        cl="severity-" + str(severity))
                 output += opentag('ul', 1)
                 coderegex = re.compile('`(.*)`')
                 for datum in data:
@@ -334,7 +350,8 @@ class HtmlOutput(Output):
             self.embed_css(config.prefs.get("html", "css"))
         print()
         if lines:
-            line = PlaintextLine(linewidth=config.prefs.getint("plain", "linewidth"), double=True)
+            line = PlaintextLine(linewidth=
+                    config.prefs.getint("plain", "linewidth"), double=True)
             print(line.draw())
         print(self._embedded)
         if lines:
@@ -382,7 +399,8 @@ class Data:
 
     def truncl(self, limit):      # truncate list
         """
-        Truncate self.items to a specified value and state how many items are hidden.
+        Truncate self.items to a specified value and state how many items are
+        hidden.
         """
 
         if (len(self.items) > limit):
@@ -400,7 +418,8 @@ class Data:
         """
 
         unsorted = list(self.items)
-        self.items = ["{0} ({1})".format(y, unsorted.count(y)) for y in sorted(set(unsorted), key = lambda x: -unsorted.count(x))]
+        self.items = ["{0} ({1})".format(y, unsorted.count(y)) for y in sorted(
+            set(unsorted), key = lambda x: -unsorted.count(x))]
         return self
 
 
@@ -409,7 +428,8 @@ class Table(object):
     A wrapper for python-tabulate's Tabulate type.
     """
     
-    def __init__(self, double=False, borders=False, hpadding=" ", maxwidth=80, headers=[]):
+    def __init__(self, double=False, borders=False, hpadding=" ",
+            maxwidth=80, headers=[]):
         """
         Initialise variables. Note the keymap is used for a faster index map,
         but is not currently used anywhere (may be removed in future).
@@ -446,7 +466,7 @@ class Table(object):
         self._align_cols[i] = align
         for row in self.rows:
             row.columns[i].align = align
-        logger.debug("Column alignment is now {0}".format(str(self._align_cols)))
+        logger.debug("Column alignment is now {0}".format(self._align_cols))
 
     def _gen_list(self):
         """
@@ -466,7 +486,8 @@ class Table(object):
         Output HTML string (wrapper for tabulate)
         """
 
-        output = tabulate(self._gen_list(), self.headers, tablefmt="html", colalign=tuple(self._align_cols))
+        output = tabulate(self._gen_list(), self.headers, tablefmt="html",
+                colalign=tuple(self._align_cols))
         return output
 
     def draw_plain(self):
@@ -474,7 +495,9 @@ class Table(object):
         Output plain text string (wrapper for tabulate)
         """
 
-        output = tabulate(self._gen_list(), self.headers, tablefmt="fancy_grid" if self.borders else "plain", colalign=tuple(self._align_cols))
+        output = tabulate(self._gen_list(), self.headers,
+                tablefmt="fancy_grid" if self.borders
+                else "plain", colalign=tuple(self._align_cols))
         return output + "\n"*2
 
 
@@ -549,7 +572,9 @@ class PlaintextLine:
         """
 
         line = (LINECHARS_DOUBLE[1] if self.double else LINECHARS_SINGLE[1])
-        return "\n" * self.vpadding + self.hpadding +  line * (self.linewidth - 2 * len(self.hpadding)) + self.hpadding + "\n" * self.vpadding
+        return "\n" * self.vpadding + self.hpadding \
+                +  line * (self.linewidth - 2 * len(self.hpadding)) \
+                + self.hpadding + "\n" * self.vpadding
 
 
 class PlaintextBox:
@@ -557,7 +582,8 @@ class PlaintextBox:
     Draw a rectangular box around text, with customisable padding/size/style
     """
 
-    def __init__(self, content="", double=True, fullwidth=True, linewidth=80, hpadding="\t", vpadding=1):
+    def __init__(self, content="", double=True, fullwidth=True, linewidth=80,
+            hpadding="\t", vpadding=1):
         """
         Initialise variables
         """
@@ -587,9 +613,12 @@ class PlaintextBox:
 
         # Calculate number of characters per line
         contentlines = self.content.splitlines()
-        contentwidth = int((self.linewidth if self.linewidth > 0 else 80) if self.content.splitlines() else len(max(contentlines, key=len)))
+        contentwidth = int((self.linewidth if self.linewidth > 0 else 80)
+                if self.content.splitlines()
+                else len(max(contentlines, key=len)))
         logger.debug("Contentwidth is {0}".format(str(contentwidth)))
-        logger.debug("Longest line is {0}".format(len(max(contentlines, key=len))))
+        logger.debug("Longest line is {0}".format(
+            len(max(contentlines, key=len))))
         contentwidth += -2*(len(self.hpadding)+1)
         if not self.fullwidth:
             longestline = len(max(contentlines, key=len))
@@ -614,7 +643,8 @@ class PlaintextBox:
                 contentlines[i] = res
 
         # Flatten list
-        #   Note list comprehension doesn't work here, so we must iterate through each item
+        #   Note list comprehension doesn't work here, so we must iterate
+        #   through each item
         newlines = []
         for line in contentlines:
             if isinstance(line, list):
@@ -630,9 +660,14 @@ class PlaintextBox:
             contentlines.append(' '*contentwidth)
 
         # Insert horizontal padding on lines that are too short
-        contentlines = [linechars[0] + self.hpadding + x + ' '*(self.linewidth-(len(x)+2*len(self.hpadding)+2) if len(x) < contentwidth else 0) + self.hpadding + linechars[0] for x in contentlines]
-        contentlines.insert(0, cornerchars[3] + linechars[1] * (contentwidth + len(self.hpadding)*2) + cornerchars[2])
-        contentlines.append(cornerchars[0] + linechars[1] * (contentwidth + len(self.hpadding)*2) + cornerchars[1])
+        contentlines = [linechars[0] + self.hpadding + x
+                + ' '*(self.linewidth-(len(x)+2*len(self.hpadding)+2)
+                    if len(x) < contentwidth else 0)
+                + self.hpadding + linechars[0] for x in contentlines]
+        contentlines.insert(0, cornerchars[3] + linechars[1] 
+                * (contentwidth + len(self.hpadding)*2) + cornerchars[2])
+        contentlines.append(cornerchars[0] + linechars[1]
+                * (contentwidth + len(self.hpadding)*2) + cornerchars[1])
         return ('\n').join(contentlines)
 
 
@@ -701,7 +736,8 @@ def opentag(tag, block=False, id=None, cl=None, style=None):
         output += " class='" + cl + "'"
     if style:
         output += " style='"
-        output += " ".join("{0}: {1};".format(attr, value) for attr, value in style.items())
+        output += " ".join("{0}: {1};".format(attr, value)
+                for attr, value in style.items())
         output += "'"
     output += '>'
     if block:
index 770640e30baff9a7d9c552b8140f23dca147569f..bee8d49f4ef46274e6f8d0b87b44b96e0d8c9e00 100644 (file)
@@ -1,5 +1,6 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # -*- coding: utf-8 -*-
+
 """
 This module is the entrypoint of the `logparse` shell command and also contains
 single-use functions which don't fit elsewhere. All user interaction with
@@ -11,15 +12,17 @@ This module provides the following methods:
     - `rotate_sim()`:   Simulate log rotation
 """
 
-import logging, logging.handlers
 import argparse
+from copy import copy
+import logging
+import logging.handlers
 import os
 from sys import stdin, version
 from subprocess import check_output
 from datetime import datetime
 
 import logparse
-from logparse import formatting, mail, config, load_parsers
+from logparse import formatting, mail, config, load_parsers, util
 
 
 def main():
@@ -40,24 +43,18 @@ def main():
                 argparser.parse_args().time_period)
     
     # Set up logging
-
     logger = logging.getLogger(__name__)
-    loghandler = logging.handlers.SysLogHandler(address = '/dev/log')
-    loghandler.setFormatter(logging.Formatter(
-        fmt='logparse[' + str(os.getpid()) + ']: %(message)s'))
-    loghandler.setLevel(logging.INFO)   # don't spam syslog with debug messages
-
     if (argparser.parse_args().quiet
             or config.prefs.getboolean("logparse", "quiet")):
-        logging.basicConfig(level=logging.CRITICAL)
+        logparse.logger.setLevel(logging.CRITICAL)
     elif (argparser.parse_args().verbose 
             or config.prefs.getboolean("logparse", "verbose")):
-        logging.basicConfig(level=logging.DEBUG)
+        logparse.logger.setLevel(logging.DEBUG)
         logger.debug("Verbose mode turned on")
     else:
-        logging.basicConfig(level=logging.INFO)
+        logparse.logger.setLevel(logging.INFO)
+
 
-    logger.addHandler(loghandler)
 
     # Time analysis
 
index c8cf7341aefd50556415d905a0ae790ab245e246..c0ed52450437a2b5739d400a572732aa31b78053 100644 (file)
@@ -1,5 +1,5 @@
-#!/usr/bin/env python
 # -*- coding: utf-8 -*-
+
 """
 A basic "plugin loader" implementation which searches for default packaged and
 user-supplied parser modules and verifies them so they can be executed by
@@ -37,17 +37,18 @@ class Parser():
     logparse.formatting.Section object.
     """
 
-    def __init__(self, name=None, path=None, info=None, deprecated=False, successor=""):
+    def __init__(self, name=None, path=None, info=None, deprecated=False,
+            successor=""):
         """
         The following variables can be set to display information about the
-        parser. The object `self.logger` can be used as for outputting messages
-        to whatever sink is set up in logparse.interface (no setup required in
-        the parser module itself).
+        parser. The object `self.logger` can be used for outputting messages to
+        to whatever sink is set up in __init__.py (no setup required in the
+        parser module itself).
         """
         self.name = str(name) if name else None
         self.path = Path(path) if path else None
         self.info = dict(info) if info else None
-        self.logger = logging.getLogger(__name__)
+        self.logger = logging.getLogger(self.__module__)
         self.deprecated = deprecated
         self.successor = successor
 
@@ -55,7 +56,8 @@ class Parser():
         """
         A generic loading method to import a parser, only used for debugging
         """
-        logger.debug("Loading parser {0} from {1}".format(self.name, str(self.path) if self.path != None else "defaults"))
+        logger.debug("Loading parser {0} from {1}".format(
+            self.name, str(self.path) if self.path != None else "defaults"))
         return importlib.import_module(self.name)
 
     def parse_log(self, **args) -> None:
@@ -102,7 +104,8 @@ class ParserLoader:
                 self.parsers.append(user_parser)
                 return user_parser
             else:
-                logger.warning("Couldn't find a matching parser module for search term {0}".format(pattern))
+                logger.warning("Couldn't find a matching parser module "
+                    "for search term {0}".format(pattern))
                 return None
 
     def _search_user(self, pattern):
@@ -112,7 +115,8 @@ class ParserLoader:
 
         logger.debug("Searching for {0} in {1}".format(pattern, self.path))
         try:
-            spec = importlib.machinery.PathFinder.find_spec(pattern, path=[self.path])
+            spec = importlib.machinery.PathFinder.find_spec(
+                    pattern, path=[self.path])
             parser_module = spec.loader.load_module(spec.name)
             return self._validate_module(parser_module)
         except Exception as e:
@@ -121,9 +125,10 @@ class ParserLoader:
     def _search_default(self, pattern):
         """
         Search for a parser name `pattern` in the default parser package
+        TODO use importlib.resources.is_resources() once there is a backport
+        to Python 3.6 or below
         """
         
-        # TODO use importlib.resources.is_resources() once there is a backport to Python 3.6 or below
         logger.debug("Searching for {0} in default parsers".format(pattern))
         try:
             parser_module = importlib.import_module(self.pkg + "." + pattern)
@@ -141,7 +146,8 @@ class ParserLoader:
             6. Must not match an already-loaded class
         """
 
-        logger.debug("Checking validity of module {0} at {1}".format(parser_module.__name__, parser_module.__file__))
+        logger.debug("Checking validity of module {0} at {1}".format(
+            parser_module.__name__, parser_module.__file__))
         available_parsers = []
         clsmembers = inspect.getmembers(parser_module, inspect.isclass)
 
@@ -150,28 +156,41 @@ class ParserLoader:
             if not (issubclass(c, Parser) & (c is not Parser)):
                 continue
             if c in self.parsers:
-                logger.warning("Parser class {0} has already been loaded from another source, ignoring it".format(c.__class__.__name__, c.__file__))
+                logger.warning("Parser class {0} has already been loaded "
+                    "from another source, ignoring it".format(
+                        c.__class__.__name__, c.__file__))
             if not inspect.isroutine(c.parse_log):
-                logger.warning("Parser class {0} in {1} does not contain a parse_log() method".format(c.__class__.__name__, c.__file__))
+                logger.warning("Parser class {0} in {1} does not contain a "
+                    "parse_log() method".format(
+                        c.__class__.__name__, c.__file__))
                 continue
             if None in get_type_hints(c):
-                logger.warning("Parser class {0} in {1} contains a null-returning parse_log() method".format(c.__class__.__name__, c.__file__))
+                logger.warning("Parser class {0} in {1} contains a "
+                    "null-returning parse_log() method".format(
+                        c.__class__.__name__, c.__file__))
                 continue
             parser_obj = c()
             if parser_obj.deprecated:
-                logger.warning("Parser {0} is deprecated - use {1} instead".format(parser_obj.name, parser_obj.successor))
-            logger.debug("Found parser {0}.{1}".format(c.__module__, c.__class__.__name__))
-            available_parsers.append(c())
+                logger.warning("Parser {0} is deprecated - "
+                    "use {1} instead".format(
+                        parser_obj.name, parser_obj.successor))
+            logger.debug("Found parser {0}.{1}".format(
+                c.__module__, c.__class__.__name__))
+            available_parsers.append(parser_obj)
 
         # Check module structure
         if len(available_parsers) == 1:
-            logger.debug("Parser module {0} at {1} passed validity checks".format(parser_module.__name__, parser_module.__file__))
+            logger.debug("Parser module {0} at {1} passed validity checks"
+                    .format(parser_module.__name__, parser_module.__file__))
             return available_parsers[0]
         elif len(available_parsers) == 0:
-            logger.warning("No valid classes in {0} at {1}".format(parser_module.__name__, parser_module.__file__))
+            logger.warning("No valid classes in {0} at {1}".
+                    format(parser_module.__name__, parser_module.__file__))
             return None
         elif len(available_parsers) > 1:
-            logger.warning("Found multiple valid parser classes in {0} at {1} - ignoring this module".format(parser_module.__name__, parser_module.__file__))
+            logger.warning("Found multiple valid parser classes in {0} at {1} "
+                "- ignoring this module"
+                .format(parser_module.__name__, parser_module.__file__))
             return None
 
     def load_pkg(self):
@@ -180,15 +199,17 @@ class ParserLoader:
         non-deprecated parser classes from self.pkg using importlib.
         """
 
-        available_parsers = [name for _, name, _ in iter_modules([dirname(importlib.import_module(self.pkg).__file__)])]
+        available_parsers = [name for _, name, _ in iter_modules(
+            [dirname(importlib.import_module(self.pkg).__file__)])]
         for parser_name in available_parsers:
-            parser_module = importlib.import_module("logparse.parsers." + parser_name)
+            parser_module = importlib.import_module(
+                    "logparse.parsers." + parser_name)
             parser_class = self._validate_module(parser_module)
             if parser_class == None:
                 continue
-            parser_obj = parser_class
-            if parser_obj.deprecated:
-                logger.debug("Ignoring parser {0} because it is deprecated".format(parser_class.__class__.__name__))
+            if parser_class.deprecated:
+                logger.debug("Ignoring parser {0} because it is deprecated"
+                        .format(parser_class.__class__.__name__))
                 continue
             self.parsers.append(parser_class)
         return self.parsers
index a61747a973254fc1edd181cb0369d29fa4361a28..fe0f64a60ac88f3c5c3f5a1a8ed5b514ebfaaada 100644 (file)
@@ -1,4 +1,5 @@
 # -*- coding: utf-8 -*-
+
 #
 #   systemctl.py
 #   
index e6d905c442a17e78244f055717df98317e05c10e..f1e2aa5d6bcf7c9e2d36678387d8675370e5d3b8 100644 (file)
@@ -9,6 +9,7 @@ This module provides the following methods:
 """
 
 from datetime import datetime, timedelta
+import copy
 import ipaddress
 import logging
 import os
@@ -44,7 +45,9 @@ def getlocaldomain(): # get the parent fqdn of current server
 
     domain = socket.getfqdn().split('.', 1)
     if len(domain) != 2:
-        logger.warning("Could not get domain of this server, only hostname. Please consider updating the hostname file at {0}".format(config.prefs.get("logparse", "hostname-path")))
+        logger.warning("Could not get domain of this server, only hostname. "
+            "Please consider updating the hostname file at {0}".format(
+                config.prefs.get("logparse", "hostname-path")))
         return 'localdomain'
     else:
         return domain[-1]
@@ -102,22 +105,28 @@ def readlog(path = None, mode = 'r'):
         return 1
     else:
         if (os.path.isfile(path) is False):
-            logger.error("Log at {0} was requested but does not exist".format(path))
+            logger.error("Log at {0} was requested but does not exist"
+                    .format(path))
             return ''
         else:
             try:
                 return open(path, mode).read()
             except IOError or OSError as e:
-                logger.warning("Error reading log at {0}: {1}".format(path, e.strerror))
+                logger.warning("Error reading log at {0}: {1}"
+                        .format(path, e.strerror))
                 return 1
 
 class LogPeriod:
 
     def __init__(self, section):
         if config.prefs.get(section.split("_")[0], "period"):
-            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.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:
-            self.startdate = datetime.now() - timeparse(config.prefs.get("logparse", "period"))
+            self.startdate = datetime.now() \
+                - timeparse(config.prefs.get("logparse", "period"))
             self.unique = False
index 8eac615666880056a18a88dad2ed7c3c7a4b41a5..8d246586d46a6fc142b99b1ade492eeac8e71220 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -13,7 +13,7 @@ with open(path.join(here, 'README.md'), encoding='utf-8') as f:
 
 setuptools.setup(
     name='logparse',                    # https://packaging.python.org/specifications/core-metadata/#name
-    version=logparse.__version__,       # https://www.python.org/dev/peps/pep-0440/   https://packaging.python.org/en/latest/single_source_version.html
+    version=__version__,       # https://www.python.org/dev/peps/pep-0440/   https://packaging.python.org/en/latest/single_source_version.html
     description='Summarise server logs',
     long_description_content_type='text/markdown',
     url='https://git.lorimer.id.au/logparse.git',