--- /dev/null
+#!/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",
+ "☠",
+ 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) )