Skip to content

Instantly share code, notes, and snippets.

@kbabioch
Created November 9, 2017 16:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kbabioch/764dbb8097c9a684b52bd1cefc092ce6 to your computer and use it in GitHub Desktop.
Save kbabioch/764dbb8097c9a684b52bd1cefc092ce6 to your computer and use it in GitHub Desktop.
Analyzes mail log files from Postfix (in Python)
#! /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