check_logfile config logfilemissing

This support forum board is for support questions relating to Nagios XI, our flagship commercial network monitoring solution.
Locked
optionstechnology
Posts: 234
Joined: Thu Nov 17, 2016 11:26 am

check_logfile config logfilemissing

Post by optionstechnology »

I am using check_logfile on a linux server

But I want it to alert OK when the file is missing instead of UNKNOWN

There is a option for this --logfilemissing=ok

Which works when I use it from command line, but I am using a config file for these checks, and when I add it to the config file it is ignored

here is my config-

Code: Select all

$logfilemissing = ok;

use POSIX qw(strftime);
my $run_date= strftime "%Y%m%d", localtime;
chomp($run_date);

@searches = (
  {
    logfile => "/usr/log_${run_date}.log",
    criticalpatterns => [
        'problem',
        'ERROR'
    ],
  });
Am I doing something wrong?
npolovenko
Support Tech
Posts: 3457
Joined: Mon May 15, 2017 5:00 pm

Re: check_logfile config logfilemissing

Post by npolovenko »

Hello, @optionstechnology.

I don't have a lot of experience with this plugin but I'd try to write the filter this way:

Code: Select all

@searches = (
  {
    logfile => "/usr/log_${run_date}.log",
    options => 'logfilemissing = ok', 
    criticalpatterns => [
        'problem',
        'ERROR'
    ],
  });

But also, I'm not sure if "logfilemissing = ok" is even supported:

Code: Select all

* 3.7.2 2015-09-22
  add option logfilemissing=[warning|critical]
I haven't found any mentionings of OK in the documentation or in the changelog.
As of May 25th, 2018, all communications with Nagios Enterprises and its employees are covered under our new Privacy Policy.
optionstechnology
Posts: 234
Joined: Thu Nov 17, 2016 11:26 am

Re: check_logfile config logfilemissing

Post by optionstechnology »

I tried that but it hasnt fixed the problem

The command definitely accepts an 'ok' state because when I run it all from command line it works fine - it only fails when used via a config file
User avatar
mcapra
Posts: 3739
Joined: Thu May 05, 2016 3:54 pm

Re: check_logfile config logfilemissing

Post by mcapra »

I assume you are referring to the check_logfiles plugin located here?
https://labs.consol.de/nagios/check_logfiles/index.html

It would be useful to know which specific version of check_logfiles you're using.

You could try this format from the examples:

Code: Select all

use POSIX qw(strftime);
my $run_date= strftime "%Y%m%d", localtime;
chomp($run_date);

$options = 'logfilemissing=ok';

@searches = (
  {
    logfile => "/usr/log_${run_date}.log",
    criticalpatterns => [
        'problem',
        'ERROR'
    ],
  });
Former Nagios employee
https://www.mcapra.com/
User avatar
tgriep
Madmin
Posts: 9190
Joined: Thu Oct 30, 2014 9:02 am

Re: check_logfile config logfilemissing

Post by tgriep »

Thanks @mcapra for the help.
@optionstechnology, if it works when using the command line option, can you edit the command in Nagios and add that option if changing the config file doesn't work for you?
Be sure to check out our Knowledgebase for helpful articles and solutions!
optionstechnology
Posts: 234
Joined: Thu Nov 17, 2016 11:26 am

Re: check_logfile config logfilemissing

Post by optionstechnology »

Thanks @mcapra, I tried it but unfortunately it did not work either

I'm not sure what version it is as there is nothing in the text of the check itself - its the one that comes as default with nrpe

Unfortunately its also not a case of adding the command line - you can either supply arguments from config file, or from the command line, but for some reason not from both

As I have to use a config to get the filename correct I cannot use the command line option
User avatar
tgriep
Madmin
Posts: 9190
Joined: Thu Oct 30, 2014 9:02 am

Re: check_logfile config logfilemissing

Post by tgriep »

Can you post the plugin here so we can view it?
Be sure to check out our Knowledgebase for helpful articles and solutions!
optionstechnology
Posts: 234
Joined: Thu Nov 17, 2016 11:26 am

Re: check_logfile config logfilemissing

Post by optionstechnology »

(This is the top half of it)

Code: Select all

#! /usr/bin/perl 
# nagios: -epn

#
# Logfile::Config::Tivoli.pm - Tivoli Config Module
#
# Purpose: Provide a convenient way for loading
#          tivoli config files and
#          return it as hash structure
#
package Nagios::Tivoli::Config::Logfile;

use strict;

sub new {
  my($this, $param ) = @_;
  my $class = ref($this) || $this;

  my $self = {
      formatfile   => '',  # format file with tivoli format definitions,
                           # can be an array of files
      formatstring => '',  # format file content as string
      severity_mappings => {},
      max_continuation_lines => 0, # in case there are %n in among the patterns
      line_buffer => [],   # for continuation lines
      line_buffer_size => 0,
  };
  bless $self, $class;

  $self->set_severity_mapping('fatal', 2);
  $self->set_severity_mapping('critical', 2);
  $self->set_severity_mapping('severe', 2);
  $self->set_severity_mapping('warning', 1);
  $self->set_severity_mapping('minor', 1);
  $self->set_severity_mapping('harmless', 0);
  $self->set_severity_mapping('unknown', 0);

  # parse parameter
  if (ref($param) eq "HASH") {
    for my $key (keys %{$param}) {
      if (!defined $self->{lc $key}) {
        printf STDERR "unrecognized parameter: %s\n", $key;
        return undef;
      } else {
        if (ref($param->{$key}) eq 'HASH') {
          $self->merge_hash($self->{$key}, $param->{$key});
        } else {
          $self->{lc $key} = $param->{$key};
        }
      }
    }
  } elsif (ref($param) eq "") {
    $self->{formatfile} = $param;
  } else {
    printf STDERR "formatfile is a required parameter\n";
  }
  if ((!defined $self->{formatfile} || $self->{formatfile} eq '') &&
      (!defined $self->{formatstring} || $self->{formatstring} eq '')) {
        printf STDERR "please either specify formatfile or formatstring\n";
    return undef;
  }
  if (defined $self->{formatstring} and $self->{formatstring} ne '') {
    $self->{_formatstring} = $self->{formatstring};
  } else {
    $self->{_formatstring} = $self->_read($self->{formatfile});
  }
  if (! $self->{_formatstring}) {
    return undef;
  }
  foreach (keys %{$self->{tivolimapping}}) {
    $self->set_severity_mapping($_, $self->{tivolimapping}->{$_});
  }
  if ($self->_parse) {
    #Data::Dumper::Dumper($self->{formats});
    return $self;
  } else {
    printf STDERR ("parsing failed, see previous messages...");
    return undef;
  }
}

sub _read {
  my $self     = shift;
  my $filename = shift;
  my $content;
  if (ref($filename) eq 'ARRAY') {
    for my $file (@{$filename}) {
      $content .= $self->_read($file);
    }
  } else {
    if (open FMT, $filename) {
      while(<FMT>) {
        $content .= $_;
      }
      close FMT;
    } else {
      printf STDERR "unable to read file %s: %s\n", $filename, $!;
      return undef;
    }
  }
  return($content);
}

sub _parse {
  my $self = shift;
  my $format;
  my $lineno = 0;
  for my $line (split /\n/, $self->{_formatstring}) {
    $lineno++;
    chomp $line;
    $line = $1 if $line =~ /^\s*(.*?)\s*$/;

    next if $line =~ m/^\/\//;
    next if $line eq "";

    if ($line =~ m/^FORMAT/) {
      my($name, $follows, $followname) = 
          $line =~ m/^FORMAT\s+(.*?)\s*(|FOLLOWS\s+(.*?))$/;
      $format= Nagios::Tivoli::Config::Logfile::Format->new({
          name => $name,
          lineno => $lineno,
          severity_mappings => $self->{severity_mappings},
      });
      if (defined $followname) {
        my @follows = split /\s*,\s*/, $followname;
        for my $follow (@follows) {
          if (my $follow_format = $self->get_format_by_name($follow)) {
            $format->inherit($follow_format);
          }
        }
        $format->{follows} = \@follows;
      }
    } elsif ($line =~ m/^END/) {
      if (!defined $format) {
        printf STDERR "found format end without beginning\n";
        return 0;
      }
      if (!defined $format->{pattern}) {
        if (!exists $format->{follows}) {
          printf STDERR "found format without pattern\n";
          return 0;
        }
      }
      $self->add_format($format);
    } elsif (defined $format) {
      if (!defined $format->{pattern}) {
        # %s Specifies a variable string.
        # %t Specifies a variable date of the form 'MMM DD hh:mm:ss'
        # %s+ Specifies one or more variable strings that
        #     are separated by spaces.
        # %s* Specifies zero or more strings separated by white space.
        # %n Specifies a new line (CR).
        #    This applies only to the following adapters:
        #    tecad_logfile_aix4-r1, tecad_logfile_hpux10,
        #    tecad_logfile_linux_ix86, tecad_logfile_linux-ppc,
        #    tecad_logfile_linux-s390, tecad_logfile_solaris2,
        #    and tecad_win.
        $format->{tiv_pattern} = $line;
        $format->{patternlines} = 0;
        if ($line =~ /%n/) {
          $format->{patternlines}++ while $line =~ /%n/g;
          $format->{pattern} = [map { $self->translate_pattern($_) } split /%n/, $line];
          $self->{max_continuation_lines} = $format->{patternlines} unless
              $format->{patternlines} <= $self->{max_continuation_lines};
        } else {
          $format->{pattern} = $self->translate_pattern($line);
        }
      } elsif ($line =~ m/^-(.*?)\s+(.*)$/i) {
        $format->add_variable($1, $2);
      } elsif ($line =~ m/^(.*?)\s+"*(.*?)"*\s*$/) {
        $format->add_slot($1, $2);
      }
    } else {
      printf STDERR "%s is outside of a format definition\n", $line;
      return 0;
    }
  }
  return 1;
}

sub translate_pattern {
  my $self = shift;
  my $tiv_pattern = shift;
  $tiv_pattern =~ s/\\/\\\\/g;          # quote \
  $tiv_pattern =~ s/\(/\\(/g;           # quote (
  $tiv_pattern =~ s/\)/\\)/g;           # quote )
  $tiv_pattern =~ s/%\[\d+\]s/%s/g;     # replace %[2]s with just %s
  $tiv_pattern =~ s/\[/\\[/g;           # quote [
  $tiv_pattern =~ s/\]/\\]/g;           # quote ]
  $tiv_pattern =~ s/\?/\\?/g;           # quote ?
  $tiv_pattern =~ s/\|/\\|/g;           # quote |
  $tiv_pattern =~ s/\-/\\-/g;           # quote -
  #$tiv_pattern =~ s/%s\+/\(.+?\)/g;     # %s+  becomes .+?
  #$tiv_pattern =~ s/%s\*/\(.*?\)/g;     # %s*  becomes .*?
  #$tiv_pattern =~ s/%s/\(\[^\\s\]+?\)/g;  # %s   becomes [^\s]+?
  $tiv_pattern =~ s/%s\+/\([^\\s]*?.+[^\\s]*?\)/g; # %s+ becomes [^\s]*?.+[^\s]*?
  $tiv_pattern =~ s/%s\*\s*$/\(.*\)/g;     # last %s*  becomes .* eats the rest
  $tiv_pattern =~ s/%s\*/\(.*?\)/g;     # %s*  becomes .*? eats as much as necessary
  $tiv_pattern =~ s/%s/\(\[^\\s\]+\)/g;  # %s   becomes [^\s]+?
  #$tiv_pattern =~ s/%n/\\n/g;           # %n   becomes \n
  $tiv_pattern =~ s/[ ]+/\\s\+/g;           # blanks become \s+
  $tiv_pattern =~ s/%n//g;           # %n   becomes \n
  $tiv_pattern =~ s/%t/\(\\w\{3\}\\s+\\d\{1,2\}\\s+\\d\{1,2\}\:\\d\{1,2\}\:\\d\{1,2\}\)/g;
  return $tiv_pattern;
}

sub match {
  my $self = shift;
  my $line = shift;
  if ($self->{line_buffer_size} < $self->{max_continuation_lines} + 1) {
    push(@{$self->{line_buffer}}, $line);
    $self->{line_buffer_size}++;
  } else {
    shift @{$self->{line_buffer}};
    push(@{$self->{line_buffer}}, $line);
  }
#printf STDERR "try: %s\n", $line;
  foreach my $format (reverse @{$self->{'formats'}}) {
    next if ! $format->{can_match};
    #if (($format->{name} ne '*DISCARD*') &&
    #    (! $format->has_slots() || ! $format->get_slot('severity'))) {
    #  next; # ungueltiges format
    #}
    my @matches = ();
#printf STDERR "format %s\n", $format->{name};
#printf STDERR "match /%s/\n", $format->{pattern};
    if (my @matches = $self->match_pattern($line, $format)) {
      my $hit = Nagios::Tivoli::Config::Logfile::Hit->new({
          format => $format,
          logline => $line,
          matches => \@matches,
          format_mappings => $self->{format_mappings},
          severity_mappings => $self->{severity_mappings},
      });
#printf STDERR "hit: %s\n", $line;
      if ($format->{name} eq '*DISCARD*') {
#printf STDERR "discard: %s %s\n", $line, Data::Dumper::Dumper($hit);
        last;
      } else {
#printf STDERR "hit2: %s // %s\n", $hit->{subject}, $format->{name};
        return({
          exit_code   => $hit->get_nagios_severity(),
          severity    => $hit->{severity},
          format_name => $hit->{format_name},
          subject     => $hit->{subject},
          logline     => $line,
          slots       => $hit->{slots},
        });
      }
    }
  }
#printf STDERR "mis: %s\n", $line;
  return({
    exit_code   => $self->get_severity_mapping('HARMLESS'),
    severity    => 'HARMLESS',
    format_name => 'NO MATCHING RULE',
    subject     => 'NO MATCHING RULE',
    logline     => $line,
    slots       => { },
  });
}

sub match_pattern {
  my $self = shift;
  my $line = shift;
  my $format = shift;
  my $pattern = $format->{pattern};
  if (ref($pattern) eq 'ARRAY') {
    my @all_matches = ();
    # 
    my $patterns = scalar(@{$pattern});
    if ($patterns > $self->{line_buffer_size}) {
      # zu wenig zeilen vorhanden
      return ();
    } else {
      my $startidx = $self->{line_buffer_size} - $patterns;
      my $idx = 0;
      while ($idx < $patterns) {
        # pattern[$idx] matched ${$self->{line_buffer}}[$startidx + $idx] ?
        if (my @matches = 
            ${$self->{line_buffer}}[$startidx + $idx] =~ /$pattern->[$idx]/) {
          $idx++;
          push(@all_matches, @matches);
        } else {
          last;
        }
      }
      if ($idx == $patterns) {
        return @all_matches;
      } else {
        return ();
      }
    }
  } else {
    #my @matches = $line =~ /$pattern/;
    my @matches = $format->{matchfunc}($line);
    return @matches;
  }
}

# inherit
#
# copy variable and slot definitions of a followed format to the current format
#
sub inherit {
  my $self = shift;
  my $ancestor = shift;
  $self->merge_hash($self->{variables}, $ancestor->{variables});
  $self->merge_hash($self->{slots}, $ancestor->{slots});
}

# get_severity_mapping
#
# get the numerical nagios level for a tivoli level
#
sub get_severity_mapping {
  my $self = shift;
  my $tivoli_severity = lc shift;
  return $self->{severity_mappings}->{$tivoli_severity};
}

# set_severity_mapping
#
# set the numerical nagios level for a tivoli level
#
sub set_severity_mapping {
  my $self = shift;
  my $tivoli_severity = lc shift;
  my $nagios_severity = shift;
  $self->{severity_mappings}->{$tivoli_severity} = $nagios_severity;
}

# set_format_mappings
#
# set runtime values for LABEL, DEFAULT,...
#
sub set_format_mappings {
    my $self = shift;
    my %mappings = @_;
    foreach (keys %mappings) {
      $self->{format_mappings}->{$_} = $mappings{$_};
    }
}

sub add_format {
  my $self = shift;
  my $format = shift;
  if (($format->{name} ne '*DISCARD*') &&
      (! $format->has_slots() || ! $format->get_slot('severity'))) {
      #printf STDERR "FORMAT %s skipped\n", $format->{name};
    $format->{can_match} = 0;
  } else {
    $format->{can_match} = 1;
  }
  push(@{$self->{formats}}, $format);
}

sub get_format_by_name {
  my $self = shift;
  my $name = shift;
  foreach (@{$self->{formats}}) {
    return $_ if $_->{name} eq $name;
  }
  return undef;
}

sub merge_hash {
    my $self  = shift;
    my $hash1 = shift;
    my $hash2 = shift;

    for my $key (keys %{$hash2}) {
        $hash1->{$key} = $hash2->{$key};
    }
    return($hash1);
}


package Nagios::Tivoli::Config::Logfile::Format;

use strict;
use warnings;
use Carp;
use vars qw(@ISA);

@ISA = qw(Nagios::Tivoli::Config::Logfile);

sub new {
  my($this, $param ) = @_;
  my $class = ref($this) || $this;

  my $self = {
      name => '',
      lineno => 0,
      slots => {},
      variables => {},
      severity_mappings => {},
  };
  bless $self, $class;

  if (ref($param) eq "HASH") {
    for my $key (keys %{$param}) {
      if (!defined $self->{lc $key}) {
        carp("unrecognized parameter: $key");
      } else {
        if (ref($param->{$key}) eq 'HASH') {
          $self->merge_hash($self->{$key}, $param->{$key});
        } else {
          $self->{lc $key} = $param->{$key};
        }
      }
    }
  }
  if (!defined $self->{name}) {
    die "please either specify formatfile or formatstring";
  }
  $self->add_match_closure();
  return $self;
}

sub add_slot {
  my $self = shift;
  my $slot = shift;
  my $value = shift;
  $self->{slots}->{$slot} = $value;
}

sub get_slot {
  my $self = shift;
  my $slot = shift;
  return $self->{slots}->{$slot};
}

sub has_slots {
  my $self = shift;
  return scalar (keys %{$self->{slots}});
}

sub add_variable {
  my $self = shift;
  my $variable = shift;
  my $value = shift;
  $self->{variables}->{$variable} = $value;
}

sub get_variable {
  my $self = shift;
  my $variable = shift;
  return $self->{variables}->{$variable};
}

sub has_variables {
  my $self = shift;
  return scalar (keys %{$self->{variables}});
}

sub add_match_closure {
  my $self = shift;
  # creates a function which keeps the compiled version of self->pattern
  $self->{matchfunc} = eval "sub { local \$_ = shift; return m/\$self->{pattern}/o; }";
}


package Nagios::Tivoli::Config::Logfile::Hit;

use strict;
use warnings;
use Carp;
use vars qw(@ISA);

@ISA = qw(Nagios::Tivoli::Config::Logfile::Format);

sub new {
  my($this, $param ) = @_;
  my $class = ref($this) || $this;

  my $self = {
      format => $param->{format},
      logline => $param->{logline},
      format_mappings => $param->{format_mappings},
      severity_mappings => $param->{severity_mappings},
      matches => {},
      variables => {},
      slots => {},
  };
  bless $self, $class;
  my $matchcnt = 1;
  map { $self->{matches}->{$matchcnt++} = $_; } @{$param->{matches}};
  $self->init();
  return $self;
}

sub init {
  my $self = shift;
  $self->{severity} = $self->{format}->{slots}->{severity};
  $self->{format_name} = $self->{format}->{name};
  $self->merge_hash($self->{variables}, $self->{format}->{variables});
  $self->merge_hash($self->{slots}, $self->{format}->{slots});
  # resolve pattern groups in internal variables
  foreach my $var (keys %{$self->{variables}}) {
    if ($self->{variables}->{$var} =~ /^\$(\d+)/) {
      if (defined $self->{matches}->{$1}) {
        $self->{variables}->{$var} = $self->{matches}->{$1};
      } else {
        printf STDERR "cannot replace \$%d in var %s\n", $1, $var;
      }
    }
  }
  # resolve pattern groups and format reserved words in slots
  foreach my $slot (keys %{$self->{slots}}) {
    if ($self->{slots}->{$slot} =~ /^\$(\d+)/) {
      if (defined $self->{matches}->{$1}) {
        $self->{slots}->{$slot} = $self->{matches}->{$1};
      } else {
        printf STDERR "cannot replace \$%d in slot %s\n", $1, $slot;
      }
    } elsif ($self->{slots}->{$slot} eq 'DEFAULT') {
      if ($slot eq 'hostname') {
        $self->{slots}->{$slot} = $self->{format_mappings}->{hostname};
      } elsif ($slot eq 'fqhostname') {
        $self->{slots}->{$slot} = $self->{format_mappings}->{fqhostname};
      } elsif ($slot eq 'origin') {
        $self->{slots}->{$slot} = $self->{format_mappings}->{origin};
      } else {
        $self->{slots}->{$slot} = 'check_logfiles';
      }
    } elsif ($self->{slots}->{$slot} eq 'LABEL') {
      $self->{slots}->{$slot} = $self->{format_mappings}->{LABEL};
    } elsif ($self->{slots}->{$slot} eq 'FILENAME') {
      $self->{slots}->{$slot} = $self->{format_mappings}->{FILENAME};
    } else {
    }
  }
  foreach my $slot (keys %{$self->{slots}}) {
    if ($self->{slots}->{$slot} =~ /PRINTF/i) {
      $self->{slots}->{$slot} = $self->printf($self->{slots}->{$slot});
    }
  }
  $self->{subject} = $self->{slots}->{msg} || $self->{logline};
  #delete $self->{slots}->{msg};
}

sub printf {
  my $self = shift;
  my $text = shift;
  my @printf = $text =~ m/printf\("(.*?)"\s*,\s*(.*)\)/i;
  my $result = $text;
  my @replacements;
  for my $key (split /\s*,\s*/, $printf[1]) {
    if (defined $self->{variables}->{$key}) {
      push @replacements, $self->{variables}->{$key};
    } elsif (defined $self->{slots}->{$key}) {
      push @replacements, $self->{slots}->{$key};
    } else {
      print STDERR "$key not found\n";
      push @replacements,  '';
    }
  }
  eval {
      $result = sprintf($printf[0], @replacements);
  };
  return($result);
}

sub get_nagios_severity {
  my $self = shift;
  return $self->get_severity_mapping($self->{slots}->{severity});
}


package Nagios::CheckLogfiles;

use strict;
use IO::File;
use File::Basename;
use File::Spec;
use File::Find;
use File::Path;
use Cwd;
use Data::Dumper;
#use Net::Domain qw(hostname hostdomain hostfqdn);
use Socket;
use POSIX qw(strftime);
use IPC::Open2;
use Errno;


use constant GZIP => '/usr/bin/gzip';
my $ERROR_OK = 0;
my $ERROR_WARNING = 1;
my $ERROR_CRITICAL = 2;
my $ERROR_UNKNOWN = 3;

our $ExitCode = $ERROR_OK;
our $ExitMsg = "OK";
my(%ERRORS, $TIMEOUT);
%ERRORS = ( OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 );
$TIMEOUT = 60;

$| = 1;

eval "require Win32;";
#eval "require Net::Domain qw(hostname hostdomain hostfqdn);";
eval "require Net::Domain;";
{
  local $^W = 0; # shut up!
  eval "require 'syscall.ph'";
  eval "require 'sys/resource.ph'";
}

sub new {
  my $class = shift;
  my $params = shift;
  my $self = bless {} , $class;
  return $self->init($params);
}

#
#  Read a hash with parameters
#
sub init {
  my $self = shift;
  my $params = shift;
  my($sec, $min, $hour, $mday, $mon, $year) = (localtime)[0, 1, 2, 3, 4, 5];
  $year += 1900; $mon += 1;
  $self->{tracefile} = $self->system_tempdir().'/check_logfiles.trace';
  $self->{trace} = -e $self->{tracefile} ? 1 : 0;
  $self->{verbose} = $params->{verbose} || 0;
  $self->{htmlencode} = $params->{htmlencode} || 0;
  $self->{seekfilesdir} = $params->{seekfilesdir} || '/var/tmp/check_logfiles';
  $self->{protocolsdir} = $params->{protocolsdir} || '/tmp';
  $self->{scriptpath} = $params->{scriptpath} || '/bin:/sbin:/usr/bin:/usr/sbin';
  $self->{protocolretention} = ($params->{protocolretention} || 7) * 24 * 3600;
  $self->{macros} = $params->{macros};
  $self->{timeout} = $params->{timeout} || 360000;
  $self->{pidfile} = $params->{pidfile};
  $self->{perfdata} = "";
  $self->{searches} = [];
  $self->{selectedsearches} = $params->{selectedsearches} || [];
  $self->{dynamictag} = $params->{dynamictag} || "";
  $self->{cmdlinemacros} = $params->{cmdlinemacros} || {};
  $self->{reset} = $params->{reset} || 0;
  $self->{unstick} = $params->{unstick} || 0;
  $self->{warning} = $params->{warning} || 0;
  $self->{critical} = $params->{critical} || 0;
  $self->{matchlines} = { OK => [], WARNING => [], CRITICAL => [], UNKNOWN => [] };
  $self->init_macros;
  $self->default_options({ prescript => 1, smartprescript => 0,
      supersmartprescript => 0, postscript => 1, smartpostscript => 0,
      supersmartpostscript => 0, report => 'short', maxlength => 4096,
      seekfileerror => 'critical', logfileerror => 'critical', 
      protocolfileerror => 'ok',
      maxmemsize => 0, rotatewait => 0, htmlencode => 0,
      outputhitcount => 1, rununique => 0, preview => 1,
  });
  if ($params->{cfgfile}) {
    if (ref($params->{cfgfile}) eq "ARRAY") {
      # multiple cfgfiles found in a config dir
      my @tmp_searches = ();
      $self->{cfgbase} = $params->{cfgbase} || "check_logfiles";
      $self->late_init_macros;
      foreach my $cfgfile (@{$params->{cfgfile}}) {
        $self->{cfgfile} = $cfgfile;
        if (! $self->init_from_file()) {
          return undef;
        }
        push(@tmp_searches, @{$self->{searches}});
        $self->{searches} = [];
      }
      my %seen = ();
      # newer searches replace searches with the same tag
      @tmp_searches = reverse map { 
        if (! exists $seen{$_->{tag}}) {
          $seen{$_->{tag}}++;
          $_;
        } else {
          ();
        }
      } reverse @tmp_searches;
      $self->{searches} = \@tmp_searches;
      my $uniqueseekfile = undef;
      my $uniqueprotocolfile = undef;
      foreach (@{$self->{searches}}) {
        $_->{cfgbase} = "check_logfiles";
        next if $_->{tag} eq "prescript";
        next if $_->{tag} eq "postscript";
        $_->construct_seekfile();
      }
      #$self->{cfgbase} = (split /\./, basename($params->{cfgfile}->[0]))[0];
      $self->{cfgbase} = "check_logfiles";
    } elsif ($params->{cfgfile} =~ /%0A/) {
      # this must be an encoded flat file
      $self->{cfgfile} = $params->{cfgfile};
      $self->{cfgbase} = "flatfile";
      $self->late_init_macros;
      if (! $self->init_from_file()) {
        return undef;
      }
    } else {
      $self->{cfgfile} = $params->{cfgfile};
      $self->{cfgbase} = (split /\./, basename($self->{cfgfile}))[0];
      $self->late_init_macros;
      if (! $self->init_from_file()) {
        return undef;
      }
    } 
    # if there is a dynamictag parameter then replace template names with
    # template_dynamictagtag
    if (scalar(@{$self->{selectedsearches}})) {
      @{$self->{searches}} = map {
        my $srch = $_;
        if (grep { $srch->{tag} eq $_ } @{$self->{selectedsearches}}) {
          # gilt sowohl fuer normale searches
          $srch;
        } elsif ($srch->{template} && grep { $srch->{template} eq $_ } @{$self->{selectedsearches}}) {
          # als auch fuer template (tag ist hier bereits template."_".tag,
          # wobei tag auf der kommandozeile uebergeben wurde)
          $srch;
        } elsif (grep { $_ =~ /[*?]/ && $srch->{tag} =~ /$_/ } @{$self->{selectedsearches}}) {
          # --selectedsearches "regexp,regexp"
          $srch;
        } elsif ($srch->{tag} eq "prescript") {
          $srch;
        } elsif ($srch->{tag} eq "postscript") {
          $srch;
        } else {
          $self->trace("skipping non-selected search %s", $srch->{tag});
          ();
        }
      } @{$self->{searches}};
    }
  } else {
    $self->{cfgbase} = $params->{cfgbase} || "check_logfiles";
    $self->late_init_macros;
    # first the global options (from the commandline in this case)
    $self->refresh_options($params->{options});
    $self->{seekfilesdir} = $self->relocate_dir("seekfilesdir", $self->{seekfilesdir});
    $self->resolve_macros(\$self->{seekfilesdir});
    foreach (@{$params->{searches}}) {
      $_->{seekfilesdir} = $self->{seekfilesdir};
      $_->{relocate_seekfilesdir} = $self->{relocate_seekfilesdir};
      $_->{scriptpath} = $self->{scriptpath};
      %{$_->{macros}} = %{$self->{macros}};
      $_->{tracefile} = $self->{tracefile};
      $_->{cfgbase} = $self->{cfgbase};
      if (my $search = Nagios::CheckLogfiles::Search->new($_)) {
        # maybe override default search options with global ones (ex. report)
        $search->refresh_default_options($self->get_options('report,seekfileerror,logfileerror,protocolfileerror'));
        push(@{$self->{searches}}, $search);
      } else {
        $ExitCode = $ERROR_UNKNOWN;
        $ExitMsg = sprintf "cannot create %s search %s",
            $_->{type}, $_->{tag};
        return undef;
      }
    }  
  }
  if (defined(&Win32::GetShortPathName) && ($^O =~ /Win/)) {
    # if this is true windows (not cygwin) and if the path exists
    # then transform it to a short form. undef if path does not exist.
    if (my $tmpshortpath = &Win32::GetShortPathName($self->{protocolsdir})) {
      $self->{protocolsdir} = $tmpshortpath;
    }
  }
  if ($self->get_option('report') !~ /^(long|short|html)$/) {
    $ExitCode = $ERROR_UNKNOWN;
    $ExitMsg = sprintf "UNKNOWN - output must be short, long or html";
    return undef;
  }
  $self->{protocolfile} = 
      sprintf "%s/%s.protocol-%04d-%02d-%02d-%02d-%02d-%02d",
      $self->{protocolsdir}, $self->{cfgbase}, 
      $year, $mon, $mday, $hour, $min, $sec;
  $self->{protocololdfiles} = sprintf "%s/%s.protocol-*-*-*-*-*-*",
      $self->{protocolsdir}, $self->{cfgbase};
  $self->{protocolfh} = new IO::File;
  $self->{protocolwritten} = 0;
  $self->{allerrors} = { OK => 0, WARNING => 0, CRITICAL => 0, UNKNOWN => 0 };
  # if parameters update
  if (@{$self->{searches}}) {
    $self->{exitcode} = $ExitCode;
    $self->{exitmessage} = $ExitMsg;
    return $self;
  } else {
    $ExitCode = $ERROR_UNKNOWN;
    $ExitMsg = sprintf "UNKNOWN - configuration incomplete";
    return undef;
  }
}

sub init_from_file {
  my $self = shift;
  my $abscfgfile;
  #
  #  variables from the config file.
  #
  our($seekfilesdir, $protocolsdir, $scriptpath, $protocolretention,
      $prescript, $prescriptparams ,$prescriptstdin, $prescriptdelay,
      $postscript, $postscriptparams, $postscriptstdin, $postscriptdelay,
      @searches, @logs, $tracefile, $options, $report, $timeout, $pidfile,
      $CHECK_LOGFILES_PRIVATESTATE);
  our $MACROS = {};
  if ($^O =~ /MSWin/) {
    $ENV{HOME} = $ENV{USERPROFILE};
  }
  if ($self->{cfgbase} eq "flatfile") {
    $self->{cfgfile} =~ s/\%([A-Fa-f0-9]{2})/pack('C', hex($1))/seg;
    eval $self->{cfgfile};
    if ($@) {
      $ExitCode = $ERROR_UNKNOWN;
      $ExitMsg = sprintf "UNKNOWN - syntax error %s", (split(/\n/, $@))[0];
      return undef;
    }
    $abscfgfile = "/dummy/dummy/".(unpack("H*", $self->{cfgfile}));
  } else {
    if (-f $self->{cfgfile}) {
      $abscfgfile = $self->{cfgfile};
    } elsif (-f $self->{cfgfile}.'.cfg') {
      $abscfgfile = $self->{cfgfile}.'.cfg';
    } elsif (-f $ENV{HOME}.'/'.$self->{cfgfile}) {
      $abscfgfile = $ENV{HOME}.'/'.$self->{cfgfile};
    } elsif (-f $ENV{HOME}.'/'.$self->{cfgfile}.'.cfg') {
      $abscfgfile = $ENV{HOME}.'/'.$self->{cfgfile}.'.cfg';
    } else {
      $ExitCode = $ERROR_UNKNOWN;
      $ExitMsg = sprintf "UNKNOWN - can not load configuration file %s", 
          $self->{cfgfile};
      return undef;
    }
    $abscfgfile = File::Spec->rel2abs($abscfgfile) 
        unless File::Spec->file_name_is_absolute($abscfgfile);
    delete $INC{$abscfgfile}; # this is mostly because of the tests which cache the cfgfile
    eval {
      require $abscfgfile;
    };
    if ($@) {
      $ExitCode = $ERROR_UNKNOWN;
      $ExitMsg = sprintf "UNKNOWN - syntax error %s", (split(/\n/, $@))[0];
      return undef;
    }
    # We might need this for a pidfile
  }

  $self->merge_macros($MACROS); # merge the defaultmacros with macros from the file
  $seekfilesdir ||= $self->{seekfilesdir};
  $protocolsdir ||= $self->{protocolsdir};
  $scriptpath ||= $self->{scriptpath};
  # We might need this for a pidfile
  $self->{abscfgfile} = $abscfgfile;
  $seekfilesdir = $self->relocate_dir("seekfilesdir", $seekfilesdir, dirname(dirname($abscfgfile)));
  return undef if ! $seekfilesdir;
  $protocolsdir = $self->relocate_dir("protocolsdir", $protocolsdir, dirname(dirname($abscfgfile)));
  $scriptpath = $self->relocate_dir("scriptpath", $scriptpath, dirname(dirname($abscfgfile)));
  $self->resolve_macros(\$seekfilesdir);
  $self->resolve_macros(\$protocolsdir);
  $self->resolve_macros(\$scriptpath);

  $self->{tracefile} = $tracefile if $tracefile;
  $self->{trace} = -e $self->{tracefile} ? 1 : 0;
  # already done one level above $self->{cfgbase} = (split /\./, basename($self->{cfgfile}))[0];
  $self->{seekfilesdir} = $seekfilesdir if $seekfilesdir;
  $self->{protocolsdir} = $protocolsdir if $protocolsdir;
  $self->{scriptpath} = $scriptpath if $scriptpath;
  $self->{protocolretention} = ($protocolretention * 24 * 3600) if $protocolretention;
  $self->{prescript} = $prescript if $prescript;
  $self->{prescriptparams} = $prescriptparams if $prescriptparams;
  $self->{prescriptstdin} = $prescriptstdin if $prescriptstdin;
  $self->{prescriptdelay} = $prescriptdelay if $prescriptdelay;
  $self->{postscript} = $postscript if $postscript;
  $self->{postscriptparams} = $postscriptparams if $postscriptparams;
  $self->{postscriptstdin} = $postscriptstdin if $postscriptstdin;
  $self->{postscriptdelay} = $postscriptdelay if $postscriptdelay;
  $self->{timeout} = $timeout || 360000;
  $self->{pidfile} = $pidfile if $pidfile;
  $self->{privatestate} = {};
  $self->refresh_options($options);
  if (@logs) {
    #
    # Since version 1.4 the what/where-array is called @searches.
    # To stay compatible, @logs is still recognized.
    #
    @searches = @logs;
  }
  if ($self->{options}->{prescript}) {
    $_->{scriptpath} = $self->{scriptpath};
    %{$_->{macros}} = %{$self->{macros}};
    $_->{tracefile} = $self->{tracefile};
    $_->{cfgbase} = $self->{cfgbase};
    $_->{script} = $self->{prescript};
    $_->{scriptparams} = $self->{prescriptparams};
    $_->{scriptstdin} = $self->{prescriptstdin};
    $_->{scriptdelay} = $self->{prescriptdelay};   
    $_->{options} = sprintf "%s%sscript",
        $self->{options}->{supersmartprescript} ? "super" : "",
        $self->{options}->{smartprescript} ? "smart" : "";
    $_->{privatestate} = $self->{privatestate};
    my $search = Nagios::CheckLogfiles::Search::Prescript->new($_);
    push(@{$self->{searches}}, $search); 
  }
  foreach (@searches) {
    $_->{seekfilesdir} = $self->{seekfilesdir};
    $_->{relocate_seekfilesdir} = $self->{relocate_seekfilesdir};
    $_->{scriptpath} = $self->{scriptpath};
    %{$_->{macros}} = %{$self->{macros}};
    $_->{tracefile} = $self->{tracefile};
    $_->{cfgbase} = $self->{cfgbase};
    if ((exists $_->{template}) && ! $self->{dynamictag}) {
      # skip templates if they cannot be tagged
      next;
    }
    $_->{dynamictag} = $self->{dynamictag};
    if (my $search = Nagios::CheckLogfiles::Search->new($_)) {
      $search->refresh_options($self->get_options('report,seekfileerror,logfileerror,protocolfileerror'));
      push(@{$self->{searches}}, $search);
      $_->{privatestate}->{$search->{tag}} = $search->{privatestate};
    } else {
      $ExitCode = $ERROR_UNKNOWN;
      $ExitMsg = sprintf "cannot create %s search %s",
          $_->{type}, $_->{tag};
      return undef;
    }
  }
  if ($self->{options}->{postscript}) {
    $_->{scriptpath} = $self->{scriptpath};
    %{$_->{macros}} = %{$self->{macros}};
    $_->{tracefile} = $self->{tracefile};
    $_->{cfgbase} = $self->{cfgbase};
    $_->{script} = $self->{postscript};
    $_->{scriptparams} = $self->{postscriptparams};
    $_->{scriptstdin} = $self->{postscriptstdin};
    $_->{scriptdelay} = $self->{postscriptdelay};   
    $_->{options} = sprintf "%s%sscript",
        $self->{options}->{supersmartpostscript} ? "super" : "",
        $self->{options}->{smartpostscript} ? "smart" : "";
    $_->{privatestate} = $self->{privatestate};
    my $search = Nagios::CheckLogfiles::Search::Postscript->new($_);
    push(@{$self->{searches}}, $search); 
  }
  return $self;
}

sub run {
  my $self = shift;
  if ($self->{reset}) {
    foreach my $search (@{$self->{searches}}) {
      if ($search->{tag} ne "prescript" && $search->{tag} ne "postscript") {
        $search->rewind();
      }
    }
    return $self;
  }
  if ($self->{unstick}) {
    foreach my $search (@{$self->{searches}}) {
      if ($search->{tag} ne "prescript" && $search->{tag} ne "postscript") {
        $search->unstick();
      }
    }
    return $self;
  }
  if ($self->{options}->{rununique}) {
    $self->{pidfile} = $self->{pidfile} || $self->construct_pidfile();
    if (! $self->check_pidfile()) {
      $self->trace("Exiting because another check is already running");
      printf "Exiting because another check is already running\n";
      exit 3;
    }
  }
  if ($self->get_option('rotatewait')) {
    $self->await_while_rotate();
  }
  my $protocol_had_error = 0;
  if (! -w $self->{protocolsdir}) {
    $protocol_had_error = 1;
  }
  foreach my $search (@{$self->{searches}}) {
    if (1) { # there will be a timesrunningout variable
      if ($search->{tag} eq "postscript") {
        $search->{macros}->{CL_SERVICESTATEID} = $self->{exitcode};
        $search->{macros}->{CL_SERVICEOUTPUT} = $self->{exitmessage};
        $search->{macros}->{CL_LONGSERVICEOUTPUT} = 
            $self->{long_exitmessage} || $self->{exitmessage};
        $search->{macros}->{CL_SERVICEPERFDATA} = $self->{perfdata};
        $search->{macros}->{CL_PROTOCOLFILE} = $self->{protocolfile};
        if ($search->{options}->{supersmartscript}) {
          # 
          #  Throw away everything found so far. Supersmart postscripts
          #  have the last word.
          #
          $self->reset_result();        
        }       
      }      
      $search->{verbose} = $self->{verbose};
      $search->{timeout} = $self->{timeout};
      $search->run();
      if (($search->{tag} eq "prescript") && 
          ($search->{options}->{supersmartscript}) &&
          ($search->{exitcode} > 0)) {
        #
        #  Prepare for a premature end. A failed supersmart prescript
        #  will abort the whole script.
        #
        $self->reset_result();
        $self->trace("failed supersmart prescript. aborting...");
      }
      $_->{privatestate}->{$search->{tag}} = $search->{privatestate};
      if ($search->{options}->{protocol}) {
        # must write protocol
        if ($protocol_had_error) {
          $search->addevent($self->get_option('protocolfileerror'),
              sprintf "cannot write protocol file %s! check your filesystem (permissions/usage/integrity) and disk devices", $self->{protocolsdir})
              if lc $self->get_option('protocolfileerror') ne 'ok';
        } else {
          if (scalar(@{$search->{matchlines}->{CRITICAL}}) ||
              scalar(@{$search->{matchlines}->{WARNING}}) ||
              scalar(@{$search->{matchlines}->{UNKNOWN}})) {
            if ($self->{protocolfh}->open($self->{protocolfile}, "a")) {
              foreach (qw(CRITICAL WARNING UNKNOWN)) {
                if (@{$search->{matchlines}->{$_}}) {
                  $self->{protocolfh}->print(sprintf "%s Errors in %s (tag %s)\n",
                      $_, $search->{logbasename}, $search->{tag});
                  foreach (@{$search->{matchlines}->{$_}}) {
                    $self->{protocolfh}->printf("%s\n", $_);
                  }
                }
              }
              $self->{protocolfh}->close();
              $self->{protocolwritten} = 1;
            }
          }
        }
      }
      if ($search->{options}->{count}) {
        foreach (qw(OK WARNING CRITICAL UNKNOWN)) {
          $self->{allerrors}->{$_} += scalar(@{$search->{matchlines}->{$_}});
          if ($search->{lastmsg}->{$_}) {
            $self->{lastmsg}->{$_} = $search->{lastmsg}->{$_};
          }
          foreach my $searchmatch (@{$search->{matchlines}->{$_}}) {
            unshift(@{$self->{matchlines}->{$_}}, $searchmatch);
          }
          while (scalar(@{$self->{matchlines}->{$_}}) >
              $self->get_option("preview")) {
            my $runter = pop(@{$self->{matchlines}->{$_}});
          }
        }
      }
      $self->formulate_result();
      if (($search->{tag} eq "prescript") && 
          ($search->{options}->{supersmartscript}) &&
          ($search->{exitcode} > 0)) {
        #
        #  Failed supersmart prescript. I'm out...
        #
        last;
      } elsif (($search->{tag} eq "postscript") && 
          ($search->{options}->{supersmartscript})) {
        my $codestr = {reverse %ERRORS}->{$search->{exitcode}};
        ($self->{exitmessage}, $self->{perfdata}) = 
            split(/\|/, $search->{lastmsg}->{$codestr}, 2);
        $self->{exitcode} = $search->{exitcode};
      }
    }
  }
  $self->cleanup_protocols();
  if ($self->get_option("htmlencode")) {
    $self->htmlencode(\$self->{exitmessage});
    $self->htmlencode(\$self->{long_exitmessage});
  }
  if ($self->{options}->{rununique}) {
    $self->cleanup_pidfile();
  }
  return $self;
}

sub htmlencode {
  my $self = shift;
  my $pstring = shift;
  return if ! $$pstring;
  $$pstring =~ s/&/&amp/g;
  $$pstring =~ s/</&lt/g;
  $$pstring =~ s/>/&gt/g;
  $$pstring =~ s/"/&quot/g;
}


sub await_while_rotate {
  my $self = shift;
  my($sec, $min, $hour, $mday, $mon, $year) = (localtime)[0, 1, 2, 3, 4, 5];
  if (($min == 0 || $min == 15 || $min == 30 || $min == 45) && $sec < 15) {
    $self->trace("waiting until **:**:15");
    foreach (1..(15 - $sec)) {
      sleep 1;
    }
  }
}

sub formulate_result {
  my $self = shift;
  #
  #  create the summary from all information collected so far
  #
  $self->{hint} = sprintf "(%s", join(", ", grep { $_ }
    ($self->{allerrors}->{CRITICAL} ? 
        sprintf "%d errors", $self->{allerrors}->{CRITICAL} : undef,
    $self->{allerrors}->{WARNING} ? 
        sprintf "%d warnings", $self->{allerrors}->{WARNING} : undef,
    $self->{allerrors}->{UNKNOWN} ? 
        sprintf "%d unknown", $self->{allerrors}->{UNKNOWN} : undef));
  if ($self->{protocolwritten}) {
    $self->{hint} .= sprintf " in %s)", basename($self->{protocolfile});
  } else {
    $self->{hint} .= ")";
  }
  foreach my $level (qw(CRITICAL WARNING UNKNOWN OK)) {
    my $preview;
    my $continue;
    if ($self->get_option("preview") > 1) {
      if (scalar(@{$self->{matchlines}->{$level}}) <
          $self->get_option("preview")) {
        $preview = join(", ", @{$self->{matchlines}->{$level}});
      } else {
        $preview = join(", ", @{$self->{matchlines}->{$level}});
      }
      $continue = scalar(@{$self->{matchlines}->{$level}}) <=
          $self->get_option("preview") ? "" : "...";
    } else {
      $preview = $self->{lastmsg}->{$level};
      $continue = $self->{allerrors}->{$level} == 1 ? "" : "...";
    }
    $self->{exitcode} = $ERRORS{$level};
    if (($level ne "OK") && ($self->{allerrors}->{$level})) {
      $self->{exitmessage} = sprintf "%s%s - %s %s", $level, 
          $self->get_option("outputhitcount") ? " - ".$self->{hint} : "",
          $preview,
          $continue;
      last;
    } else {
      $self->{exitmessage} = sprintf "OK - no errors or warnings";
    }
  }
  $self->{perfdata} = join (" ", 
      map { $_->formulate_perfdata(); if ($_->{perfdata}) {$_->{perfdata}} else {()} }
      @{$self->{searches}});
  if ($self->get_option('report') ne "short") {
    $self->formulate_long_result();
  }
}

sub formulate_long_result {
  my $self = shift;
  my $maxlength = $self->get_option('maxlength');
  $self->{long_exitmessage} = "";
  my $prefix = ($self->get_option('report') eq "html") ?
      "<table style=\"border-collapse: collapse;\">" : "";
  my $suffix = ($self->get_option('report') eq "html") ?
      "</table>" : "";
  my $messagelen = length($prefix) + length($suffix) +
      length($self->{exitmessage});
  my $line = "";
   
  foreach my $search (@{$self->{searches}}) {
    next if $search->{tag} eq 'postscript';
    if (scalar(@{$search->{matchlines}->{CRITICAL}}) ||
        scalar(@{$search->{matchlines}->{WARNING}}) ||
        scalar(@{$search->{matchlines}->{UNKNOWN}})) {
      if ($self->get_option('report') eq "html") {
        $line =
            sprintf "<tr valign=\"top\"><td class=\"service%s\">tag %s</td></tr>",
                ((scalar(@{$search->{matchlines}->{CRITICAL}}) && "CRITICAL") ||
                 (scalar(@{$search->{matchlines}->{WARNING}}) && "WARNING") ||
                 (scalar(@{$search->{matchlines}->{UNKNOWN}}) && "UNKNOWN")),
                $search->{tag};
      } else {
        $line =
            sprintf "tag %s %s\n",
                $search->{tag},
                ((scalar(@{$search->{matchlines}->{CRITICAL}}) && "CRITICAL") ||
                 (scalar(@{$search->{matchlines}->{WARNING}}) && "WARNING") ||
                 (scalar(@{$search->{matchlines}->{UNKNOWN}}) && "UNKNOWN"));
      }
      if ($messagelen + length($line) < $maxlength) {
        $self->{long_exitmessage} .= $line;
        $messagelen += length($line);
      } else {
        last;
      }
      foreach my $level (qw(CRITICAL WARNING UNKNOWN)) {
        foreach my $message (@{$search->{matchlines}->{$level}}) {
          if ($self->get_option('report') eq "html") {
            $message =~ s/</</g;
            $message =~ s/>/>/g;
            $line =
                sprintf "<tr valign=\"top\"><td nowrap width=\"100%%\" class=\"service%s\" style=\"border: 1px solid black;\">%s</td></tr>",
                $level, $message;
          } else {
            $line = sprintf "%s\n", $message;
          }
          if ($messagelen + length($line) < $maxlength) {
            $self->{long_exitmessage} .= $line;
            $messagelen += length($line);
          } else {
            last;
          }
        }
      }
    }
  }
  if ($self->{long_exitmessage}) {
    $self->{long_exitmessage} = sprintf "%s%s%s\n",
        $prefix, $self->{long_exitmessage}, $suffix;
  }
}

sub reset_result {
  my $self = shift;
  $self->{allerrors} = { OK => 0, WARNING => 0, CRITICAL => 0, UNKNOWN => 0 };
  foreach my $search (@{$self->{searches}}) {
    next if $search->{tag} eq 'postscript';
    next if $search->{tag} eq 'prescript';
    $search->{matchlines} = {
        OK => [],
        WARNING => [],
        CRITICAL => [],
        UNKNOWN => [],
    }
  }
}

sub reset {
  my $self = shift;
  $self->{allerrors} = { OK => 0, WARNING => 0, CRITICAL => 0, UNKNOWN => 0 };
  foreach my $level (qw(OK CRITICAL WARNING UNKNOWN)) {
    $self->{lastmsg}->{$level} = "";
  }
  foreach my $search (@{$self->{searches}}) {
    $search->reset();
  }
}

sub cleanup_protocols {
  my $self = shift;
  #
  #  cleanup old protocol files
  #
  #
  if ($self->{protocololdfiles} =~ /[^\\][ ]/) {
    # because Core::glob splits the argument on whitespace
    $self->{protocololdfiles} =~ s/( )/\\$1/g;
  }
  foreach my $oldprotocolfile (glob "$self->{protocololdfiles}") {
    if ((stat $oldprotocolfile)[9] < (time - $self->{protocolretention})) {
      $self->trace("deleting old protocol %s", $oldprotocolfile);
      unlink $oldprotocolfile;
    }
  }
}

sub init_macros {
  my $self = shift;
  my($sec, $min, $hour, $mday, $mon, $year) = (localtime)[0, 1, 2, 3, 4, 5];
  my $cw = $^O =~ /MSWin/ ? 0 : 
      strftime("%V", $sec, $min, $hour, $mday, $mon, $year, -1, -1, -1);
  $year += 1900; $mon += 1;
  #
  #  Set default values for the built-in macros.
  #
  my $DEFAULTMACROS = {
      CL_DATE_YYYY => sprintf("%04d", $year),
      CL_DATE_YY => substr($year,2,2),
      CL_DATE_MM => sprintf("%02d", $mon),
      CL_DATE_DD => sprintf("%02d", $mday),
      CL_DATE_HH => sprintf("%02d", $hour),
      CL_DATE_MI => sprintf("%02d", $min),
      CL_DATE_SS => sprintf("%02d", $sec),
      CL_DATE_TIMESTAMP => sprintf("%10d", time),
      CL_DATE_CW => sprintf("%02d", $cw),
      CL_NSCA_HOST_ADDRESS => "127.0.0.1",
      CL_NSCA_PORT => 5667,
      CL_NSCA_TO_SEC => 10,
      CL_NSCA_CONFIG_FILE => "/usr/local/nagios/etc/send_nsca.cfg",
  };
  if (defined(&Win32::LoginName)) {
    $DEFAULTMACROS->{CL_USERNAME} = &Win32::LoginName();
    $DEFAULTMACROS->{CL_HAS_WIN32} = 1;
  } else {
    $DEFAULTMACROS->{CL_USERNAME} = scalar getpwuid $>;
    $DEFAULTMACROS->{CL_HAS_WIN32} = 0;
  }
  if (defined(&Net::Domain::hostname)) {
    $DEFAULTMACROS->{CL_HOSTNAME} = &Net::Domain::hostname();
    $DEFAULTMACROS->{CL_DOMAIN} = &Net::Domain::hostdomain();
    $DEFAULTMACROS->{CL_FQDN} = &Net::Domain::hostfqdn();
    $DEFAULTMACROS->{CL_HAS_NET_DOMAIN} = 1;
  } else {
    $DEFAULTMACROS->{CL_HOSTNAME} = POSIX::uname();
    $DEFAULTMACROS->{CL_DOMAIN} = "localdomain";
    $DEFAULTMACROS->{CL_FQDN} = POSIX::uname().'.'.'localdomain';
    $DEFAULTMACROS->{CL_HAS_NET_DOMAIN} = 0;
  }
#printf STDERR "%s\n", Data::Dumper::Dumper($DEFAULTMACROS);
  $DEFAULTMACROS->{CL_IPADDRESS} =
      scalar gethostbyname($DEFAULTMACROS->{CL_HOSTNAME}) ?
      inet_ntoa(scalar gethostbyname($DEFAULTMACROS->{CL_HOSTNAME})) :
      '127.0.0.1';
  #
  #  Add self-defined macros to the defaultmacros structure or overwrite
  #  already defined macros.
  #
  if ($self->{macros}) {
    foreach (keys %{$self->{macros}}) {
      $DEFAULTMACROS->{$_} = $self->{macros}->{$_};
    }
  }
  #
  #  Add self-defined macros from the command line 
  #  --macro CL_KAAS="so a kaas" --macro CL_SCHMARRN="so a schmarrn"
  #
  if ($self->{cmdlinemacros}) {
    foreach (keys %{$self->{cmdlinemacros}}) {
      $DEFAULTMACROS->{$_} = $self->{cmdlinemacros}->{$_};
    }
  }
  #
  #  Escape the most commonly used special characters so they will no longer
  #  be treated like special characters in a pattern.
  #
  $self->{macros} = $DEFAULTMACROS;
  return $self;
}

sub late_init_macros {
  # these are macros filled with values that do not exist before
  # the Nagios::CheckLogfiles object has been fully initialized
  my $self = shift;
  $self->{macros}->{CL_SERVICEDESC} = $self->{cfgbase};
  $self->{macros}->{CL_NSCA_SERVICEDESC} = $self->{cfgbase};
  $self->{macros}->{CL_WARNING} = $self->{warning};
  $self->{macros}->{CL_CRITICAL} = $self->{critical};
}

sub merge_macros {
  my $self = shift;
  my $extramacros = shift;
  foreach (keys %{$extramacros}) {
    $self->{macros}->{$_} = $extramacros->{$_};
  }
}

#
#  Resolve macros in a string. 
#  If a second parameter is given, then this string is meant as a regular expression.
#  Escape special characters accordingly.
#
sub resolve_macros {
  my $self = shift;
  my $pstring = shift;
  return if ! defined $$pstring;
  while ($$pstring =~ /\$(.+?)\$/g) {
    my $maybemacro = $1;
    if (exists $self->{macros}->{$maybemacro}) {
      my $macro = $self->{macros}->{$maybemacro};
      $$pstring =~ s/\$$maybemacro\$/$macro/;
    }
  }
}


sub resolve_macros_in_pattern {
  my $self = shift;
  my $pstring = shift;
  return if ! $$pstring;
  while ($$pstring =~ /\$(.+?)\$/g) {
  # das alte bleibt hier stehen als denkmal der schande
  #while ($$pstring =~ /.*\$(\w+)\$.*/g) { 
    my $maybemacro = $1;
    if (exists $self->{macros}->{$maybemacro}) {
      my $macro = $self->{macros}->{$maybemacro};
       #
      #  Escape the most commonly used special characters so they will no longer
      #  be treated like special characters in a pattern.
      #
      $macro =~ s|/|\\/|g;
      $macro =~ s|\-|\\-|g;
      $macro =~ s|\.|\\.|g;
      $$pstring =~ s/\$$maybemacro\$/$macro/;
    }
  }
}

sub default_options {
  my $self = shift;
  my $defaults = shift;
  $self->{defaultoptions} = {};
  while (my($key, $value) = each %{$defaults}) {
    $self->{options}->{$key} = $value;
    $self->{defaultoptions}->{$key} = $value;
  }
}

sub set_options {
  my $self = shift;
  my $options = shift;
  while (my($key, $value) = each %{$options}) {
    $self->{options}->{$key} = $value if $value;
  }
}

sub set_option {
  my $self = shift;
  my $option = shift;
  my $value = shift;
  $self->{options}->{$option} = $value if defined $value;
}

sub get_option {
  my $self = shift;
  my $option = shift;
  return exists $self->{options}->{$option} ?
      $self->{options}->{$option} : undef;
}

sub get_options {
  my $self = shift;
  my $list = shift;
  if (! $list) {
    return $self->{options};
  } else {
    my %h = map {($_, $self->{options}->{$_})} split(',', $list);
    return \%h;
  }
}

sub get_non_default_options {
  my $self = shift;
  my $list = shift;
  if (! $list) {
    my %h = map {
      ($_, $self->{options}->{$_})
    } grep {
      ! exists $self->{defaultoptions}->{$_} ||
          "$self->{defaultoptions}->{$_}" ne "$self->{options}->{$_}";
    } keys %{$self->{options}};
    return \%h;
  } else {
    my %h = map {
      ($_, $self->{options}->{$_})
    } grep {
      ! exists $self->{defaultoptions}->{$_} ||
          "$self->{defaultoptions}->{$_}" ne "$self->{options}->{$_}";
    } split(',', $list);
    return \%h;
  }
}

sub refresh_default_options {
  my $self = shift;
  my $options = shift;
  if ($options) {
    if (ref($options) eq 'HASH') { # already as hash
      foreach my $option (keys %{$options}) {
        my $optarg = $options->{$option};
        if (! exists $self->{defaultoptions}->{$option} ||
            "$self->{defaultoptions}->{$option}" eq "$self->{options}->{$option}") {
          $self->{options}->{$option} = $optarg;
        }
      }
    }
  }
}

sub refresh_options {
  my $self = shift;
  my $options = shift;
  if ($options) {
    if (ref($options) eq 'HASH') { # already as hash
      foreach my $option (keys %{$options}) {
        my $optarg = $options->{$option};
        foreach my $defoption (keys %{$self->{options}}) {
          if ($option eq $defoption) {
            $self->{options}->{$defoption} = $optarg;
          }
        }
      }
    } else { # comes as string
      foreach my $option (split /,/, $options) {
        my $optarg = undef;
        $option =~ s/^\s+//;
        $option =~ s/\s+$//;
        if ($option =~ /(.*)=(.*)/) {
          $option = $1;
          $optarg = $2;
          $optarg =~ s/^"//;
          $optarg =~ s/"$//;
          $optarg =~ s/^'//;
          $optarg =~ s/'$//;
        }
        foreach my $defoption (keys %{$self->{options}}) {
          if ($option eq $defoption) {
            if (defined $optarg) {
              # example: sticky=3600,syslogclient="winhost1.dom"
              $self->{options}->{$defoption} = $optarg;
            } else {
              $self->{options}->{$defoption} = 1;
            }
          } elsif ($option eq 'no'.$defoption) {
            $self->{options}->{$defoption} = 0;
          }
        }
      } 
    }
  } 
  # reset [smart][pre|post]script options if no script should be called 
  foreach my $option (qw(script prescript postscript)) {
    if (exists $self->{options}->{'supersmart'.$option}) {
      $self->{options}->{'smart'.$option} = 1
          if $self->{options}->{'supersmart'.$option};
    }
    if (exists $self->{options}->{'smart'.$option}) {
      $self->{options}->{$option} = 1
          if $self->{options}->{'smart'.$option};
    }
    if (exists $self->{options}->{$option}) {
      if (($self->{options}->{$option}) && ! exists $self->{$option}) {
        $self->{options}->{$option} = 0;
        $self->{options}->{'smart'.$option} = 0;
        $self->{options}->{'supersmart'.$option} = 0;
      }
    }
  }
  if ($self->{options}->{sticky}) {
    if ($self->{options}->{sticky} > 1) {
      $self->{maxstickytime} = $self->{options}->{sticky};
      $self->{options}->{sticky} = 1;
    } else {
      # durch mehrmaliges refresh (seitens des CheckLogfiles-Objekts kann maxstickytime
      # zerschossen werden
      if (! exists $self->{maxstickytime} || $self->{maxstickytime} == 0) {
        $self->{maxstickytime} = 3600 * 24 * 365 * 10;
      }
    }
  }
  if ($self->{options}->{syslogclient}) {
#    $self->{prefilter} = $self->{options}->{syslogclient};
  }
}

sub trace {
  my $self = shift;
  my $format = shift;
  $self->{tracebuffer} = [] unless exists $self->{tracebuffer};
  push(@{$self->{tracebuffer}}, @_);
  if ($self->{verbose}) {
    printf("%s: ", scalar localtime);
    printf($format."\n", @_);
  }
  if ($self->{trace}) {
    my $logfh = new IO::File;
    $logfh->autoflush(1);
    if ($logfh->open($self->{tracefile}, "a")) {
      $logfh->printf("%s: ", scalar localtime);
      $logfh->printf($format, @_);
      $logfh->printf("\n");
      $logfh->close();
    }
  }
}

sub action {
  my $self = shift;
  my $script = shift;
  my $scriptparams = shift;
  my $scriptstdin = shift;
  my $scriptdelay = shift;
  my $smart = shift;
  my $privatestate = shift;
  my $success = 0;
  my $rc = 0;
  my $exitvalue;
  my $signalnum;
  my $dumpedcore;
  my $output;
  my $pid = 0;
  my $wait = 0;
  my $strerror = (qw(OK WARNING CRITICAL UNKNOWN))
      [$self->{macros}->{CL_SERVICESTATEID}];
  my $cmd;
  my @stdinformat = ();
  foreach my $macro (keys %{$self->{macros}}) {
    my $envmacro = $macro;
    if ($envmacro =~ /^CL_/) {
      $envmacro =~ s/^CL_/CHECK_LOGFILES_/;
    } else {
      $envmacro = "CHECK_LOGFILES_".$macro;
    }
    $ENV{$envmacro} = defined($self->{macros}->{$macro}) ? 
        $self->{macros}->{$macro} : "";
  }
  $ENV{CHECK_LOGFILES_SERVICESTATE} = (qw(OK WARNING CRITICAL UNKNOWN))
      [$ENV{CHECK_LOGFILES_SERVICESTATEID}];
  if (ref $script eq "CODE") {
    $self->trace("script is of type %s", ref $script);
    if (ref($scriptparams) eq "ARRAY") {
      foreach (@{$scriptparams}) {
        $self->resolve_macros(\$_) if $_;
      }
    }
    my $stdoutvar;
    *SAVEOUT = *STDOUT;
    eval {
      our $CHECK_LOGFILES_PRIVATESTATE = $privatestate;
      open OUT ,'>',\$stdoutvar;
      *STDOUT = *OUT;
      $exitvalue = &{$script}($scriptparams, $scriptstdin);
    };
    *STDOUT = *SAVEOUT;
    if ($@) {
      $output = $@;
      $success = 0;
      $rc = -1;
      $self->trace("script said: %s", $output);
    } else {
      #$output = $stdoutvar || "";
      $output = defined $stdoutvar ?  $stdoutvar :  "";
      chomp $output;
      $self->trace("script said: %s", $output);
      if ($smart) {
        if (($exitvalue =~ /^\d/) && ($exitvalue >= 0 && $exitvalue <= 3)) {
          $success = 1;
          $rc = $exitvalue;
          $self->trace("script %s exits with code %d", $script, $rc);
        } else {
          $success = 1;
          $rc = -4;
          $self->trace("script %s failed for unknown reasons", $script);
        }
      } else {
        $success = 1;
        $rc = $exitvalue;
        $output = $self->{macros}->{CL_SERVICEOUTPUT};
      }
    }
  } else {
    my $pathsep = ($^O =~ /MSWin/) ? ';' : ':';
    foreach my $dir (split(/$pathsep/, $self->{scriptpath})) {
      if ( -x $dir.'/'.$script || ( -f $dir.'/'.$script && $^O =~ /cygwin|MSWin/ && $script =~ /\.(bat|exe)$/i )) {
        $self->trace(sprintf "found script in %s/%s", $dir, $script);
        $cmd = sprintf "%s/%s", $dir, $script;
        if ($^O =~ /MSWin/) {
          $cmd =~ s/\//\\/g;
          if ($cmd =~ /\s/) {
            if (defined(&Win32::GetShortPathName)) {
              $cmd = &Win32::GetShortPathName($cmd);
            } else {
              $cmd = sprintf "\"%s\"", $cmd;
            }
          }
        } else {
          # need to escape blanks
          if ($cmd =~ /\s/) {
            $cmd =~ s/([ ])/\\$1/g;
          }
        }
        last;
      }
    }
    if ($cmd) {
      if (defined $scriptparams) {
        $self->resolve_macros(\$scriptparams);
        $cmd = sprintf "%s %s", $cmd, $scriptparams;
      }
      $self->trace(sprintf "execute %s", $cmd);
      if (defined $scriptstdin) {
        my $pid = 0;
        my $wait = 0;
        my $maxlines = 100;
        if (! ref($scriptstdin eq "ARRAY")) {
          $scriptstdin = [$scriptstdin];
        }
        foreach (@{$scriptstdin}) {
          $self->resolve_macros(\$_);

User avatar
tgriep
Madmin
Posts: 9190
Joined: Thu Oct 30, 2014 9:02 am

Re: check_logfile config logfilemissing

Post by tgriep »

Thanks for posting what you did. It is incomplete so it did not show the option.

I did some further testing and it looks like the plugin is not reading that option from the config file as it seems to be a constant, at least in the version I am testing.
What I had to do is to edit the script and change the constant from

Code: Select all

logfilemissing => 'unknown',
to

Code: Select all

logfilemissing => 'ok',
And then it would return an OK status when the file is missing.
You could create a copy of the plugin as to keep the original around for the default setting if needed.
Be sure to check out our Knowledgebase for helpful articles and solutions!
optionstechnology
Posts: 234
Joined: Thu Nov 17, 2016 11:26 am

Re: check_logfile config logfilemissing

Post by optionstechnology »

Yip this has fixed it - thanks

can be closed
Locked