#!/usr/bin/perl

use strict ;
use warnings ;
use autodie ;

use Carp ;
use File::Basename ;
use YAML::Tiny;
use Getopt::Long::Descriptive ;
use Data::Dumper ;

use Stanford::Orange::Util qw/ tail_file_matches run_command_improved /;
use Stanford::Orange::Nagios ;

my $DEBUG ;

sub get_config {
    my ($config_file) = @_ ;
    my $yaml = YAML::Tiny->read($config_file) ;
    return $yaml->[0] ;
}

# ---
# logfile:    /var/log/syslog
# regex:      (puppet-agent|pam_unix)
# tail-lines: 100
sub process_config {
    my ($config_href) = @_ ;

    my %config = %{ $config_href } ;

    my @keys = keys %config ;
    my $name = $keys[0] ;
    progress("processing log-file check '$name'") ;

    my $value_href = $config{$name} ;

    my $regex_string = $value_href->{'regex'} ;
    my $filename     = $value_href->{'file'} ;

    progress("file name is '$filename'") ;
    progress("regex string is '$regex_string'") ;

    return ;
}

sub progress {
    my ($msg) = @_ ;
    if ($DEBUG) {
        print $msg . "\n" ;
    }
    return ;
}

sub exit_with_message {
    my ($msg) = @_ ;
    print "error: $msg\n" ;
    exit 1 ;
}

sub get_man_page {
    my @cmd = ('perldoc', '-t', $0) ;
    my ($stdout, $stderr, $exit_value) = run_command_improved(@cmd) ;
    return $stdout ;
}

sub help_text {
    my ($usage_text) = @_ ;
    my $rv = q{} ;
    $rv .= $usage_text ;
    return $rv ;
}

##########################################################################

my $script_name   = basename($0) ;
my $overrides     = ' (overrides config file)' ;
my ($opt, $usage) = describe_options(
    "$script_name %o",
    [ 'config-file|c=s', 'path to configuration file'],
    [ 'tail-lines|t=s',  "number of lines to tail$overrides"],
    [ 'logfile|f=s',     "the file to check$overrides"],
    [ 'regex|r=s',       "compare lines against this PCRE$overrides"],
    [ 'verbose|v',       'print extra stuff'            ],
    [ 'help|h',          'print usage message and exit' ],
    [ 'manual|m',        'print man page'],
    );

if ($opt->verbose()) {
    $DEBUG = 1 ;
}

if ($opt->help()) {
    print help_text($usage->text()) ;
    exit 0 ;
} elsif ($opt->manual()) {
    print get_man_page() ;
    exit 0 ;
}

my ($logfile, $regex, $tail_lines) ;
my $config_file = $opt->config_file() ;

if ($config_file) {
    my $config_href = get_config($config_file) ;
    progress(Dumper $config_href) ;

    $logfile    = $config_href->{'logfile'} ;
    $regex      = $config_href->{'regex'} ;
    $tail_lines = $config_href->{'tail-lines'} ;
}

if ($opt->logfile()) {
    $logfile = $opt->logfile() ;
}
if ($opt->regex()) {
    $regex = $opt->regex() ;
}
if ($opt->tail_lines()) {
    $tail_lines = $opt->tail_lines() ;
}

if (!$logfile) {
    exit_with_message('missing logfile') ;
}
if (!$regex) {
    exit_with_message('missing regex') ;
}
if (!$tail_lines) {
    exit_with_message('missing tail-lines') ;
}

progress("file name is '$logfile'") ;
progress("regex string is '$regex'") ;
progress("tail-lines is '$tail_lines'") ;

my @results ;
eval {
    @results = tail_file_matches($logfile, qr/$regex/xsm, $tail_lines) ;
} ;
my $at_error = $@ ;

my $number_results ;
if (@results) {
    $number_results = scalar(@results) ;
}

my $nagios = Stanford::Orange::Nagios->new() ;

if ( $number_results > 0) {
    $nagios->set_critical() ;
    my $s ;
    if ($number_results == 1) {
        $s = q{} ;
    } else {
        $s = q{s} ;
    }
    $nagios->set_exit_message("CRITICAL: found $number_results matching line$s") ;
} elsif ($at_error) {
    $nagios->set_unknown() ;
    $nagios->set_exit_message("system is in an undefined state: $at_error") ;
} else {
    $nagios->set_ok() ;
    $nagios->set_exit_message('OK') ;
}

# This should be the LAST line of your Nagios script:
$nagios->finish() ;


__END__

=head1 NAME

check_log_tail_pcre - Look for problems in a log file and alert Nagios

=head1 SYNOPSIS

check_log_tail_pcre B<-h>

check_log_tail_pcre B<-c> I<config-file> [B<-v>]

check_log_tail_pcre B<-f> I<logfile> B<-r> I<regex> B<-t> I<tail-lines> [B<-v>]

=head1 DESCRIPTION

Looks for lines that match a Perl-compatible regular expression in the
last lines of a file and, if any are found, returns a Nagios-compatible
value.

The three parameters I<logfile>, I<regex>, and I<tail-lines> must be specified,
either in the config-file or via the command-line options (or both).

As an example, if I<logfile> is "/var/log/syslog",
I<regex> is "puppet-agent|pam_unix",
and I<tail-lines> is 100,
then each of the last 100 lines of /var/log/syslog is
compared to the regular expression "puppet-agent|pam_unix" and, if any
lines match, the script exits with a Nagios-compatible CRITICAL return
code. If no lines match, it returns a Nagios-compatible OK return value.

WHEN TO USE: This sort of log file monitoring is only appropriate in
certain situations, namely when service problem error messages are logged
and I<keep> being logged as long as the service problem
persists. Furthermore, once the service problem is resolved, the logged
error messages "scroll" off the bottom of the log file.

EXAMPLE: an web application logs to a single file whenever the web
application is accessed either a success message (when the service is
working properly) or an error message (when the service is broken). This
is an appropriate use of the monitor.

WHEN NOT TO USE: a service logs error messages but the error messages sit
on the bottom of the log file even after the problem is resolved.

WHEN NOT TO USE: a service problem causes error messages to be logged only
at the start of the problem and then, even if the problem persists, no new
error messages are logged.


=head1 OPTIONS

=over 4

=item B<-c> I<config_file>

Get the configuration from I<config_file>. The configuration file must
be in YAML format and look like this:

    ---
    logfile:    /var/log/syslog
    regex:      (puppet-agent|pam_unix)
    tail-lines: 100

Any of these parameters can be overridden via command-line options.

=item B<-t> I<file>

The file we want to check for matching lines.
This option will override the corresponding parameter in the configuration file.

=item B<-t> I<regex>

A PCRE (Perl-compatible regular expression) used when looking for matching
lines in I<file>.
This option will override the corresponding parameter in the configuration file.

=item B<-t> I<tail-lines>

Examine the last I<tail-lines> of the file.
This option will override the corresponding parameter in the configuration file.

=item B<-v>

Show verbose output.

=back

=head1 EXIT STATUS

Returns a Nagios-compatible status, either 0 (OK) if nothing in the tail
of the log matches, or 2 (CRITICAL) if any lines in the tail match.

=head1 EXAMPLES

Run the alert using the configuration in
B</etc/shibboleth-idp/alert.yaml>:

   check_log_tail_pcre -f /etc/shibboleth-idp/alert.yaml

Run the alert using the configuration in
B</etc/shibboleth-idp/alert.yaml> but override the number of lines
in the tail of the file to use:

   check_log_tail_pcre -f /etc/shibboleth-idp/alert.yaml -t 500

=head1 SEE ALSO

Stanford::Orange::Util::tail_file_matches

=head1 AUTHOR

Adam Lewenberg <adamhl@stanford.edu>

=head1 COPYRIGHT AND LICENSE

Copyright 2016 The Board of Trustees of the Leland Stanford Junior
University.  All rights reserved.

Permission to use, copy, modify, and distribute this software and its
documentation for any purpose and without fee is hereby granted, provided
that the above copyright notice appear in all copies and that both that
copyright notice and this permission notice appear in supporting
documentation, and that the name of Stanford University not be used in
advertising or publicity pertaining to distribution of the software
without specific, written prior permission.  Stanford University makes no
representations about the suitability of this software for any purpose.
It is provided "as is" without express or implied warranty.

THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.

=cut

#############################################################################
#############################################################################
