#!/usr/bin/perl ############################################################################### # # Copyright (c) 2008 Ramon Novoa # 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 [--summary] [--nodatalist]\n\n"; print "Options:\n"; print "\t\t\tPath to the log file to be monitored\n"; print "\t\t\tName of the module that will be created\n"; print "\t\t\tRegex string to be matched in log file\n"; print "\t\tShows NUM lines before matching lines to provide context\n"; print "\t\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 = ; ($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 = ) { 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 = ) { 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 = "\n"; $output .= "\n"; $output .= "\n"; $output .= "\n"; $output .= "\n"; $output .= "\n"; $output .= "\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 = "\n"; $output .= "\n"; $output .= ""; $output .= "\n"; print stdout $output; } # Regular module else { my $output; print_summary() if ($summary_flag == 1); $output = "\n"; $output .= "\n"; $output .= "\n"; if ($nodatalist_flag == 1){ $output .= "{$line}}) { my $processed_line = $content; $processed_line =~ "\n"; $output .= $processed_line; } } $output .= "]]>\n"; } else { $output .= "\n"; foreach my $line (@kdata) { $output .= "{$line}}) { my $processed_line = $content; $processed_line =~ s/\]\]/]]]]>\n"; } $output .= "\n"; } $output .= "\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;