File Coverage

blib/lib/Logfile/Tail/Switch.pm
Criterion Covered Total %
statement 65 67 97.0
branch 30 36 83.3
condition 4 5 80.0
subroutine 8 8 100.0
pod 2 2 100.0
total 109 118 92.3


line stmt bran cond sub pod time code
1             package Logfile::Tail::Switch;
2              
3             our $DATE = '2019-01-30'; # DATE
4             our $VERSION = '0.004'; # VERSION
5              
6 1     1   89010 use 5.010001;
  1         18  
7 1     1   7 use strict;
  1         2  
  1         24  
8 1     1   5 use warnings;
  1         2  
  1         45  
9              
10 1     1   512 use Time::HiRes 'time';
  1         1404  
  1         4  
11              
12             our $WARN_NO_MATCH = 1;
13              
14             sub new {
15 4     4 1 12223 my ($class, %args) = @_;
16              
17 4         32 my $self = {
18             _cur_file => {},
19             _cur_fh => {},
20             _pending => {},
21             check_freq => 2,
22             tail_new => 0,
23             };
24              
25 4 50       19 if (defined(my $globs = delete $args{globs})) {
26 4 50       18 ref($globs) eq 'ARRAY' or die "globs must be arrayref";
27 4         24 $self->{globs} = $globs;
28             } else {
29 0         0 die "Please specify globs";
30             }
31 4 100       16 if (defined(my $check_freq = delete $args{check_freq})) {
32 3         9 $self->{check_freq} = $check_freq;
33             }
34 4 100       13 if (defined(my $tail_new = delete $args{tail_new})) {
35 1         3 $self->{tail_new} = $tail_new;
36             }
37 4 50       16 die "Unknown arguments: ".join(", ", keys %args) if keys %args;
38              
39 4         17 bless $self, $class;
40             }
41              
42             sub _switch {
43 12     12   47 my ($self, $glob, $filename, $seek_end) = @_;
44              
45             #say "D: opening $filename";
46 12         99 $self->{_cur_file}{$glob} = $filename;
47 12 50       565 open my $fh, "<", $filename or die "Can't open $filename: $!";
48 12 100       90 seek $fh, 0, 2 if $seek_end;
49 12         70 $self->{_cur_fh}{$glob} = $fh;
50             }
51              
52             sub _getline {
53 69     69   144 my ($self, $fh) = @_;
54              
55 69         732 my $size = -s $fh;
56 69         218 my $pos = tell $fh;
57             #say "D:size=<$size>, pos=<$pos>";
58 69 100       204 if ($pos == $size) {
    50          
59             # we are still at the end of file, return empty string
60 45         149 return '';
61             } elsif ($pos > $size) {
62             # file reduced in size, it probably means it has been rotated, start
63             # from the beginning
64 0         0 seek $fh, 0, 0;
65             } else {
66             # there are new content to read after our position
67             }
68 24   50     409 return(<$fh> // '');
69             }
70              
71             sub getline {
72 47     47 1 139 my $self = shift;
73              
74 47         160 my $now = time();
75              
76             CHECK_NEWER_FILES:
77             {
78 47         95 last if $self->{_last_check_time} &&
79 47 100 100     341 $self->{_last_check_time} >= $now - $self->{check_freq};
80 7         18 $self->{_last_check_time} = $now;
81             #say "D: checking for newer file";
82 7         14 for my $glob (@{ $self->{globs} }) {
  7         43  
83 9         1352 my @files = sort glob($glob);
84             #say "D: files matching glob: ".join(", ", @files);
85 9 100       65 unless (@files) {
86 1 50       63 warn "No files matched '$glob'" if $WARN_NO_MATCH;
87 1         8 next;
88             }
89 8 100       52 if (defined $self->{_cur_fh}{$glob}) {
90 4         20 for (@files) {
91             # there is a newer file than the current one, add to the
92             # pending list of files to be read after the current one say
93 16 100       62 if ($_ gt $self->{_cur_file}{$glob}) {
94             #say "D: there is a newer file: $_";
95 8         59 $self->{_pending}{$glob}{$_} = 1;
96             }
97             }
98             } else {
99             # this is our first time, pick the newest file in the pattern
100             # and tail it.
101 4         14 $self->_switch($glob, $files[-1], 1);
102             }
103             }
104             }
105              
106 47         107 my $line = '';
107 47         71 for my $glob (@{ $self->{globs} }) {
  47         123  
108 62         119 my $fh = $self->{_cur_fh}{$glob};
109 62 100       152 next unless $fh;
110 61         157 $line = $self->_getline($fh);
111 61 100       171 if (length $line) {
    100          
112 18         36 last;
113 43         169 } elsif (keys %{$self->{_pending}{$glob}}) {
114             # switch to a newer named file
115 8         20 my @files = sort keys %{$self->{_pending}{$glob}};
  8         59  
116 8         53 $self->_switch($glob, $files[0], $self->{tail_new});
117 8         33 delete $self->{_pending}{$glob}{$files[0]};
118 8         27 $line = $self->_getline($self->{_cur_fh}{$glob});
119 8 100       216 last if length $line;
120             }
121             }
122 47         383 $line;
123             }
124              
125             1;
126             # ABSTRACT: Tail a file, but switch when another file with newer name appears
127              
128             __END__