#!/usr/bin/perl -w
#
# Web tool to search inside syslog's files
#
# 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; either version 2 of the License, or
# (at your option) any later version.
#
# 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.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# author     Jean-Louis Bergamo <jlb@googlog.org>
# copyright  2006-2007
#
## $Id: GoogLog_cgi.pl,v 1.18 2006/12/21 13:42:14 jlb Exp $
## $Source: /cvsroot/dev/Linux/GoogLog/GoogLog_cgi.pl,v $
#

# standard module
use strict;
use Time::HiRes qw( usleep ualarm gettimeofday tv_interval);
use Carp;

# CPAN module
use Config::Tiny;
use Compress::Zlib ;
use CGI;
use HTML::Template;

=head1 NAME

GoogLog - CGI part (the research part) of GoogLog

=head1 VERSION

$Revision: 1.18 $

=cut

# versionning
'$Revision: 1.18 $ ' =~ /\s([\d.]+)/;
my $VERSION = $1;

=head1 SYNOPSIS

GoogLog is a easy and simple web tool to search inside your syslog files. If you want to search inside your syslog files from a web interface, this tool is made for you.

Install it in the cgi-bin directory of your web server, and configure some variable below.

The goal of Googlog is to be simple to install and use. so there's not a lot of functionnality, but, from my point of vue, it is simple to install and use and fast to search into syslog's files.

=head1 DOWNLOAD

Go here L<http://www.googlog.org/download/> to download the latest version.

=head1 DEMO

Follow this link to see an online demo of GoogLog (on very few restrictedt syslog files): L<http://www.googlog.org/demo/GoogLog_cgi.pl?pattern=spamd&date=2006-12-07&hours=17&cat=mail&B1=Search%21&action=search>

=head1 INSTALL

=head2 PERL Modules

Required PERL modules

=over

=item Config::Tiny

=item Compress::Zlib

=item HTML::Template

=back

Under debian and debian based distrib : apt-get install libhtml-template-perl libcompress-zlib-perl libconfig-tiny-perl

=head2 SYSLOG server

I recommend to use syslog-ng (actually google works only with syslog-ng) and to add this sort of generic config in the syslog-ng config file (on your syslog server of course). Modify to fit your needs but, keep in mind that the structure of the directory must always be like this : /LOGDIR/Category/YYYY-MM-DD/HH-name.log


 # log generique
 source net { udp(); };
 destination d_generique { file("/var/log/net/$FACILITY/$YEAR-$MONTH-$DAY/$HOUR-$FACILITY.log" owner(root) group(adm) perm(0644) dir_perm(0755) create_dirs(yes) dir_group(adm)); };
 log { source(net) ; destination(d_generique); };


=head2 GoogLog

GoogLog consist of 3 files that need to be installed into the same directory into your cgi-bin directory (/usr/lib/cgi-bin/ on GNU/Debian):

=over

=item GoogLog_cgi.pl

This file. this is the main program. It has to have the execution bits enabled to the user that run you web server (or the user you configured into the virtualhost if you use suexec)

=item index.html

The template file used to render the result. put it with the B<GoogLog_cgi.pl>.

=item googlog.ini

Configuration file for GoogLog. you will put here where to find logfile to search in. see description below to know what to put in it.

=back

=head1 PARAMETERS

=head2 CONFIG FILE

Googlog use a config file (placed in the same directory of the Googlog CGI) that contains these attributes :

=head3 general directives

=over

=item debug

Active debug or not

=item logdir

Where are the logs

=back

=head3 Directive for categories

Each specific categories start with [nameofcategories] and understand this specific attributes :

=over

=item subdir

For wich subdir this category is for 

=item line_around

How many lines do we search around the previous found pattern for related search.

=back

=head2 CGI

This is what GoogLog_cgi.pl understand in a GET request.

=over

=item action

action can be :
 list : list available categories to search (not used now)
 search : do the real search
 related : do the related search (it means try to use some related grep to find related line)

=item date

Format of date : YYYY-MM-DD

=item hours

Which hours

=item pattern

What we're looking for in log

=item cat

Which category

=item host

On which host do we need to do related search

=item process

On which process do we need to do related search (if not more pertinent)

=item file

Which file. used by related search

=back


=cut


# global var
# where is the config file
my $CONFFILE='googlog.ini';
my $Config;
if (-r $CONFFILE){
  # Create a config
  $Config = Config::Tiny->new();
  # Open the config
  $Config = Config::Tiny->read($CONFFILE);
}else{
  warn "Can't read $CONFFILE\n";
}
# Configuration. you have to edit these (at least the CONFFILE).
my $LOGDIR=$Config->{_}->{'logdir'}||'/var/log/net';
# END Configuration


# useful var
my $DEBUG=$Config->{_}->{'debug'}||0;
my $cgi=new CGI;
my $t0 = [gettimeofday];
my @HOURS=(
	   {HNAME=>'00',HSEL=>'0'},
	   {HNAME=>'01',HSEL=>'0'},
	   {HNAME=>'02',HSEL=>'0'},
	   {HNAME=>'03',HSEL=>'0'},
	   {HNAME=>'04',HSEL=>'0'},
	   {HNAME=>'05',HSEL=>'0'},
	   {HNAME=>'06',HSEL=>'0'},
	   {HNAME=>'07',HSEL=>'0'},
	   {HNAME=>'08',HSEL=>'0'},
	   {HNAME=>'09',HSEL=>'0'},
	   {HNAME=>'10',HSEL=>'0'},
	   {HNAME=>'11',HSEL=>'0'},
	   {HNAME=>'12',HSEL=>'0'},
	   {HNAME=>'13',HSEL=>'0'},
	   {HNAME=>'14',HSEL=>'0'},
	   {HNAME=>'15',HSEL=>'0'},
	   {HNAME=>'16',HSEL=>'0'},
	   {HNAME=>'17',HSEL=>'0'},
	   {HNAME=>'18',HSEL=>'0'},
	   {HNAME=>'19',HSEL=>'0'},
	   {HNAME=>'20',HSEL=>'0'},
	   {HNAME=>'21',HSEL=>'0'},
	   {HNAME=>'22',HSEL=>'0'},
	   {HNAME=>'23',HSEL=>'0'}
	  );

my $action;
unless ($action = $cgi->param('action')){
  #default page
  my ($hour,$mday,$mon,$year)=(localtime(time))[2..5];
  my $today=(1900+$year)."-".(($mon+1)<10?"0".($mon+1):($mon+1))."-".($mday<10?"0".$mday:$mday);

  # open the html template
  my $template = HTML::Template->new(filename => 'index.html');
  # fill in some parameters
  $template->param(FORM => $cgi->url(-full));
  $template->param(ACTION => 'search');
  #$template->param(DATE => $today);
  $template->param(DATE => &list_date($today));
  $template->param(CATEGORY => &list_category());
  $HOURS[$hour]->{HSEL}=1;
  $template->param(HOURS => \@HOURS);
  my $elapsed=tv_interval ( $t0, [gettimeofday]);
  $template->param(ELAPSED => $elapsed);
  $template->param(VERSION => $VERSION);
  # send the obligatory Content-Type and print the template output
  print "Content-Type: text/html\n\n", $template->output;
  exit(0);
}

# what do we have to do
if ($action eq 'list'){
  # only send available categories
  print "Content-Type: text/plain\n\n";
  my $cat=&list_category();
  foreach (0 .. scalar (@$cat)){
    print $cat->[$_]->{name},"\n";
  }
  exit(0);
}elsif($action eq 'search'){
  # do the real search
  my @res=();
  my $file;
  my $lastline;
  my $line;
  my $pattern;
  my $cat;
  my $line_number=0;
  my @hours;
  my $hours_cgi;
  my @files;

  unless ($pattern = $cgi->param('pattern')){
    print "Content-Type: text/plain\n\n";
    print  "pattern missing\n"; 
    exit(0);
  }

  unless ($cat = $cgi->param('cat')){
    print "Content-Type: text/plain\n\n";
    print  "category missing\n"; 
    exit(0);
  }

  my $date = $cgi->param('date');
  unless ($date =~ /\d{4}\-\d{2}-\d{2}/i){
    print "Content-Type: text/plain\n\n";
    print "format invalide pour la date : $date\n";
    exit(0);
  }

  if (@hours= $cgi->param('hours')){
    $hours_cgi=join("&hours=",@hours);
    @files=&list_files($Config->{$cat}->{'subdir'},$date,@hours);
  }elsif ($file=$cgi->param('file')){
    if (&check_filename($file,$cat,$date)) {
      warn "Good filename : $file\n" if $DEBUG;
      @files=($file);
    }else {
      print "Content-Type: text/plain\n\n";
      print "Bad filename : $file\n";
      exit(0);
    }
  }else{
    print "Content-Type: text/plain\n\n";
    print  "file AND hours missing\n";
    exit(0);
  }

  warn "Files : ".join(',',@files) if $DEBUG;

  foreach $file (@files) {
    warn "=>Fichier : $file\n" if $DEBUG;
    if ($file =~/gz$/i){
      # compressed file
      my $gz;
      unless ($gz = gzopen($file, "rb")){
	warn "Cannot open $file: $gzerrno\n" ; 
	next;
      }
      while ($gz->gzreadline($_) > 0) {
	my ($host,$processus)=(split(/\s+/,$_,6))[3,4];
	if (/$pattern/o){
	  my @res_line=split(/\s+/,$_);
	  my $res_line;
	  foreach (@res_line){
	    my $pat=$_;
	    $pat=~s/(\[|\]|\\|\|)/\\$1/g;
	    my $escapedstr=$cgi->escapeHTML($_);
	    $escapedstr=~s/\^I/<br>/g;
	    $escapedstr=~s/($pattern)/<b>$1<\/b>/g;
	    $res_line.="<a class=\"searchlink\" href=\"".$cgi->url(-full)."?action=search&cat=$cat&date=$date&pattern=$pat&hours=$hours_cgi\">$escapedstr</a> ";
	  }
	  push(@res,{
		     res_nb_line=>$line_number,
		     res_line=>$res_line,
		     related_url=>$cgi->url(-full)."?action=related&file=$file&cat=$cat&date=$date&pattern=$pattern&line_number=$line_number&hours=$hours_cgi&host=$host&processus=$processus"
		    }
	      );
	}
	$line_number++;
      }
      if ($gzerrno != Z_STREAM_END){
	warn "Error reading from $file: $gzerrno\n";
	#exit(0);
      }
      $gz->gzclose() ;
    }else{
      # uncompressed file
      unless (open(FILE ,$file)){
	warn "Cannot open $file: $!\n" ;
	next;
      }
      while (<FILE>) {
	my ($host,$processus)=(split(/\s+/,$_,6))[3,4];
	if (/$pattern/o){
	  my @res_line=split(/\s+/,$_);
	  my $res_line;
	  foreach (@res_line){
	    my $pat=$_;
	    $pat=~s/(\[|\]|\\|\|)/\\$1/g;
	    my $escapedstr=$cgi->escapeHTML($_);
	    $escapedstr=~s/\^I/<br>/g;
	    $escapedstr=~s/($pattern)/<b>$1<\/b>/g;
	    $res_line.="<a class=\"searchlink\" href=\"".$cgi->url(-full)."?action=search&cat=$cat&date=$date&pattern=$pat&hours=$hours_cgi\">$escapedstr</a> ";
	  }
	  push(@res,{
		     res_nb_line=>$line_number,
		     res_line=>$res_line,
		     related_url=>$cgi->url(-full)."?action=related&file=$file&cat=$cat&date=$date&pattern=$pattern&line_number=$line_number&hours=$hours_cgi&host=$host&processus=$processus"
		    }
	      );
	}
	$line_number++;
      }
      close(FILE) ;
    }
  }

  # open the html template
  my $template = HTML::Template->new(filename => 'index.html');
  # fill in some parameters
  $template->param(FORM => $cgi->url(-full));
  $template->param(ACTION => 'search');
  $template->param(PATTERN => $pattern);
  #$template->param(DATE => $date);
  $template->param(DATE => &list_date($date));
  $template->param(CATEGORY => &list_category($cat));
  foreach my $hour (@hours){
    $HOURS[$hour]->{HSEL}=1;
  }
  $template->param(HOURS => \@HOURS);
  $template->param(RESULTS => scalar @res);
  $template->param(RES_LOOP => \@res);
  my $elapsed=tv_interval ( $t0, [gettimeofday]);
  $template->param(ELAPSED => $elapsed);
  $template->param(VERSION => $VERSION);
  $template->param(LINENB => $line_number);
  # send the obligatory Content-Type and print the template output
  print "Content-Type: text/html\n\n", $template->output;
  exit(0);
}elsif($action eq 'related'){
  # RELATED search
  #print "Content-Type: text/plain\n\n";
  #print  "related\n";
  my $pattern;
  my $cat;
  my $file;
  # we will put file content in this array
  my @file;
  my $host;
  my $processus;
  my @res;
  my $line_number;
  my @hours;
  my $hours_cgi;

  unless ($pattern = $cgi->param('pattern')){
    print "Content-Type: text/plain\n\n";
    print  "pattern missing\n"; 
    exit(0);
  }

  unless (defined($line_number = $cgi->param('line_number'))){
    print "Content-Type: text/plain\n\n";
    print  "line_number missing\n";
    exit(0);
  }

  unless ($host = $cgi->param('host')){
    print "Content-Type: text/plain\n\n";
    print  "host missing\n";
    exit(0);
  }

  unless ($processus = $cgi->param('processus')){
    print "Content-Type: text/plain\n\n";
    print  "processus missing\n";
    exit(0);
  }

  unless ($cat = $cgi->param('cat')){
    print "Content-Type: text/plain\n\n";
    print  "category missing\n"; 
    exit(0);
  }

  unless ($file=$cgi->param('file')){
    print "Content-Type: text/plain\n\n";
    print  "file missing\n";
    exit(0);
  }

  my $date = $cgi->param('date');
  unless ($date =~ /\d{4}\-\d{2}-\d{2}/i){
    print "Content-Type: text/plain\n\n";
    print "format invalide pour la date : $date\n";
    exit(0);
  }

  if (@hours= $cgi->param('hours')){
    $hours_cgi=join("&hours=",@hours);
  }

  foreach ($cgi->param()){
    warn "$_ => ",$cgi->param($_),"\n" if $DEBUG;
  }

  my $line_around=$Config->{$cat}->{'line_around'}||10;

  if (&check_filename($file,$cat,$date)) {
    warn "Good filename : $file\n" if $DEBUG;
  }else {
    print "Content-Type: text/plain\n\n";
    print "Bad filename : $file\n";
    exit(0);
  }

  warn "=>Fichier : $file\n" if $DEBUG;
  my $line=0;
  my $tmp;
  my $end = $line_number + $line_around;
  my $start = $line_number - $line_around;
  my $found_line;
  warn "END: $line_number + $line_around = $end\n" if $DEBUG;

  if ($file =~/gz$/i){
    # compressed file
    my $gz;
    unless ($gz = gzopen($file, "rb")){
      warn "Cannot open $file: $gzerrno\n" ; 
      next;
    }
    while ($line <= $end && $gz->gzreadline($tmp) > 0){
      push(@file,"$line:$tmp") if ($line >= $start);
      $found_line = $tmp if $line==$line_number;
      warn"LINE: $line\n" if $DEBUG;
      $line++;
    }
    #if ($gzerrno != Z_STREAM_END){
    #  warn "Error reading from $file: $gzerrno\n";
    #  exit(0);
    #}
    $gz->gzclose() ;
  }else{
    # uncompressed file
    unless (open(FILE ,$file)){
      warn "Cannot open $file: $!\n" ;
      next;
    }

    while ($line <= $end && ($tmp=<FILE>)) {
      push(@file,"$line:$tmp") if ($line >= $start);
      $found_line = $tmp if $line==$line_number;
      warn"LINE: $line\n" if $DEBUG;
      $line++;
    }
    close(FILE) ;
  }

  # do a global grep
  warn "HOST : $host\n" if $DEBUG;
  foreach (@file){
    warn "$_";
  }
#  my @tab=grep(/\s$host\s/o,@file);
#  foreach (@tab){
#    warn "$_";
#  }
#  foreach (grep(/\s$host\s/o,@file)){
#    my ($line,$msg)=split(/:/,$_,2);
#    push (@res, {
#		 rel_nb_line=>$line,
#		 rel_line=>$msg
#		 }
#	 );
#  }

  # search
  @res=&googlog_grep({ host=>$host,
		       processus=>$processus,
		       found_line=>$found_line,
		       pattern=>$pattern,
		       file=>$file,
		       cat=>$cat,
		       date=>$date,
		       hours=>$hours_cgi,
		     },\@file);

  # open the html template
  my $template = HTML::Template->new(filename => 'index.html');
  # fill in some parameters
  $template->param(FORM => $cgi->url(-full));
  $template->param(ACTION => 'search');
  $template->param(PATTERN => $pattern);
  #$template->param(DATE => $date);
  $template->param(DATE => &list_date($date));
  $template->param(CATEGORY => &list_category($cat));
  if ($file =~ /\/([\d]+)-[^\/]*$/){
    $HOURS[$1]->{HSEL}=1;
  }
  $template->param(HOURS => \@HOURS);
  $template->param(RELATED => scalar @res);
  $template->param(REL_LOOP => \@res);
  my $elapsed=tv_interval ( $t0, [gettimeofday]);
  $template->param(ELAPSED => $elapsed);
  $template->param(VERSION => $VERSION);
  # send the obligatory Content-Type and print the template output
  print "Content-Type: text/html\n\n", $template->output;
  exit(0);
}


#
#=head1 FUNCTIONS
#
#=head2 Googlog_grep
#
#Do the grep inside lines extracted from log file
#
#=cut
#
sub googlog_grep{
  my($args,$tab)=@_;
  my @res;
  my $packagebase='GoogLog';
  my $procname='Default';
  my $pattern;
  my $host=$args->{'host'};
  my $processus=$args->{'processus'};
  my $mod;
  my %modinside=(
		 'exim'=>1,
		);
  if ($processus =~/^(.*)\[/){
    $procname=$1;
  }

  $mod=$packagebase."::".$procname;
  unless(defined $modinside{$procname}){
    unless (eval "use $mod") {
      warn "couldn't load $mod: $@";
      $mod=$packagebase."::Default";
    }
  }

  warn "use ".$mod."::GrepRelated function\n" if $DEBUG;
  no strict 'refs';
  @res = &{ $mod . "::GrepRelated"}($args,$tab);

  return @res;
}
#
#=head2 check_filename
#
#Check if the filename contains LOGDIR, category and not something like ..
#
#return false or true
#
#=cut
#
sub check_filename{
  my ($filename,$cat,$date)=@_;
  unless ( $filename =~ /^$LOGDIR[\/]+$cat[\/]+$date[\/]+.*$/) {
    # bad filename
    return undef;
  }
  if ($filename =~ /\.\./g) {
    # try to go to parent dir
    return undef;
  }
  # good filename
  return 1;

}
#
#=head2 list_category
#
#Not Yet Used
#
#=cut
#
sub list_category{
  my ($cat_sel)=@_;

  my @res;
  # only send available categories
  foreach (sort keys %$Config){
    if (defined $Config->{$_}->{subdir} && -d $LOGDIR.'/'.$Config->{$_}->{subdir}){
      if (defined $cat_sel && $_ eq $cat_sel){
	push(@res,{name=>$_,desc=>$_,sel=>1});
      }else{
	push(@res,{name=>$_,desc=>$_});
      }
    }
  }
  return(\@res);
}

#
#=head2 list_date
#
#Used to have all the date available from syslog files
#
#=cut
#
sub list_date{
  my ($date_sel)=@_;

  my @res;
  my %dates;
  # only send available categories
  foreach (sort keys %$Config){
    if (defined $Config->{$_}->{subdir} && -d $LOGDIR.'/'.$Config->{$_}->{subdir}){
      foreach my $dir (glob($LOGDIR.'/'.$Config->{$_}->{subdir}.'/*')){
	if ($dir =~ /\/(\d{4}-\d{2}-\d{2})[\/]*$/){
	  $dates{$1}=1;
	}
      }
    }
  }

  foreach (sort keys %dates){
    if (defined $date_sel && $_ eq $date_sel){
      push(@res,{dname=>$_,dsel=>1});
    }else{
      push(@res,{dname=>$_,dsel=>0});
    }
  }
  return(\@res);
}

#
#=head2 list_files
#
#return a list of files in where we need to search
#
#=cut
#
sub list_files{
  my ($cat,$date,@hours)=@_;

  my @files=();
  my $hour;

  foreach $hour (@hours){
    warn "Glob : $LOGDIR/$cat/$date/$hour*" if $DEBUG;
    push(@files,glob($LOGDIR.'/'.$cat.'/'.$date.'/'.$hour.'*'));
  }
  return(@files);
}

1;

package GoogLog::Default;
#
# Default search function for related search
#
use strict;
use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);

use Exporter;
$VERSION = 1.00;              # Or higher
@ISA = qw(Exporter);

@EXPORT      = qw(GrepRelated);       # Symbols to autoexport (:DEFAULT tag)
@EXPORT_OK   = qw(GrepRelated);       # Symbols to export on request
%EXPORT_TAGS = (                      # Define names for sets of symbols
		Functions => [qw(GrepRelated)],
	       );

########################
# your code goes here
########################
sub GrepRelated{
  my($args,$tab)=@_;
  my @res;
  my $pattern;
  my $this_pack = __PACKAGE__;
  my $patternorig=$args->{'pattern'};
  my $host=$args->{'host'};
  my $processus=$args->{'processus'};
  $processus =~ s/\[/\\\[/g;
  $processus =~ s/\]/\\\]/g;

  $pattern="\\s+$host\\s+$processus\\s+";
  warn "$this_pack PATTERN = $pattern\n" if $DEBUG;

  foreach (grep(/$pattern/o,@$tab)){
    my ($line,$msg)=split(/:/,$_,2);

    # split msg line into small links
    my @res_line=split(/\s+/,$msg);
    my $res_line;
    foreach (@res_line){
      my $pat=$_;
      $pat=~s/(\[|\]|\\|\|)/\\$1/g;
      my $escapedstr=$cgi->escapeHTML($_);
      $escapedstr=~s/\^I/<br>/g;
      $escapedstr=~s/($patternorig)/<b>$1<\/b>/g;
      $res_line.="<a class=\"searchlink\" href=\"".$cgi->url(-full)."?action=search&cat=$args->{cat}&date=$args->{date}&pattern=$pat&file=$args->{file}&hours=$args->{hours}\">$escapedstr</a> ";
    }

    push (@res, {
		 rel_nb_line=>$line,
		 rel_line=>$res_line
		}
	 );
  }
  return @res;

}

1;                            # this should be your last line

# EXIM "plugins"
package GoogLog::exim;
#
# exim search function for related search
#
use strict;
use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);

use Exporter;
$VERSION = 1.00;              # Or higher
@ISA = qw(Exporter);

@EXPORT      = qw(GrepRelated);       # Symbols to autoexport (:DEFAULT tag)
@EXPORT_OK   = qw(GrepRelated);       # Symbols to export on request
%EXPORT_TAGS = (              # Define names for sets of symbols
		Functions => [qw(GrepRelated)],
	       );

########################
# your code goes here
########################
sub GrepRelated{
  my($args,$tab)=@_;
  my $pattern;
  my @res;
  my $this_pack = __PACKAGE__;
  my $patternorig=$args->{'pattern'};
  my $host=$args->{'host'};
  my $processus=$args->{'processus'};
  $processus =~ s/\[/\\\[/g;
  $processus =~ s/\]/\\\]/g;

  if ($args->{'found_line'} =~ /\s+([^\s]+)\s+(<=|=>|->)\s+/oi) {
    # ID found so grep on it
    $pattern=$1;
  }else {
    # no ID found so only grep on host and processus
    $pattern="\\s+$host\\s+$processus\\s+";
  }
  warn "$this_pack PATTERN = $pattern\n" if $DEBUG;

  foreach (grep(/$pattern/o,@$tab)){
    my ($line,$msg)=split(/:/,$_,2);

    # split msg line into small links
    my @res_line=split(/\s+/,$msg);
    my $res_line;
    foreach (@res_line){
      my $pat=$_;
      $pat=~s/(\[|\]|\\|\|)/\\$1/g;
      my $escapedstr=$cgi->escapeHTML($_);
      $escapedstr=~s/\^I/<br>/g;
      $escapedstr=~s/($patternorig)/<b>$1<\/b>/g;
      $res_line.="<a class=\"searchlink\" href=\"".$cgi->url(-full)."?action=search&cat=$args->{cat}&date=$args->{date}&pattern=$pat&file=$args->{file}&hours=$args->{hours}\">$escapedstr</a> ";
    }

    push (@res, {
		 rel_nb_line=>$line,
		 rel_line=>$res_line
		}
	 );
  }
  return @res;

}

1;                            # this should be your last line

=head1 HISTORY

Extract from CVS commit :

 $Log: GoogLog_cgi.pl,v $
 Revision 1.18  2006/12/21 13:42:14  jlb
 add some documentation

 Revision 1.17  2006/12/21 13:22:50  jlb
 replace date text field with a select field

 Revision 1.16  2006/12/08 16:26:58  jlb
 substitute ^I by <br> in syslog line, because some program syslog mutltiline with ^I instead of \n

 Revision 1.15  2006/12/08 15:30:09  jlb
 cosmetic update :
 - result are put in a table (to be well align)
 - nbsp replaced by space (to be able to have broken line)

 Revision 1.14  2006/12/07 16:20:47  jlb
 Do some documentation work

 Revision 1.13  2006/12/04 10:57:37  jlb
 use CVS version as the main versionning

 Revision 1.12  2006/12/01 16:01:22  jlb
 add some cosmetic features

 Revision 1.11  2006/12/01 14:38:50  jlb
 add escapeHTML function for some string that contains bad caracters

 Revision 1.10  2006/12/01 14:12:25  jlb
 Little bug fix on the default date presentation

 Revision 1.9  2006/11/30 17:08:38  jlb
 bug fix in the generation of file list

 Revision 1.8  2006/11/30 16:43:51  jlb
 - start using CSS
 - simplify template (only used index.html
 - add URL-click-search feature (can make search on every part of log found)

 Revision 1.7  2006/10/19 16:39:21  jlb
 start of cosmetic design

 Revision 1.6  2006/10/18 21:29:09  jlb
 bug fix when file is compressed and we want to see related lines

 Revision 1.5  2006/10/18 21:25:56  jlb
 starting the plugin support

 Revision 1.4  2006/10/16 16:19:30  jlb
 irelated link starting to work

 Revision 1.3  2006/10/15 20:16:06  jlb
 work on related lines

 Revision 1.2  2006/10/12 16:23:27  jlb
 little design
 starting the related link (TBC)

 Revision 1.1  2006/10/11 23:46:33  jlb
 first functionnal version
 TODO : return more lines from log files

=head1 LICENCE

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; either version 2 of the License, or
(at your option) any later version.

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.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

=head1 AUTHOR

Jean-Louis Bergamo <jlb@googlog.org> copyright  2006-2007




syntax highlighted by Code2HTML, v. 0.9.1