]> git.treefish.org Git - logalert.git/commitdiff
initial commit
authorAlexander Schmidt <alex@treefish.org>
Mon, 5 Oct 2020 11:54:08 +0000 (13:54 +0200)
committerAlexander Schmidt <alex@treefish.org>
Mon, 5 Oct 2020 11:54:08 +0000 (13:54 +0200)
.gitignore [new file with mode: 0644]
src/line.py [new file with mode: 0644]
src/logalert.py [new file with mode: 0755]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..45bc86b
--- /dev/null
@@ -0,0 +1,3 @@
+/run
+__pycache__
+*~
diff --git a/src/line.py b/src/line.py
new file mode 100644 (file)
index 0000000..179d6c9
--- /dev/null
@@ -0,0 +1,4 @@
+class Line:
+    def __init__(self, timestamp, content):
+        self.timestamp = timestamp
+        self.content = content
diff --git a/src/logalert.py b/src/logalert.py
new file mode 100755 (executable)
index 0000000..d240f44
--- /dev/null
@@ -0,0 +1,114 @@
+#!/usr/bin/env python3
+
+import argparse
+import logging
+import os
+import shlex
+import subprocess
+import time
+
+from line import Line
+
+MAX_LINES = 10
+
+def follow(filename):
+    while True:
+        try:
+            with open(filename, "r") as f:
+                logging.info("Re-attached to log file.")
+                for line in f: pass
+                while True:
+                    line = f.readline()
+                    if not line:
+                        if not os.path.exists(filename):
+                            break
+                        else:
+                            time.sleep(1.0)
+                            yield None
+                    else:
+                        yield line.rstrip("\n")
+        except FileNotFoundError:
+            time.sleep(1.0)
+            yield None
+
+def feed_handler(data):
+    try:
+        handler = subprocess.Popen(shlex.split(args.handler),
+                                   stdin=subprocess.PIPE,
+                                   stdout=subprocess.PIPE,
+                                   stderr=subprocess.PIPE,
+                                   encoding='UTF-8')
+        out_data, err_data = handler.communicate("%s\n" % data)
+        if handler.returncode != 0:
+            logging.warning("Handler exited with non-zero return code %d! (%s)" %
+                            (handler.returncode, err_data))
+    except Exception as e:
+        logging.error("Error feeding handler: %s" % str(e))
+
+def create_msg(title, icon, logfile, text, lines):
+    msg = "<b>%s</b> <i>%s</i> %s" % (title, logfile, icon)
+    msg += "<br>%s" % text
+    msg += "<br><pre>"
+    for line in lines: msg += line + "\n"
+    msg += "</pre>"
+    return msg
+
+logging.basicConfig(format='[%(asctime)s] %(levelname)s: %(message)s',
+                    level=logging.INFO,
+                    datefmt='%m/%d/%Y %H:%M:%S')
+
+parser = argparse.ArgumentParser(description='Alert on excessive number of error log lines.')
+parser.add_argument('logfile', type=str, help='logfile to be watched')
+parser.add_argument('handler', type=str,
+                    help='alert will be delivered to standard input of handler')
+parser.add_argument('-s', '--interval-size', type=int, default=600, dest='interval_size',
+                    help='sample interval size in seconds (default: 600)')
+parser.add_argument('-n', '--num-intervals', type=int, default=6, dest='num_intervals',
+                    help='number of intervals to keep in history (default: 6)')
+
+args = parser.parse_args()
+
+kept_times = []
+lines = []
+last_time = None
+error_state = False
+
+for line in follow(args.logfile):
+    now = int(time.time()) // args.interval_size
+
+    if line != None:
+        if not last_time or now > last_time:
+            kept_times.append(now)
+            last_time = now
+        lines.append(line)
+        if len(lines) > MAX_LINES:
+            lines.pop(0)
+
+    while len(kept_times) > 0 and \
+          kept_times[0] <= now - args.num_intervals:
+        kept_times.pop(0)
+
+    intervals = [False] * args.num_intervals
+    for kept_time in kept_times:
+        intervals[now - kept_time] = True
+
+    logging.debug(intervals)
+
+    if not False in intervals:
+        if not error_state:
+            logging.warning("Entering error state!")
+            error_state = True
+            feed_handler( create_msg("Log Alert",
+                                     "&#9760;",
+                                     args.logfile,
+                                     "Number of errors exceeded!",
+                                     lines) )
+    else:
+        if error_state:
+            logging.info("Leaving error state.")
+            error_state = False
+            feed_handler( create_msg("Log Un-Alert",
+                                     "&#127774;",
+                                     args.logfile,
+                                     "Log is back to normal.",
+                                     lines) )