import argparse
import logging
-import os
-import shlex
-import subprocess
import time
-from line import Line
+import misc
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')
+ALERT_INTERVAL = 86400
parser = argparse.ArgumentParser(description='Alert on excessive number of error log lines.')
parser.add_argument('logfile', type=str, help='logfile to be watched')
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)')
+parser.add_argument('-l', '--log-level', type=str, default='INFO', dest='log_lvl',
+ choices=['DEBUG', 'INFO', 'WARNING'], help='select log level')
args = parser.parse_args()
+logging.basicConfig(format='[%(asctime)s] %(levelname)s: %(message)s',
+ level=logging.getLevelName(args.log_lvl),
+ datefmt='%m/%d/%Y %H:%M:%S')
+
kept_times = []
lines = []
-last_time = None
+last_slot_time = None
error_state = False
+last_alert_time = 0
-for line in follow(args.logfile):
- now = int(time.time()) // args.interval_size
+for line in misc.follow_file(args.logfile):
+ time_now = time.time()
+ slot_now = int(time_now) // args.interval_size
if line != None:
- if not last_time or now > last_time:
- kept_times.append(now)
- last_time = now
+ if not last_slot_time or slot_now > last_slot_time:
+ kept_times.append(slot_now)
+ last_slot_time = slot_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[0] <= slot_now - (args.num_intervals + 1):
kept_times.pop(0)
- intervals = [False] * args.num_intervals
+ intervals = [False] * (args.num_intervals + 1)
for kept_time in kept_times:
- intervals[now - kept_time] = True
+ intervals[slot_now - kept_time] = True
logging.debug(intervals)
- if not False in intervals:
+ if not False in intervals[1:]:
+ if not error_state or time_now - last_alert_time > ALERT_INTERVAL:
+ last_alert_time = time_now
+ misc.feed_handler( args.handler,
+ misc.create_msg("Log Alert",
+ "☠",
+ args.logfile,
+ "Number of errors exceeded!",
+ lines) )
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) )