Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Analyzes mail log files from Postfix (in Go)
package main
import (
"bufio"
"flag"
"fmt"
"os"
"regexp"
"strconv"
"time"
)
const (
EXIT_OK int = 0
EXIT_WARNING int = 1
EXIT_CRITICAL int = 2
EXIT_UNKNOWN int = 3
)
func main() {
// Setup command line arguments
optw := flag.String("w", "0,0,0", "warning thresholds for sent, received and picked up mail")
optc := flag.String("c", "0,0,0", "critical thresholds for sent, received and picked up mail")
optf := flag.String("F", "/var/log/mail.log", "destination of mail log file")
optp := flag.Int("p", 1, "period (in hours) to analyze")
// Parse command line arguments
flag.Parse()
// Split and parse warning thresholds
var sw, rw, pw int
if argc, err := fmt.Sscanf(*optw, "%d,%d,%d", &sw, &rw, &pw); argc != 3 || err != nil {
flag.PrintDefaults()
os.Exit(EXIT_UNKNOWN)
}
// Split and parse criticial thresholds
var sc, rc, pc int
if argc, err := fmt.Sscanf(*optc, "%d,%d,%d", &sc, &rc, &pc); argc != 3 || err != nil {
flag.PrintDefaults()
os.Exit(EXIT_UNKNOWN)
}
// Counter for sent, received and picked up mail
var s, r, p int
// Open mail log
f, err := os.Open(*optf)
if err != nil {
fmt.Println(err)
os.Exit(EXIT_UNKNOWN)
}
// Current time
now := time.Now()
// Regular expression to match log entries and extract interesting fields as groups
r1 := regexp.MustCompile("([A-Z][a-z]{2}) +([0-9]{1,2}) ([0-9]{2}):([0-9]{2}):([0-9]{2}) (.*)")
r2 := regexp.MustCompile("smtp.*status=sent")
r3 := regexp.MustCompile("smtpd.*client=")
r4 := regexp.MustCompile("pickup.*(sender|uid)=")
// Static map for translation of month names to int
months := map[string]time.Month {
"Jan": time.January,
"Feb": time.February,
"Mar": time.March,
"Apr": time.April,
"May": time.May,
"Jun": time.June,
"Jul": time.July,
"Aug": time.August,
"Sep": time.September,
"Oct": time.October,
"Nov": time.November,
"Dec": time.December,
}
// Read file line by line
scanner := bufio.NewScanner(f)
for scanner.Scan() {
var result = r1.FindStringSubmatch(scanner.Text())
// TODO Use time.Parse() + time.Add() (for year/tz) instead?
month := months[result[1]]
day, _ := strconv.Atoi(result[2])
hour, _ := strconv.Atoi(result[3])
minute, _ := strconv.Atoi(result[4])
second, _ := strconv.Atoi(result[5])
// Get time for this entry
ld := time.Date(now.Year(), month, day, hour, minute, second, 0, now.Location())
// Check whether entry is in the future (something is wrong?)
if ld.After(now) {
continue
}
// Only consider lines that are covered by the specified period
if ld.Add(time.Duration(*optp) * time.Hour).Before(now) {
continue
}
if r2.MatchString(result[6]) {
s++;
} else if r3.MatchString(result[6]) {
r++;
} else if r4.MatchString(result[6]) {
p++;
}
}
// Output stats
fmt.Printf("Sent: %d, received: %d, picked up: %d\n", s, r, p)
// Check if any of the critical thresholds has been reached
if (sc > 0 && s > sc) || (rc > 0 && r > rc) || (pc > 0 && p > pc) {
os.Exit(EXIT_CRITICAL)
// Check if any of the warning thresholds has been reached
} else if (sw > 0 && s > sw) || (rw > 0 && r > rw) || (pw > 0 && p > pw) {
os.Exit(EXIT_WARNING)
}
// No thresholds reached, exit normally
os.Exit(EXIT_OK)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment