File Coverage

blib/lib/Logfile/Tail/Switch.pm
Criterion Covered Total %
statement 65 67 97.0
branch 29 34 85.2
condition 4 5 80.0
subroutine 8 8 100.0
pod 2 2 100.0
total 108 116 93.1


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