Created
November 9, 2017 16:49
-
-
Save kbabioch/764dbb8097c9a684b52bd1cefc092ce6 to your computer and use it in GitHub Desktop.
Analyzes mail log files from Postfix (in Python)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /usr/bin/env python | |
from datetime import datetime, timedelta | |
from optparse import OptionParser | |
import sys | |
import re | |
# TODO Read log lines backwards | |
desc="""This is a simple Nagios plugin that will analyze the mail log from | |
Postfix and sum up the amount of sent, received and picked up mail. These | |
values can then be compared against thresholds, and the script will exit with | |
an appropriate exit code.""" | |
# Nagios exit codes | |
EXIT_OK = 0 | |
EXIT_WARNING = 1 | |
EXIT_CRITICAL = 2 | |
EXIT_UNKNOWN = 3 | |
# Set up OptionParser | |
p = OptionParser(description=desc) | |
p.add_option('-w', dest='w', default='0,0,0', help='warning thresholds for sent, received and picked up mail, default: %default', metavar='S,R,P') | |
p.add_option('-c', dest='c', default='0,0,0', help='critical thresholds for sent, received and picked up mail, default: %default', metavar='S,R,P') | |
p.add_option('-F', '--filename', dest='f', default='/var/log/mail.log', help='destination of mail log file, default: %default', metavar='FILENAME') | |
p.add_option('-p', '--period', dest='p', default='1', type='int', help='period (in hours) to analyze, default: %default', metavar='HOURS') | |
# Parse arguments | |
(opts, args) = p.parse_args() | |
# Get thresholds for sent, received and picked up mail | |
sw, rw, pw = [int(x) for x in opts.w.split(',', 3)] | |
sc, rc, pc = [int(x) for x in opts.c.split(',', 3)] | |
# Get current date and time (without microseconds) | |
now = datetime.today().replace(microsecond = 0) | |
# Date and time for log entries (will be replaced for each entry) | |
ld = datetime.today() | |
# Counters for sent, received and picked up mails | |
s = 0 | |
r = 0 | |
p = 0 | |
try: | |
# Open log file for reading | |
with open(opts.f, 'r') as f: | |
# Iterate over log file line by line | |
for line in f: | |
# Match the log entry with named groups so date processing will be easier | |
m = re.match(r'(?P<month>[A-Z][a-z]{2}) +(?P<day>\d{1,2}) (?P<hour>\d{2}):(?P<minute>\d{2}):(?P<second>\d{2})(.*)', line) | |
# Check for valid match | |
if not m: | |
# Skip invalid lines | |
continue | |
# Actual content of the log entry | |
l = m.group(6) | |
# Process date of the log entry | |
d = m.groupdict() | |
# Convert string representation of month (e.g. Jan) to numerical value (e.g. 1) | |
d['month'] = datetime.strptime(d['month'], '%b').strftime('%m') | |
# Cast matched arguments from str to int | |
#d = {str(k):int(v) for k, v in d.items()} | |
d = dict((str(k), int(v)) for k, v in d.items()) | |
# Replace attributes with the ones matched from log entry (ignore microseconds) | |
#ld = ld.replace(**d, microsecond = 0, year = now.year) | |
ld = ld.replace(**d) | |
ld = ld.replace(microsecond = 0, year = now.year) | |
# Check whether log entry lies in the future | |
if ld > now: | |
# Assume that log entry is from last year | |
ld = ld.replace(year = ld.year - 1) | |
# Check whether log entry is too old to be processed | |
if ld + timedelta(hours=opts.p) < now: | |
# Skip line since entry is too old | |
continue | |
# Check whether log entry is about sent mail | |
if re.search(r'smtp.*status=sent', l): | |
s += 1 | |
# Check whether log entry is about received mail | |
elif re.search(r'.*smtpd.*client=', l): | |
r += 1 | |
# Check whether log entry is about picked up mail | |
elif re.search(r'pickup.*(sender|uid)=', l): | |
p += 1 | |
# Handle invalid files | |
except FileNotFoundError: | |
print("Log file was not found: %s" % opts.f) | |
sys.exit(EXIT_UNKNOWN) | |
# Handle I/O errors | |
except IOError as e: | |
print("I/O error: %s" % e) | |
sys.exit(EXIT_UNKNOWN) | |
# Handle everything else | |
except: | |
print("Unknown error!") | |
sys.exit(EXIT_UNKNOWN) | |
# Output statistics | |
print("Sent: %d, received: %d, picked up: %d" % (s, r, p)) | |
# Check if any of the critical thresholds has been reached | |
if (sc > 0 and s > sc) or (rc > 0 and r > rc) or (pc > 0 and p > pc): | |
sys.exit(EXIT_CRITICAL) | |
# Check if any of the warning thresholds has been reached | |
elif (sw > 0 and s > sw) or (rw > 0 and r > rw) or (pw > 0 and p > pw): | |
sys.exit(EXIT_WARNING) | |
# No thresholds reached, exit normally | |
sys.exit(EXIT_OK) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment