#!/usr/bin/perl
#-----------------------------------------------------------------------------
#
#  xlog -- tools for handling xlog log files
#
#-----------------------------------------------------------------------------
#
#  Copyright (C) 2001  Jochen Topf <jochen@remote.org>
#
#  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
#
#-----------------------------------------------------------------------------

use strict;
use Getopt::Long;
use IO::File;
use Time::Local;

my $VERSION = "1.0";

my $session_grep = 0;
my %sessions;

my $subcommand = shift;

if ($subcommand eq "sgrep") {
  $subcommand = "grep";
  $session_grep = 1;
}

if ($subcommand eq "grep") {
  my %opts = ();
  GetOptions(\%opts, 'date=s', 'time=s', 'host=s', 'command=s', 'pid=i', 'sessionid=s', 'level=s@', 'id=s@', 'message=s@', 'x=s', 'v');
  if (defined $opts{'date'}) {
    if ($opts{'date'} =~ /^\d{8}$/) {
      $opts{'startdate'} = $opts{'date'};
      $opts{'enddate'} = $opts{'date'};
    } elsif ($opts{'date'} =~ /^(\d{8})-$/) {
      $opts{'startdate'} = $1;
      $opts{'enddate'} = "99999999";
    } elsif ($opts{'date'} =~ /^-(\d{8})$/) {
      $opts{'startdate'} = "00000000";
      $opts{'enddate'} = $1;
    } elsif ($opts{'date'} =~ /^(\d{8})-(\d{8})$/) {
      if ($1 < $2) {
        $opts{'startdate'} = $1;
        $opts{'enddate'} = $2;
      } else {
        $opts{'startdate'} = $2;
        $opts{'enddate'} = $1;
      }
    } else {
      print STDERR "xlog: illegal date: $opts{'date'}\n";
      exit 1;
    }
  }
  if (defined $opts{'time'}) {
    if ($opts{'time'} =~ /^\d{6}$/) {
      $opts{'starttime'} = $opts{'time'};
      $opts{'endtime'} = $opts{'time'};
    } elsif ($opts{'time'} =~ /^(\d{6})-$/) {
      $opts{'starttime'} = $1;
      $opts{'endtime'} = "235959";
    } elsif ($opts{'time'} =~ /^-(\d{6})$/) {
      $opts{'starttime'} = "000000";
      $opts{'endtime'} = $1;
    } elsif ($opts{'time'} =~ /^(\d{6})-(\d{6})$/) {
      if ($1 lt $2) {
        $opts{'starttime'} = $1;
        $opts{'endtime'} = $2;
      } else {
        $opts{'starttime'} = $2;
        $opts{'endtime'} = $1;
      }
    } else {
      print STDERR "xlog: illegal time: $opts{'time'}\n";
      exit 1;
    }
  }
  if (defined $opts{'sessionid'} && $opts{'sessionid'} !~ /^(\d+\.\d+|-)$/) {
    print STDERR "xlog: illegal session id: $opts{'sessionid'}\n";
    exit 1;
  }
  if (defined $opts{'level'}) {
    foreach (split(/,/, join(',', @{$opts{'level'}}))) {
      if (! /^(DBG|INF|ERR|ADM|SOS)$/) {
	print STDERR "xlog: illegal level: $_\n";
	exit 1;
      }
      $opts{'Hlevel'}->{$_} = 1;
    }
  }
  if (defined $opts{'id'}) {
    foreach (split(/,/, join(',', @{$opts{'id'}}))) {
      if (! /^[0-9a-fA-F]+$/) {
	print STDERR "xlog: illegal message id: $_\n";
	exit 1;
      }
      $opts{'Hid'}->{hex($_)} = 1;
    }
  }
  if (defined $opts{'message'}) {
    foreach (split(/,/, join(',', @{$opts{'message'}}))) {
      if (! /^[a-z_]+$/) {
	print STDERR "xlog: illegal message name: $_\n";
	exit 1;
      }
      $opts{'Hmessage'}->{$_} = 1;
    }
  }
  @ARGV = map { /\.(gz|Z)$/ ? "gzip -dc < $_ |" : $_ } @ARGV;
  my @SAVEARGV = @ARGV;
  xlog_grep(\%opts);
  if ($session_grep) {
    @ARGV = @SAVEARGV;
    while (<>) {
      my ($date, $time, $host, $command, $pid, $sessionid, $level, $id, $message, $text) = split(/ /, $_, 10);
      print if ($sessions{$sessionid});
    }
  }
} elsif ($subcommand eq "join") {
  xlog_join();
} elsif ($subcommand eq "stime") {
  xlog_stime();
} elsif ($subcommand eq "stat") {
  xlog_stat();
} elsif ($subcommand eq "cut") {
  my %opts = ();
  GetOptions(\%opts, 'date', 'time', 'host', 'command', 'pid', 'sessionid', 'level', 'id', 'message', 'x');
  xlog_cut(\%opts);
} elsif ($subcommand eq "help") {
  print <<"EOF";
Usage: xlog <subcommand> [OPTIONS] ...

Subcommands:
  xlog grep     print only lines matching certain criteria
  xlog sgrep    print all lines belonging to sessions matching certain criteria
  xlog join     join several logfiles sorting the lines by date/time
  xlog stat     display statistics on log file contents
  xlog cut      display only some columns of a log file
  xlog version  print version number
  xlog help     this help text

Options:
  xlog [s]grep [-date DATE] [-time TIME] [-host HOST] [-command COMMAND]
               [-pid PID] [-sessionid SESSIONID] [-level LEVEL] [-id ID]
	       [-message MESSAGE] [-x TEXT] <filename> ...
  xlog cut     [-date] [-time] [-host] [-command] [-pid] [-sessionid]
               [-level] [-id] [-message] [-x]

All options can be abbreviated to the first letter. DATE and TIME can be
ranges like '20010425-20010426'. LEVEL, ID, and MESSAGE can be comma
separated lists. TEXT is a regular expression, all others are just strings.
EOF
  exit 0;
} elsif ($subcommand eq "version") {
  print "xlog $VERSION\n";
  exit 0;
} else {
  print STDERR "xlog: unknown or missing subcommand.\n";
  exit 1;
}


#-----------------------------------------------------------------------------
#
#  xlog_grep
#
#-----------------------------------------------------------------------------
sub xlog_grep {
  my ($h) = @_;
  while (<>) {
    chomp;
    my ($date, $time, $host, $command, $pid, $sessionid, $level, $id, $message, $text) = split(/ /, $_, 10);
    if ((!defined $h->{'date'} || ($h->{'startdate'} le $date && $date le $h->{'enddate'})) &&
        (!defined $h->{'time'} || ($h->{'starttime'} le $time && $time le $h->{'endtime'})) &&
        (!defined $h->{'host'} || $h->{'host'} eq $host) &&
        (!defined $h->{'command'} || $h->{'command'} eq $command) &&
        (!defined $h->{'pid'} || $h->{'pid'} == $pid) &&
        (!defined $h->{'sessionid'} || $h->{'sessionid'} eq $sessionid) &&
        (!defined $h->{'level'} || $h->{'Hlevel'}->{$level}) &&
        (!defined $h->{'id'} || $h->{'Hid'}->{hex($id)}) &&
        (!defined $h->{'message'} || $h->{'Hmessage'}->{$message}) &&
        (!defined $h->{'x'} || $text =~ /$h->{'x'}/)) {
      if (! $h->{'v'}) {
	if ($session_grep) {
	  $sessions{$sessionid} = 1 unless ($sessionid eq '-');
	} else {
	  print "$_\n";
	}
      }
    } else {
      if ($h->{'v'}) {
	if ($session_grep) {
	  $sessions{$sessionid} = 1 unless ($sessionid eq '-');
	} else {
	  print "$_\n";
	}
      }
    }
  }
}


#-----------------------------------------------------------------------------
#
#  xlog_stat
#
#-----------------------------------------------------------------------------
sub xlog_stat {
  my %count_level;
  my %count_id;
  my %count_msg;
  my $count;

  while (<>) {
    chomp;
    my ($date, $time, $host, $command, $pid, $sessionid, $level, $id, $message, $text) = split(/ /, $_, 10);
    $count_level{$level}++;
    $count_id{"$message $id $level"}++;
    $count_msg{"$message ---- $level"}++;
    $count++;
  }

  printf("%40s: %10d (100%)\n", "all messages", $count);
  print "------------------------------------------------------------------------------\n";
  for my $lvl ("DBG", "INF", "ERR", "ADM", "SOS") {
    printf("%40s: %10d (%3d%)\n", $lvl, $count_level{$lvl}, 100*$count_level{$lvl} / $count);
  }
  printf("%40s: %10d\n", "ERR/INF", $count_level{'INF'} > 0 ? $count_level{'ERR'} / $count_level{'INF'} : 0);
  printf("%40s: %10d\n", "ADM/INF", $count_level{'INF'} > 0 ? $count_level{'ADM'} / $count_level{'INF'} : 0);
  print "------------------------------------------------------------------------------\n";
  for my $msg (sort { $count_msg{$b} <=> $count_msg{$a} } keys(%count_msg)) {
    printf("%40s: %10d (%3d%)\n", $msg, $count_msg{$msg}, 100*$count_msg{$msg} / $count);
  }
  print "------------------------------------------------------------------------------\n";
  for my $id (sort { $count_id{$b} <=> $count_id{$a} } keys(%count_id)) {
    printf("%40s: %10d (%3d%)\n", $id, $count_id{$id}, 100*$count_id{$id} / $count);
  }
}


#-----------------------------------------------------------------------------
#
#  xlog_cut
#
#-----------------------------------------------------------------------------
sub xlog_cut {
  my ($h) = @_;
  while (<>) {
    chomp;
    my ($date, $time, $host, $command, $pid, $sessionid, $level, $id, $message, $text) = split(/ /, $_, 10);
    my $out = "";
    $out .= "$date " if ($h->{'date'});
    $out .= "$time " if ($h->{'time'});
    $out .= "$host " if ($h->{'host'});
    $out .= "$command " if ($h->{'command'});
    $out .= "$pid " if ($h->{'pid'});
    $out .= "$sessionid " if ($h->{'sessionid'});
    $out .= "$level " if ($h->{'level'});
    $out .= "$id " if ($h->{'id'});
    $out .= "$message " if ($h->{'message'});
    $out .= "$text " if ($h->{'x'});
    chop $out;
    print "$out\n" if ($out);
  }
}


#-----------------------------------------------------------------------------
#
#  smallest
#
#-----------------------------------------------------------------------------
sub smallest {
  my ($r) = @_;

  my $s;
  foreach my $file (keys(%$r)) {
    if (! defined $s) {
      $s = $file;
    } else {
      $s = $file if ($r->{$file} lt $r->{$s});
    }
  }
  return $s;
}


#-----------------------------------------------------------------------------
#
#  xlog_join
#
#-----------------------------------------------------------------------------
sub xlog_join {
  my %fh;
  my %line;

  if (@ARGV < 1) {
    print "xlog: need at least one files for 'join' command\n";
    exit 1;
  }

  foreach my $file (@ARGV) {
    $fh{$file} = new IO::File $file;
    if (! defined $fh{$file}) {
      print "xlog: open of file '$file' failed: $!\n";
      exit 1;
    }
    $line{$file} = $fh{$file}->getline();
    if (! defined $line{$file}) {
      $fh{$file}->close();
      delete $fh{$file};
    }
  }

  while (%fh) {
    my $file = smallest(\%line);
    print "$line{$file}";
    $line{$file} = $fh{$file}->getline();
    if (! defined $line{$file}) {
      $fh{$file}->close();
      delete $fh{$file};
      delete $line{$file};
    }
  }
}


#-----------------------------------------------------------------------------
#
#  iso2ar
#
#-----------------------------------------------------------------------------
sub iso2ar {
  my ($date, $time) = @_;

  $date =~ /^(....)(..)(..)$/;
  my ($year, $mon, $mday) = ($1, $2, $3);
  $mon--;

  $time =~ /^(..)(..)(..)$/;
  my ($hours, $min, $sec) = ($1, $2, $3);

  return ($sec, $min, $hours, $mday, $mon, $year);
}


#-----------------------------------------------------------------------------
#
#  xlog_stime
#
#-----------------------------------------------------------------------------
sub xlog_stime {
  my %sids;
  while (<>) {
    chomp;
    my ($date, $time, $host, $command, $pid, $sessionid, $level, $id, $message, $text) = split(/ /, $_, 10);
    next if ($sessionid eq "-");
    if (defined $sids{$sessionid}) {
      $sids{$sessionid} =~ s/:.*$//;
      $sids{$sessionid} .= ":$date $time $message";
    } else {
      $sids{$sessionid} = "$date $time";
    }
  }
  foreach (sort { substr($a, 15) cmp substr($b, 15) } keys(%sids)) {
    my ($date1, $time1, $date2, $time2, $message) = split(/[ :]/, $sids{$_});
    my $diff = timelocal(iso2ar($date2, $time2)) - timelocal(iso2ar($date1, $time1));
    print "$_ $date1 $time1 $date2 $time2 $diff $message\n";
  }
}


#-- THE END ------------------------------------------------------------------


syntax highlighted by Code2HTML, v. 0.9.1