Skip to content

Commit

Permalink
journald support of postfix_mail* plugins (#1634)
Browse files Browse the repository at this point in the history
journald support of postfix_mailstats and postfix_mailvolume inspired by
the sshd_log plugin from munin-contrib
  • Loading branch information
steveschnepp authored Sep 15, 2024
2 parents fa433a4 + 9326a6e commit 86616d4
Show file tree
Hide file tree
Showing 2 changed files with 249 additions and 107 deletions.
209 changes: 147 additions & 62 deletions plugins/node.d/postfix_mailstats
100755 → 100644
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
#!/usr/bin/perl -w
# -*- perl -*-

=head1 NAME
postfix_mailstats - Plugin to monitor the number of mails delivered and
rejected by postfix
rejected by postfix, with support for journald logs
=head1 CONFIGURATION
Expand All @@ -13,15 +14,22 @@ if you need to override the defaults below:
[postfix_mailstats]
env.logdir - Which logfile to use
env.logfile - What file to read in logdir
env.use_journald - Set to 1 to use journald instead of a logfile
env.journalctlargs
=head2 DEFAULT CONFIGURATION
[postfix_mailstats]
env.logdir /var/log
env.logfile mail.log
env.use_journald 0
env.journalctlargs [email protected]
=head1 AUTHOR
Original plugin contributed by Nicolai Langfeldt,
extended for journald support by Stephan Kleber with some help by ChatGPT.
Records show that the plugin was contributed by Nicolai Langfeldt in
2003. Nicolai can't find anything in his email about this and expects
the plugin is based on the corresponding exim plugin - to which it now
Expand All @@ -46,77 +54,125 @@ munin-node.
=cut

use strict;

use Munin::Plugin;

my $statefile = $ENV{'MUNIN_PLUGSTATE'} . "/munin-plugin-postfix_mailstats.state";
my $pos;
my $delivered;
my $LOGDIR = (defined($ENV{'logdir'}) ? $ENV{'logdir'} : '/var/log');
my $LOGFILE = (defined($ENV{'logdir'}) ? $ENV{'logfile'} : 'mail.log');
my $delivered = 0;
my %rejects = ();

my $LOGDIR = $ENV{'logdir'} || '/var/log';
my $LOGFILE = $ENV{'logfile'} || 'mail.log';
my $USE_JOURNALD = $ENV{'use_journald'} || 0;
my $journalctlargs = $ENV{'journalctlargs'} // '[email protected]';

my $logfile = "$LOGDIR/$LOGFILE";

if ( defined($ARGV[0]) and $ARGV[0] eq "autoconf" )
if ( $ARGV[0] and $ARGV[0] eq "autoconf" )
{
if (-d $LOGDIR)
{
if (-f $logfile)
if ($USE_JOURNALD) {
# Check if journalctl command is available
if (system("which journalctl > /dev/null 2>&1") == 0) {
print "yes\n";
exit 0;
} else {
print "no (journalctl not found)\n";
exit 0;
}
} else {
# Logfile handling
if (-d $LOGDIR)
{
if (-r $logfile)
if (-f $logfile)
{
print "yes\n";
exit 0;
if (-r $logfile)
{
print "yes\n";
exit 0;
}
else
{
print "no (logfile '$logfile' not readable)\n";
}
}
else
{
print "no (logfile '$logfile' not readable)\n";
print "no (logfile '$logfile' not found)\n";
}
}
else
{
print "no (logfile '$logfile' not found)\n";
print "no (could not find logdir '$LOGDIR')\n";
}
}
else
{
print "no (could not find logdir '$LOGDIR')\n";
}

exit 0;
}

my @state = restore_state();

$pos = shift @state;
$delivered = shift @state;

$pos = 0 unless defined($pos);
$delivered = 0 unless defined($delivered);

my %rejects = @state;
if ($USE_JOURNALD) {
if (!defined $pos)
{
# Initial run.
$pos = 0;
}

if (! -f $logfile)
{
print "delivered.value U\n";
foreach my $reason (sort keys %rejects)
# Parse logs from journald
parseJournald();
} else {
# Load statefile if it exists
if ( -f $statefile)
{
my $fieldname = clean_fieldname("r$reason");
print "$fieldname.value U\n";
open (IN, '<', $statefile) or die "Unable to open state-file: $!\n";
if (<IN> =~ /^(\d+):(\d+)/)
{
($pos, $delivered) = ($1, $2);
}
while (<IN>)
{
if (/^([0-9a-z.\-]+):(\d+)$/)
{
$rejects{$1} = $2;
}
}
close IN;
}

# Logfile handling
if (! -f $logfile)
{
print "delivered.value U\n";
foreach my $reason (sort keys %rejects)
{
my $fieldname = clean_fieldname("r$reason");
print "$fieldname.value U\n";
}
exit 0;
}
exit 0;
}

my $startsize = (stat $logfile)[7];

my $startsize = (stat $logfile)[7];
if (!defined $pos)
{
# Initial run.
$pos = $startsize;
}

if (!defined $pos)
{
# Initial run.
parseLogfile($logfile, $pos, $startsize);
$pos = $startsize;

# Save statefile
if(-l $statefile) {
die("$statefile is a symbolic link, refusing to touch it.");
}
open (OUT, '>', $statefile) or die "Unable to open statefile: $!\n";
print OUT "$pos:$delivered\n";
foreach my $i (sort keys %rejects)
{
print OUT "$i:", $rejects{$i}, "\n";
}
close OUT;
}

$pos = parseLogfile($logfile, $pos, $startsize);

if ( $ARGV[0] and $ARGV[0] eq "config" )
{
print "graph_title Postfix message throughput\n";
Expand Down Expand Up @@ -148,31 +204,60 @@ foreach my $reason (sort keys %rejects)
print "$fieldname.value ", $rejects{$reason}, "\n";
}

save_state($pos, $delivered, %rejects);

# Function to parse logs from a regular logfile
sub parseLogfile
{
my ($fname, $start, $stop) = @_;
open (LOGFILE, $fname)
or die "Unable to open logfile $fname for reading: $!\n";
seek (LOGFILE, $start, 0)
or die "Unable to seek to $start in $fname: $!\n";

my ($logfd, $reset) = tail_open($fname, $start);

while (tell($logfd) < $stop)
while (tell (LOGFILE) < $stop)
{
my $line = <$logfd>;
chomp ($line);

if ($line =~ / to=.*, status=sent /)
{
$delivered++;
}
elsif ($line =~ /postfix\/smtpd.*proxy-reject: \S+ (\S+)/ ||
$line =~ /postfix\/smtpd.*reject: \S+ \S+ \S+ (\S+)/ ||
$line =~ /postfix\/postscreen.*reject: \S+ \S+ \S+ (\S+)/ ||
$line =~ /postfix\/cleanup.* reject: (\S+)/ ||
$line =~ /postfix\/cleanup.* milter-reject: \S+ \S+ \S+ (\S+)/)
{
$rejects{$1}++;
}
my $line = <LOGFILE>;
chomp ($line);

if ($line =~ / to=.*, status=sent /)
{
$delivered++;
}
elsif ($line =~ /postfix\/smtpd.*proxy-reject: \S+ (\S+)/ ||
$line =~ /postfix\/smtpd.*reject: \S+ \S+ \S+ (\S+)/ ||
$line =~ /postfix\/cleanup.* reject: (\S+)/ ||
$line =~ /postfix\/cleanup.* milter-reject: \S+ \S+ \S+ (\S+)/)
{
$rejects{$1}++;
}
}
return tail_close($logfd);
close(LOGFILE) or warn "Error closing $fname: $!\n";
}

# Function to parse logs from journald
sub parseJournald
{
my $cmd;
$cmd = "journalctl --no-pager --quiet --since=" . `date -dlast-sunday +%Y-%m-%d` . " $journalctlargs";
open(my $journal, '-|', $cmd)
or die "Unable to read journald logs: $!\n";

while (my $line = <$journal>) {
chomp($line);

if ($line =~ / to=.*, status=sent /)
{
$delivered++;
}
elsif ($line =~ /postfix\/smtpd.*proxy-reject: \S+ (\S+)/ ||
$line =~ /postfix\/smtpd.*reject: \S+ \S+ \S+ (\S+)/ ||
$line =~ /postfix\/postscreen.*reject: \S+ \S+ \S+ (\S+)/ ||
$line =~ /postfix\/cleanup.* reject: (\S+)/ ||
$line =~ /postfix\/cleanup.* milter-reject: \S+ \S+ \S+ (\S+)/)
{
$rejects{$1}++;
}
}
close($journal) or warn "Error closing journald stream: $!\n";
}

# vim:syntax=perl
Loading

0 comments on commit 86616d4

Please sign in to comment.