/usr/local/CPAN/Stem/Stem/Log/Tail.pm


#  File: Stem/Log/Tail.pm

#  This file is part of Stem.
#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.

#  Stem 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.

#  Stem 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 Stem; if not, write to the Free Software
#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

#  For a license to use the Stem under conditions other than those
#  described here, to purchase support for this software, or to purchase a
#  commercial warranty contract, please contact Stem Systems at:

#       Stem Systems, Inc.		781-643-7504
#  	79 Everett St.			info@stemsystems.com
#  	Arlington, MA 02474
#  	USA

package Stem::Log::Tail ;

use strict ;
use IO::Seekable ;
use Data::Dumper ;

use Stem::Trace 'log' => 'stem_status', 'sub' => 'TraceStatus' ;
use Stem::Trace 'log' => 'stem_error' , 'sub' => 'TraceError' ;

my $attr_spec = [

	{
		'name'		=> 'path',
		'required'	=> 1,
		'help'		=> <<HELP,
This is the full path to the file we want to tail.
HELP
	},

	{
		'name'		=> 'data_log',
		'required'	=> 1,
		'help'		=> <<HELP,
This is the log which gets sent the data log entries.
HELP
	},

	{
		'name'		=> 'status_log',
		'help'		=> <<HELP,
This is the log which gets sent the status log entries.
These include things like: Log has been rotated, deleted, moved, etc...
HELP
	},
	{
		'name'		=> 'label',
		'default'	=> 'tail',
		'help'		=> <<HELP,
Label to tag tailed log entry.
HELP
	},
	{
		'name'		=> 'level',
		'default'	=> '5',
		'help'		=> <<HELP,
Severity level for this tailed log entry.
HELP
	},
	{
		'name'		=> 'interval',
		'help'		=> <<HELP,
This specifies (in seconds) how often we check the log file for new
data.  If this is not specified, you need to call the tail_cmd method
to check for new data.
HELP
	},
	{
		'name'		=> 'delay',
		'default'	=> 10,
		'help'		=> <<HELP,
This specifies (in seconds) how long the delay is before the
first repeated checking of the log file for new data.
HELP
	},
] ;


sub new {

	my( $class ) = shift ;

	my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
	return $self unless ref $self ;

print "TAIL INT $self->{'interval'}\n" ;

	if ( my $interval = $self->{'interval'} ) {

		$self->{'timer'} = Stem::Event::Timer->new(
				'object'	=> $self,
				'method'	=> 'tail_cmd',
				'interval'	=> $interval,
				'delay'		=> $self->{'delay'},
				'repeat'	=> 1,
				'hard'		=> 1,
		) ;

print "TIMER $self->{'timer'}\n" ;

	}

	$self->{'prev_size'} = 0 ;
	$self->{'prev_mtime'} = 0 ;
	$self->{'prev_inode'} = -1 ;

	return( $self ) ;
}

sub tail_cmd {

	my( $self ) = @_ ;

print "TAILING\n" ;

	local( *LOG ) ;

	my $path = $self->{'path'} ;

	unless( open( LOG, $path ) ) {

		return if $self->{'open_failed'} ;
		$self->{'open_failed'} = 1 ;

		if ( my $status_log = $self->{'status_log'} ) {

			Stem::Log::Entry->new(
				'logs'	=> $status_log,
				'label'	=> 'LogTail status',
				'text'	=>
					"LogTail: missing log $path $!\n",
			) ;
		}
		return ;
	}

	$self->{'open_failed'} = 0 ;

	my( $inode, $size, $mtime ) = (stat LOG)[1, 7, 9] ;

	TraceStatus "size $size mtime $mtime $inode" ;

	my $prev_inode = $self->{'prev_inode'} ;
	my $prev_size = $self->{'prev_size'} ;

	if ( $prev_inode == -1 ) {

		$self->{'prev_inode'} = $inode ;

		if ( my $status_log = $self->{'status_log'} ) {

			Stem::Log::Entry->new(
				'logs'	=> $status_log,
				'level'	=> 6,
				'label'	=> 'LogTail status',
				'text'	=>
					"LogTail: first open of $path\n",
			) ;
		}
	}
	elsif ( $inode != $prev_inode ) {

		$self->{'prev_inode'} = $inode ;

		if ( my $status_log = $self->{'status_log'} ) {

			Stem::Log::Entry->new(
				'logs'	=> $status_log,
				'level'	=> 6,
				'label'	=> 'LogTail status',
				'text'	=>
					"LogTail: $path has moved\n",
			) ;
		}

# tail the entire file as it is new

		$prev_size = 0 ;

	}
	elsif ( $size < $prev_size ) {
	
		if ( my $status_log = $self->{'status_log'} ) {

			Stem::Log::Entry->new(
				'logs'	=> $status_log,
				'level'	=> 6,
				'label'	=> 'LogTail status',
				'text'	=>
					"LogTail: $path has shrunk\n",
			) ;
		}

# tail the entire file as it has shrunk

		$prev_size = 0 ;
	}
	elsif ( $size == $prev_size ) {

		TraceStatus "no changes" ;
		return ;
	}

	$self->{'prev_size'} = $size ;

	my $delta_size = $size - $prev_size ;

	return unless $delta_size ;

	my $read_buf ;

	sysseek( *LOG, $prev_size, SEEK_SET ) ;
	sysread( *LOG, $read_buf, $delta_size ) ;

	Stem::Log::Entry->new(
		'logs'	=> $self->{'data_log'},
		'level'	=> $self->{'level'},
		'label'	=> $self->{'label'},
		'text'	=> $read_buf,
	) ;

	return ;
}

1 ;