Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
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
# 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 = = 0)
# Date and time for log entries (will be replaced for each entry)
ld =
# Counters for sent, received and picked up mails
s = 0
r = 0
p = 0
# 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
# Actual content of the log entry
l =
# 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
# Check whether log entry is about sent mail
if'smtp.*status=sent', l):
s += 1
# Check whether log entry is about received mail
elif'.*smtpd.*client=', l):
r += 1
# Check whether log entry is about picked up mail
elif'pickup.*(sender|uid)=', l):
p += 1
# Handle invalid files
except FileNotFoundError:
print("Log file was not found: %s" % opts.f)
# Handle I/O errors
except IOError as e:
print("I/O error: %s" % e)
# Handle everything else
print("Unknown error!")
# 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):
# 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):
# No thresholds reached, exit normally
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment