From 6b726fdf122e07843cbaafd2f2ea76338a5878da Mon Sep 17 00:00:00 2001 From: Alexander Schmidt Date: Mon, 5 Oct 2020 13:54:08 +0200 Subject: [PATCH] initial commit --- .gitignore | 3 ++ src/line.py | 4 ++ src/logalert.py | 114 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+) create mode 100644 .gitignore create mode 100644 src/line.py create mode 100755 src/logalert.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..45bc86b --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/run +__pycache__ +*~ diff --git a/src/line.py b/src/line.py new file mode 100644 index 0000000..179d6c9 --- /dev/null +++ b/src/line.py @@ -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 index 0000000..d240f44 --- /dev/null +++ b/src/logalert.py @@ -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 = "%s %s %s" % (title, logfile, icon) + msg += "
%s" % text + msg += "
"
+    for line in lines: msg += line + "\n"
+    msg += "
" + 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", + "☠", + 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", + "🌞", + args.logfile, + "Log is back to normal.", + lines) ) -- 2.39.5