123
123

Reputation: 11216

How to tail file in perl when copytruncate is used

Problem

I have created a simple perl script to read log files and process the data asynchronously.
The reading sub also checks for changes in inode number so a new filehandle is created when the logs rotate.

The problem i am facing is that when copytruncate is used in logrotation then then inode does not change when the file is rotated.
This shouldn't be an issue as the script should just continue reading the file but for some reason that i cannot immediately see, as soon as the logs rotate no new lines are ever read.


Question

How can i modify the below script (or completely scrap and start again) to continously tail a file which is logrotated using copytruncate using perl ?


Code

use strict;
use warnings;

use threads;
use Thread::Queue;
use threads::shared;

my $logq = Thread::Queue->new();
my %Servers :shared;
my %servername :shared;

#########
#This sub just reads the data off the queue and processes it, i have
#reduced it to a simple print statement for simplicity.
#The sleep is to prevent it from eating cpu.
########

sub process_data
{
        while(sleep(5)){
                if ($logq->pending())
                {
                        while($logq->pending() > 0){
                                my $data = $logq->dequeue();
                                print "Data:$data\n";
                        }
                }
        }
}

sub read_file
{
        my $myFile=$_[0];
        #Get the argument and assign to var.

        open(my $logfile,'<',$myFile) || die "error";
        #open file

        my $Inode=(stat($logfile))[1];
        #Get the current inode

        seek $logfile, 0, 2;
        #Go to the end of the file

        for (;;) {
                while (<$logfile>) {
                        chomp( $_ );
                        $logq->enqueue( $_ );
                        #Add lines to queue for processing

                }
                sleep 5;
                if($Inode != (stat($myFile))[1]){
                        close($logfile);
                        while (! -e $myFile){
                                sleep 2;
                        }
                        open($logfile,'<',$myFile) || die "error";
                        $Inode=(stat($logfile))[1];
                }
                #Above checks if the inode has changed and the file exists still

                seek $logfile, 0, 1;
                #Remove eof

        }

}


my $thr1 = threads->create(\&read_file,"test");
my $thr4 = threads->create(\&process_data);
$thr1->join();
$thr4->join();
#Creating the threads, can add more log files for processing or multiple processing sections.

Possibly relevant info

Log config for logrotate contains

compress
compresscmd /usr/bin/bzip2
uncompresscmd /usr/bin/bunzip2
daily
rotate 5
notifempty
missingok
copytruncate

for this file.

Specs

GNU bash, version 3.2.57(1)-release (s390x-ibm-linux-gnu)
perl, v5.10.0
(if logrotate has version and someone knows how to check then i will also add that)

Any more info needed just ask.

Upvotes: 1

Views: 694

Answers (1)

123
123

Reputation: 11216

So the reason that this was failing is pretty obvious when you look at copytruncate, it copies the original file and then truncates the current one.
Whilst this ensure that the inode is kept, it created another problem.

As the current way i tail the file is by simply staying at the end and removing the eof flag this means that when the file is truncated, the pointer stays at the position of the last line before truncation, which in turn means that no more lines would be read until it reached that pointer again.

The obvious solution then is to simply check the size of the file and reset the pointer if it is ever pointing past the end of the file.

I found it easier to just check that file size never got smaller though, using the two lines below.

my $fileSize=(stat($logfile))[7];
#Added after the inode is assigned

and changing

if($Inode != (stat($myFile))[1]){

to

if($Inode != (stat($myFile))[1] || (stat($myFile))[7] < $fileSize){

Upvotes: 2

Related Questions