#!/usr/bin/perl -w # # $Revision: 2.5 $ $Date: 2012-03-27 21:42:12-04 $ # $UUID: 3164fe07-8ee8-3938-a5e2-2ecabac503df $ # # \$opth, # print usage "d" => \$optd, # debug output "m" => \$optm, # print manpage "r" => \$optr, # print human-readable output, ignored if -s "s" => \$opts, # show current filesizes vs. ones in DBM file "u" => \$optu, # print UUID "v" => \$optv, # print version "w" => \$optw # print source location ); usage unless $rc; manpage if $optm; myuuid if $optu; version if $optv; where if $optw; usage if $opth; unless ($opts) { usage "Needs at least one file argument\n" unless @ARGV; } # If "-s", don't change the DBM file, just compare it to current files. if ($opts) { showdbm; } else { runtail; } exit(0); #--------------------------------------------------------------------- # Show current filesizes vs. contents of DBM files. sub showdbm { my %states; tie(%states, 'NDBM_File', $state_file, O_RDONLY, 0660) or die "no statefile $state_file: $!\n"; # If filenames were provided, just check those... if (@ARGV) { while (my $filename = shift(@ARGV)) { $b = basename($filename); next if $b eq "$state_file.dir"; next if $b eq "$state_file.pag"; next unless -r $filename; ($device, $inode, $size) = (stat(_))[0, 1, 7]; next unless defined($device); $skey = $device . '/' . $inode . '/' . $b; # No entry (-1) usually means the file isn't in the DB; # it could also mean the file's there but zero-length. my $offset = $states{$skey} || -1; $offset = 0 if $offset == -1 && $size == 0; my $cmp = ($offset == $size) ? "s" : "d"; print "$cmp\t$offset\t$size\t$skey\n"; } } # ...or display the whole DBM file. else { foreach (sort keys %states) { print "$states{$_}\t$_\n"; } } # Close DBM. untie(%states); } #--------------------------------------------------------------------- # "tail" each file, ignoring any statefiles that may have been # accidentally included. sub runtail { my %states; tie(%states, 'NDBM_File', $state_file, O_CREAT | O_RDWR, 0660) or die "cannot tie state to $state_file: $!\n"; while (my $filename = shift(@ARGV)) { $b = basename($filename); next if $b eq "$state_file.dir"; next if $b eq "$state_file.pag"; next unless -f $filename; ($device, $inode, $size) = (stat(_))[0, 1, 7]; next unless defined($device); $skey = $device . '/' . $inode . '/' . $b; warn "$filename: $skey\n" if $optd; next unless open($fh, '<', $filename); # Reverting to the last cursor position my $offset = $states{$skey} || 0; my $append = ''; if ($optr) { # print filenames if ($offset == $size) { print "==> $filename [no changes] <==\n"; } else { print "\n==> $filename <==\n"; $append = "\n"; } } if ($offset <= $size) { sysseek($fh, $offset, SEEK_SET); } else { # file was truncated, restarting from the beginning $offset = 0; } # Reading until the end my $buffer; while ((my $read_count = sysread($fh, $buffer, 8192)) > 0) { $offset += $read_count; print $buffer; } print $append; # Nothing to read close($fh); $states{$skey} = $offset; } # Sync the states untie(%states); } #--------------------------------------------------------------------- # Print a usage message from the comments and exit. sub usage { my ($emsg) = @_; use Pod::Usage qw(pod2usage); warn "$emsg\n" if defined $emsg; pod2usage(-verbose => 99, -sections => "NAME|SYNOPSIS|OPTIONS"); } sub manpage { my @args = ("perldoc", "$0"); exec {$args[0]} @args; # safe even with one-arg list die "should not get here\n"; } #--------------------------------------------------------------------- # Print the UUID, current version, or source location. sub myuuid { my $UUID = $1 if q$UUID: eaa1c8fb-9ddd-3bf1-9084-54a8f54e2240 $ =~ /UUID: (.*) /; print "$UUID\n"; exit(0); } sub version { my $VERSION = sprintf("%d.%02d", q$Revision: 2.5 $ =~ /(\d+)\.(\d+)/); my $DATE = $1 if q$Date: 2012-03-27 21:42:12-04 $ =~ /Date: (.*) /; print "$myname $VERSION $DATE\n"; exit(0); } sub where { my $SOURCE = $1 if q$Source: /home/vogelke/notebook/2012/0326/tailing-files/RCS/since.pl,v $ =~ /Source: (.*) /; my $HOST = $1 if q$Host: www.hcst.net $ =~ /Host: (.*) /; print "file://$HOST", "$SOURCE\n"; exit(0); } #--------------------------------------------------------------------- __END__ =head1 NAME since - shows lines appended to files since the last time it was run. =head1 SYNOPSIS since [-dhmruvw] file [file ...] since [-s] =head1 OPTIONS =over 4 =item B<-d> Print verbose output to stderr. =item B<-h> Print a brief help message and exit. =item B<-m> Print the manual page and exit. =item B<-r> Print more readable output including the filename. Ignored if B<-s> is also specified. =item B<-s> Show the current DBM contents, if any. Output could look like this for two files, one of which has changed since the DBM was updated: d 87 116 1234/5678/some-file s 120 120 1234/5679/other-file Field 1 is 's' (file is unchanged) or 'd' (different). Field 2 is the old offset into the file -- in other words, where we left off reading. Field 3 is the new size of the file, and field 4 is the device/inode/basename key used to store the offset. All fields are tab-separated. =item B<-u> Print the script UUID and exit. =item B<-v> Print the version and exit. =item B<-w> Print the source location and exit. =back =head1 DESCRIPTION B is essentially a stateful version of tail. Unlike tail, B only shows the lines appended since the last time. It's useful for monitoring growing log files. =head1 EXAMPLE Take a snapshot of some logs: me% since /var/log/Xorg.0.log /var/log/authlog /var/log/brlog \ /var/log/ftplog /var/log/kernlog /var/log/local?log \ /var/log/maillog /var/log/syslog /var/log/uucplog > /dev/null Logs are quiet, so using "-s" shows no differences: me% since -s /var/log/Xorg.0.log ... /var/log/uucplog s 62045 62045 267389/9904/Xorg.0.log s 0 0 267389/9939/authlog s 27 27 267389/1930/brlog s 0 0 267389/9346/ftplog s 32142 32142 267389/9953/kernlog s 15912168 15912168 267389/9824/local0log s 0 0 267389/9349/local1log s 316 316 267389/9351/local3log s 356 356 267389/9352/local4log s 1902 1902 267389/9353/local5log s 72 72 267389/9354/local6log s 120 120 267389/9355/local7log s 159238 159238 267389/9956/maillog s 244300 244300 267389/9948/syslog s 0 0 267389/9362/uucplog Change one logfile, update the DBM files, and display any changes in more readable form: me% logger running pid $$ me% since -r /var/log/Xorg.0.log ... /var/log/uucplog ==> /var/log/Xorg.0.log [no changes] <== ==> /var/log/authlog [no changes] <== ==> /var/log/brlog [no changes] <== ==> /var/log/ftplog [no changes] <== ==> /var/log/kernlog [no changes] <== ==> /var/log/local0log [no changes] <== ==> /var/log/local1log [no changes] <== ==> /var/log/local3log [no changes] <== ==> /var/log/local4log [no changes] <== ==> /var/log/local5log [no changes] <== ==> /var/log/local6log [no changes] <== ==> /var/log/local7log [no changes] <== ==> /var/log/maillog [no changes] <== ==> /var/log/newslog [no changes] <== ==> /var/log/ntplog [no changes] <== ==> /var/log/securelog [no changes] <== ==> /var/log/syslog <== Mar 27 21:17:09 myhost vogelke: running pid 24049 ==> /var/log/uucplog [no changes] <== =head1 AUTHOR Original version by marc@welz.org.za at http://welz.org.za/projects/since Mods by Karl Vogel , Array InfoTech =cut