444 lines
11 KiB
Perl
Executable File
444 lines
11 KiB
Perl
Executable File
#!/usr/bin/perl
|
|
###############################################################################
|
|
#
|
|
# Copyright (c) 2008 Ramon Novoa <rnovoa@artica.es>
|
|
# Copyright (c) 2008 Artica Soluciones Tecnologicas S.L.
|
|
#
|
|
# grep_log Perl script to search log files for a matching pattern. The last
|
|
# searched position is saved in an index file so that consecutive
|
|
# runs do not return the same results. The log file inode number is
|
|
# also saved to detect log rotation.
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; version 2 of the License.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
###############################################################################
|
|
use strict;
|
|
use File::Basename;
|
|
use Scalar::Util qw(looks_like_number);
|
|
|
|
# Output format (module or log_module).
|
|
my $Output = 'module';
|
|
|
|
# Be verbose
|
|
my $Verbose = 0;
|
|
|
|
# Index file storage directory, with a trailing '/'
|
|
my $Idx_dir=($^O =~ /win/i)?"$ENV{'TMP'}\\":"/tmp/";
|
|
|
|
# Log file
|
|
my $Log_file = '';
|
|
|
|
# Module name
|
|
my $Module_name = "default_log";
|
|
|
|
# Index file
|
|
my $Idx_file = '';
|
|
|
|
# Log file position index
|
|
my $Idx_pos = 0;
|
|
|
|
# Log file inode number
|
|
my $Idx_ino = '';
|
|
|
|
# Log file size
|
|
my $Idx_size = 0;
|
|
|
|
# Regular expression to be matched
|
|
my $Reg_exp = '';
|
|
|
|
# Flag to show or not summary module
|
|
my $summary_flag = 0;
|
|
|
|
my $nodatalist_flag = 0;
|
|
|
|
# Number of coincidences found
|
|
my $coincidences = 0;
|
|
|
|
if ( (defined ($ENV{GREP_LOG_TMP})) && (-d $ENV{GREP_LOG_TMP}) ) {
|
|
$Idx_dir=$ENV{GREP_LOG_TMP};
|
|
}
|
|
|
|
########################################################################################
|
|
# Erase blank spaces before and after the string
|
|
########################################################################################
|
|
sub trim($){
|
|
my $string = shift;
|
|
if (empty ($string)){
|
|
return "";
|
|
}
|
|
|
|
$string =~ s/\r//g;
|
|
|
|
chomp ($string);
|
|
$string =~ s/^\s+//g;
|
|
$string =~ s/\s+$//g;
|
|
|
|
return $string;
|
|
}
|
|
|
|
########################################################################################
|
|
# Empty
|
|
########################################################################################
|
|
sub empty($){
|
|
my $str = shift;
|
|
|
|
if (! (defined ($str)) ){
|
|
return 1;
|
|
}
|
|
|
|
if(looks_like_number($str)){
|
|
return 0;
|
|
}
|
|
|
|
if ($str =~ /^\ *[\n\r]{0,2}\ *$/) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
###############################################################################
|
|
# SUB error_msg
|
|
# Print an error message and exit.
|
|
###############################################################################
|
|
sub error_msg ($) {
|
|
my $err_msg = $_[0];
|
|
|
|
if (! -z $err_msg) {
|
|
print(stderr "[error] $err_msg.\n");
|
|
}
|
|
|
|
exit 1;
|
|
}
|
|
|
|
###############################################################################
|
|
# SUB print_help
|
|
# Print a help message.
|
|
###############################################################################
|
|
sub print_help () {
|
|
print "Usage: $0 <log_file> <module_name> <pattern> <up_lines_extra> <bot_lines_extra> [--summary] [--nodatalist]\n\n";
|
|
print "Options:\n";
|
|
print "\t<log_file>\t\tPath to the log file to be monitored\n";
|
|
print "\t<module_name>\t\tName of the module that will be created\n";
|
|
print "\t<pattern>\t\tRegex string to be matched in log file\n";
|
|
print "\t<up_lines_extra>\tShows NUM lines before matching lines to provide context\n";
|
|
print "\t<bot_lines_extra>\tShows NUM lines after matching lines to provide context\n";
|
|
print "\t--summary\t\tCreates a module with the total number of matches\n";
|
|
print "\t--nodatalist\t\tInserts all coincidences in a single data output instead of a data per line\n";
|
|
}
|
|
|
|
###############################################################################
|
|
# SUB log_msg
|
|
# Print a log message.
|
|
###############################################################################
|
|
sub log_msg ($) {
|
|
my $log_msg = $_[0];
|
|
|
|
if (! -z $log_msg && $Verbose == 1) {
|
|
print(stdout "[log] $log_msg.\n");
|
|
}
|
|
}
|
|
|
|
###############################################################################
|
|
# SUB load_idx
|
|
# Load index file.
|
|
###############################################################################
|
|
sub load_idx () {
|
|
my $line;
|
|
my $current_ino;
|
|
my $current_size;
|
|
|
|
log_msg("Loading index file $Idx_file");
|
|
|
|
open(IDXFILE, $Idx_file) || error_msg("Error opening file $Idx_file: " .
|
|
$!);
|
|
|
|
# Read position and date
|
|
$line = <IDXFILE>;
|
|
($Idx_pos, $Idx_ino, $Idx_size) = split(' ', $line);
|
|
|
|
close(IDXFILE);
|
|
|
|
# Reset the file index if the file has changed
|
|
$current_ino = (stat($Log_file))[1];
|
|
$current_size = -s "$Log_file";
|
|
if ($current_ino != $Idx_ino || $current_size < $Idx_size) {
|
|
log_msg("File changed, resetting index");
|
|
|
|
$Idx_pos = 0;
|
|
$Idx_ino = $current_ino;
|
|
}
|
|
$Idx_size = $current_size;
|
|
|
|
return;
|
|
}
|
|
|
|
###############################################################################
|
|
# SUB save_idx
|
|
# Save index file.
|
|
###############################################################################
|
|
sub save_idx () {
|
|
|
|
log_msg("Saving index file $Idx_file");
|
|
|
|
open(IDXFILE, "> $Idx_file") || error_msg("Error opening file $Idx_file: "
|
|
. $!);
|
|
print (IDXFILE $Idx_pos . " " . $Idx_ino . " " . $Idx_size);
|
|
close(IDXFILE);
|
|
|
|
return;
|
|
}
|
|
|
|
###############################################################################
|
|
# SUB create_idx
|
|
# Create index file.
|
|
###############################################################################
|
|
sub create_idx () {
|
|
my $first_line;
|
|
|
|
log_msg("Creating index file $Idx_file");
|
|
|
|
open(LOGFILE, $Log_file) || error_msg("Error opening file $Log_file: " .
|
|
$!);
|
|
|
|
# Go to EOF and save the position
|
|
seek(LOGFILE, 0, 2);
|
|
$Idx_pos = tell(LOGFILE);
|
|
|
|
close(LOGFILE);
|
|
|
|
# Save the file inode number
|
|
$Idx_ino = (stat($Log_file))[1];
|
|
|
|
# Save the index file
|
|
save_idx();
|
|
|
|
return;
|
|
}
|
|
|
|
###############################################################################
|
|
# SUB parse_log
|
|
# Parse log file starting from position $Idx_pos.
|
|
###############################################################################
|
|
sub parse_log (;$$) {
|
|
my ($up_lines,$bot_lines) = @_;
|
|
my $line;
|
|
|
|
log_msg("Parsing log file $Log_file");
|
|
|
|
# Open log file for reading
|
|
open(LOGFILE, $Log_file) || error_msg("Error opening file $Log_file: " .
|
|
$!);
|
|
|
|
# Go to starting position.
|
|
seek(LOGFILE, $Idx_pos, 0);
|
|
|
|
# Parse log file
|
|
my %data;
|
|
|
|
# Matched line id
|
|
my $matched_line = 0;
|
|
|
|
if ( (defined($up_lines)) || (defined($bot_lines)) ){
|
|
# Detailed workmode
|
|
my @lines;
|
|
my $nl = 0;
|
|
my @nl_found;
|
|
while ($line = <LOGFILE>) {
|
|
push @lines, $line;
|
|
if ($line =~ m/$Reg_exp/i) {
|
|
push @nl_found, $nl;
|
|
$coincidences++;
|
|
}
|
|
$nl++;
|
|
}
|
|
# Return all coincidences with the desired margin
|
|
foreach my $curr_line (@nl_found){
|
|
my $flag = 0; # avoid repetition of current line
|
|
if (defined($up_lines)){
|
|
$flag = 1;
|
|
# Push upper lines
|
|
for (my $i = ($curr_line-$up_lines); $i<=$curr_line; $i++){
|
|
if ($i < 0) {next;}
|
|
if (defined ($lines[$i])) {
|
|
push (@{$data{$matched_line}}, $lines[$i]);
|
|
}
|
|
|
|
}
|
|
}
|
|
if (defined($bot_lines)){
|
|
# Push bottom lines
|
|
for (my $i = ($curr_line+$flag); $i<=($curr_line+$bot_lines); $i++){
|
|
if (defined ($lines[$i])) {
|
|
push (@{$data{$matched_line}}, $lines[$i]);
|
|
}
|
|
}
|
|
}
|
|
$matched_line++;
|
|
}
|
|
}
|
|
else { # Standar workmode
|
|
while ($line = <LOGFILE>) {
|
|
if ($line =~ m/$Reg_exp/i) {
|
|
push (@{$data{$matched_line++}}, $line);
|
|
}
|
|
}
|
|
}
|
|
|
|
$Idx_pos = tell(LOGFILE);
|
|
close(LOGFILE);
|
|
|
|
# Save the index file
|
|
save_idx();
|
|
|
|
return \%data;
|
|
}
|
|
|
|
###############################################################################
|
|
# SUB print_summary
|
|
# Print module summary to stdout.
|
|
###############################################################################
|
|
sub print_summary() {
|
|
my $output = "<module>\n";
|
|
$output .= "<name><![CDATA[" . $Module_name . " coincidences]]></name>\n";
|
|
$output .= "<type><![CDATA[async_data]]></type>\n";
|
|
$output .= "<datalist>\n";
|
|
$output .= "<data><value><![CDATA[$coincidences]]></value></data>\n";
|
|
$output .= "</datalist>\n";
|
|
$output .= "</module>\n";
|
|
print stdout $output;
|
|
}
|
|
|
|
###############################################################################
|
|
# SUB parse_log
|
|
# Print log data to stdout.
|
|
###############################################################################
|
|
sub print_log ($) {
|
|
my $data = shift;
|
|
|
|
# No data
|
|
my @kdata = keys (%{$data});
|
|
if ($#kdata < 0) {
|
|
print_summary() if ($summary_flag == 1);
|
|
return;
|
|
|
|
}
|
|
|
|
# Log module
|
|
if ($Output eq 'log_module') {
|
|
my $output = "<log_module>\n";
|
|
$output .= "<source><![CDATA[" . $Module_name . "]]></source>\n";
|
|
$output .= "<data><![CDATA[";
|
|
foreach my $line (@kdata) {
|
|
my $processed_line = $line;
|
|
$processed_line =~ s/\]\]/]]]]><![CDATA[/g;
|
|
$output .= $processed_line;
|
|
}
|
|
$output .= "]]></data>";
|
|
$output .= "</log_module>\n";
|
|
|
|
print stdout $output;
|
|
}
|
|
# Regular module
|
|
else {
|
|
my $output;
|
|
print_summary() if ($summary_flag == 1);
|
|
$output = "<module>\n";
|
|
$output .= "<name><![CDATA[" . $Module_name . "]]></name>\n";
|
|
$output .= "<type><![CDATA[async_string]]></type>\n";
|
|
if ($nodatalist_flag == 1){
|
|
$output .= "<data><![CDATA[";
|
|
foreach my $line (@kdata) {
|
|
foreach my $content (@{$data->{$line}}) {
|
|
my $processed_line = $content;
|
|
$processed_line =~ "\n";
|
|
$output .= $processed_line;
|
|
}
|
|
}
|
|
$output .= "]]></data>\n";
|
|
}
|
|
else {
|
|
$output .= "<datalist>\n";
|
|
foreach my $line (@kdata) {
|
|
$output .= "<data><value><![CDATA[";
|
|
foreach my $content (@{$data->{$line}}) {
|
|
my $processed_line = $content;
|
|
$processed_line =~ s/\]\]/]]]]><![CDATA[/g;
|
|
$output .= $processed_line;
|
|
}
|
|
$output .= "]]></value></data>\n";
|
|
}
|
|
$output .= "</datalist>\n";
|
|
}
|
|
$output .= "</module>\n";
|
|
|
|
print stdout $output;
|
|
}
|
|
|
|
}
|
|
|
|
###############################################################################
|
|
###############################################################################
|
|
## Main
|
|
###############################################################################
|
|
###############################################################################
|
|
|
|
# Check command line parameters
|
|
if ($#ARGV < 2) {
|
|
print_help();
|
|
exit 1;
|
|
}
|
|
|
|
$Log_file = trim($ARGV[0]);
|
|
$Module_name = trim($ARGV[1]);
|
|
$Reg_exp = trim($ARGV[2]);
|
|
my $up_lines = trim($ARGV[3]);
|
|
my $bot_lines = trim($ARGV[4]);
|
|
my $sum_flag = trim($ARGV[5]);
|
|
my $nodatalist = trim($ARGV[6]);
|
|
|
|
if ( grep { /--summary/ } @ARGV )
|
|
{
|
|
$summary_flag = 1;
|
|
}
|
|
|
|
if ( grep { /--nodatalist/ } @ARGV )
|
|
{
|
|
$nodatalist_flag = 1;
|
|
}
|
|
|
|
# Create index file storage directory
|
|
if ( ! -d $Idx_dir) {
|
|
mkdir($Idx_dir) || error_msg("Error creating directory $Idx_dir: "
|
|
. $!);
|
|
}
|
|
|
|
# Check that log file exists
|
|
if (! -e $Log_file) {
|
|
error_msg("File $Log_file does not exist");
|
|
}
|
|
|
|
# Create index file if it does not exist
|
|
$Idx_file=$Idx_dir . $Module_name . "_" . basename($Log_file) . ".idx";
|
|
if (! -e $Idx_file) {
|
|
create_idx();
|
|
exit 0;
|
|
}
|
|
|
|
# Load index file
|
|
load_idx();
|
|
|
|
# Parse log file
|
|
my $data = parse_log($up_lines,$bot_lines);
|
|
|
|
# Print output to stdout
|
|
print_log ($data);
|
|
|
|
exit 0;
|