monitor_mail ¶
Save into '/usr/local/bin/monitor_mail', then:
chmod +x /usr/local/bin/monitor_mail
#!/usr/bin/env python3 import time import re import argparse # Strong styles for primary fields STATUS_STYLES = { 'sent': '\033[97;42m', # white on green 'bounced': '\033[97;41m', # white on red 'deferred': '\033[30;44m', # black on blue 'expired': '\033[97;45m', # white on magenta 'reject': '\033[97;41m', # white on red 'discarded': '\033[30;47m', # black on white 'hold': '\033[30;46m', # black on cyan 'error': '\033[97;41m', # white on red } DEFAULT_STYLE = '\033[30;43m' # black on yellow SECONDARY_STYLE = '\033[94m' # light blue RESET = '\033[0m' def highlight_status_and_to(line, use_color=True): """ Highlights: - 'status=...' and 'to=<...>' with background color - 'from=...', 'relay=...', 'message-id=...' with light foreground color Skips orig_to. """ status_match = re.search(r'status=([a-zA-Z0-9_]+)', line) if not status_match: return None status_value = status_match.group(1) if use_color: strong_style = STATUS_STYLES.get(status_value, DEFAULT_STYLE) secondary_style = SECONDARY_STYLE else: strong_style = '' secondary_style = '' reset = RESET if use_color else '' # Primary highlights line = re.sub( r'status=([a-zA-Z0-9_]+)', lambda m: f"{strong_style}{m.group(0)}{reset}", line ) line = re.sub( r'(?<!orig_)to=<[^>]+>', lambda m: f"{strong_style}{m.group(0)}{reset}", line ) # Secondary highlights line = re.sub( r'from=<[^>]+>', lambda m: f"{secondary_style}{m.group(0)}{reset}", line ) line = re.sub( r'relay=[^ ,]+', lambda m: f"{secondary_style}{m.group(0)}{reset}", line ) line = re.sub( r'message-id=<[^>]+>', lambda m: f"{secondary_style}{m.group(0)}{reset}", line ) return line def tail_f(filepath, show_all=False, from_start=False, use_color=True, only_status=None): """ Tail -f with optional filtering, coloring and status filtering. """ with open(filepath, 'r') as f: if not from_start: f.seek(0, 2) while True: line = f.readline() if not line: time.sleep(0.1) continue line = line.rstrip() # Extract status value (if any) status_match = re.search(r'status=([a-zA-Z0-9_]+)', line) status_value = status_match.group(1) if status_match else None if only_status and status_value != only_status: continue # Skip line if it doesn't match the --only-status filter highlighted = highlight_status_and_to(line, use_color=use_color) if highlighted: print(highlighted) elif show_all: print(line) if __name__ == "__main__": parser = argparse.ArgumentParser(description="Tail and highlight Postfix log lines") parser.add_argument( "--show-all", action="store_true", help="Show all lines (not just those with 'status=...')" ) parser.add_argument( "--from-start", action="store_true", help="Start reading from the beginning of the file instead of the end" ) parser.add_argument( "--no-color", action="store_true", help="Disable all ANSI color output (monochrome)" ) parser.add_argument( "--only-status", metavar="STATUS", help="Show only lines with a specific status (e.g. bounced, sent, deferred)" ) args = parser.parse_args() # Default path to Postfix log log_file_path = "/var/log/mail.log" # Start tailing tail_f( filepath=log_file_path, show_all=args.show_all, from_start=args.from_start, use_color=not args.no_color, only_status=args.only_status )