File Coverage

blib/lib/Log/Deep/Read.pm
Criterion Covered Total %
statement 65 166 39.1
branch 9 78 11.5
condition 11 41 26.8
subroutine 13 18 72.2
pod 6 6 100.0
total 104 309 33.6


line stmt bran cond sub pod time code
1             package Log::Deep::Read;
2              
3             # Created on: 2008-11-11 19:37:26
4             # Create by: ivan
5             # $Id$
6             # $Revision$, $HeadURL$, $Date$
7             # $Revision$, $Source$, $Date$
8              
9 2     2   26198 use strict;
  2         4  
  2         61  
10 2     2   10 use warnings;
  2         2  
  2         57  
11 2     2   420 use version;
  2         1341  
  2         11  
12 2     2   138 use Carp;
  2         2  
  2         118  
13 2     2   894 use Data::Dump::Streamer;
  2         47142  
  2         19  
14 2     2   1481 use English qw/ -no_match_vars /;
  2         3854  
  2         15  
15 2     2   1537 use Readonly;
  2         2313  
  2         118  
16 2     2   1482 use Time::HiRes qw/sleep/;
  2         2821  
  2         9  
17 2     2   389 use base qw/Exporter/;
  2         4  
  2         168  
18 2     2   940 use Log::Deep::File;
  2         3  
  2         81  
19 2     2   902 use Log::Deep::Line;
  2         5  
  2         3248  
20              
21             our $VERSION = version->new('0.3.5');
22             our @EXPORT_OK = qw//;
23             our %EXPORT_TAGS = ();
24              
25             Readonly my @colours => qw/
26             black
27             red
28             green
29             yellow
30             blue
31             magenta
32             cyan
33             white
34             /;
35             Readonly my %excludes => map { $_ => 1 } qw/cyangreen greencyan bluemagenta magentablue cyanblue bluecyan greenblue bluegreen/;
36              
37             sub new {
38 1     1 1 13 my $caller = shift;
39 1 50       6 my $class = ref $caller ? ref $caller : $caller;
40 1         3 my %param = @_;
41 1         2 my $self = \%param;
42              
43 1         2 bless $self, $class;
44              
45 1   50     14 $self->{short_break} ||= 2;
46 1   50     6 $self->{short_lines} ||= 2;
47 1   50     9 $self->{long_break} ||= 5;
48 1   50     6 $self->{long_lines} ||= 5;
49 1   50     7 $self->{foreground} ||= 0;
50 1   50     6 $self->{background} ||= 0;
51 1   50     7 $self->{sessions_max} ||= 100;
52 1   50     9 $self->{sleep_time} ||= 0.5;
53              
54 1         19 $self->{dump} = Data::Dump::Streamer->new()->Indent(4);
55              
56             $self->{line} = {
57             verbose => $self->{verbose},
58             display => $self->{display},
59             show => $self->{show},
60             dump => $self->{dump},
61 1         103 };
62              
63 1         4 delete $self->{show};
64 1         2 delete $self->{display};
65              
66 1         5 return $self;
67             }
68              
69             sub read_files {
70 0     0 1 0 my ($self, @files) = @_;
71 0         0 my $once = 1;
72 0         0 my $read = 5;
73 0         0 my %files;
74              
75 0         0 for my $file_glob (@files) {
76 0         0 my (@files, $warn);
77             {
78 0     0   0 local $SIG{__WARN__} = sub { $warn = $_ };
  0         0  
  0         0  
79 0         0 @files = glob $file_glob;
80             }
81              
82 0 0 0     0 next if !@files || $warn;
83              
84 0         0 for my $file (sort @files) {
85 0   0     0 $files{$file} ||= Log::Deep::File->new($file);
86             }
87             }
88 0 0       0 die "No files to read!" if !keys %files;
89              
90             # record the current number of files watched
91 0         0 $self->{file_count} = keys %files;
92              
93             # loop for ever if we are following the log file other wise we loop
94             # only one time.
95 0   0     0 while ( $self->{follow} || $once == 1 ) {
96             # increment $once to keep track of the itteration number
97 0         0 $once++;
98 0         0 my $lines = 0;
99 0 0       0 if ($read < 1) {
100 0         0 $read = 1;
101             }
102              
103             # itterate over each file found/specified
104             FILE:
105 0         0 for my $file (keys %files) {
106 0 0 0     0 next FILE if !$file || !$files{$file};
107              
108             # process the file for any (new) log lines
109 0         0 $lines += $self->read_file($files{$file});
110 0 0       0 if ( !$files{$file}->{handle} ) {
111             # delete the file if there was nothing to read
112 0         0 delete $files{$file};
113             }
114             }
115              
116             # exit the loop if there was no data to be read
117 0 0       0 last if !%files;
118              
119             # turn off tracking last lines/sessions
120 0         0 $self->{number} = 0;
121 0         0 $self->{'session-number'} = 0;
122              
123             # every 1,000 itterations check if there are any new files matching
124             # any passed globs in, allows not having to re-run every time a new
125             # log file is created.
126 0 0 0     0 if ( $once % 1_000 || !%files ) {
    0          
127 0         0 for my $file ( map { sort glob $_ } @files ) {
  0         0  
128             # check that the file still exists
129 0 0       0 next if !-e $file;
130              
131             # add the new file only if it doesn't already exist
132 0   0     0 $files{$file} ||= { name => $file };
133             }
134              
135             # record the current number of files watched
136 0         0 $self->{file_count} = keys %files;
137             }
138             elsif ( $self->{follow} ) {
139 0 0       0 $read += $lines ? 1 : -1;
140 0 0       0 my $multiplier =
    0          
141             $lines ? 1
142             : !$read ? 5
143             : 2;
144             # sleep every time we have cycled through all the files to
145             # reduce CPU load.
146 0         0 sleep $self->{sleep_time} * $multiplier;
147             }
148              
149             # exit the loop if all log files have been deleted
150 0 0       0 last if !%files;
151             }
152              
153 0         0 return;
154             }
155              
156             sub read_file {
157 0     0 1 0 my ($self, $file) = @_;
158 0         0 my @lines;
159             my %sessions;
160 0         0 my $line_count = 0;
161              
162 0 0       0 confess "read_file called with out a file object!" if !ref $file;
163              
164             # read the rest of the lines in the file
165             LINE:
166 0         0 while (my $line = $file->line) {
167              
168 0         0 chomp $line;
169 0 0       0 next if !$line;
170 0         0 $line_count++;
171              
172             # parse the line
173 0         0 my $line = Log::Deep::Line->new( { %{ $self->{line} } }, $line, $file );
  0         0  
174              
175             # skip lines that don't have a session id
176 0 0       0 next LINE if !$line->id;
177              
178             # set the colour for the line
179 0         0 $line->colour( $self->session_colour($line->id) );
180              
181             # skip displaying the line if it should be filtered out
182 0 0       0 next LINE if !$line->show();
183              
184             # get the display text for the line
185 0         0 my $line_text = eval { $line->text() . join '', $line->data() };
  0         0  
186              
187             # check that there were no errors
188 0 0       0 if ($EVAL_ERROR) {
189             # warn the errors
190 0         0 warn $EVAL_ERROR;
191              
192             # go on to the next line
193 0         0 next LINE;
194             }
195              
196             # check if we are displaying lines/sessions from the end of the file
197 0 0       0 if ($self->{number}) {
    0          
198             # add the line to end of the lines
199 0         0 push @lines, $line_text;
200 0 0       0 if (@lines > 10 * $self->{number}) {
201 0         0 @lines = @lines[@lines - $self->{number} - 1 .. @lines - 1];
202             }
203             }
204             elsif ( $self->{'session-number'} ) {
205             # get the session id
206 0         0 my $session = $line->id;
207              
208             # add the session to the list of session if we have not already come accross it
209 0 0       0 push @lines, $session if !$sessions{$session};
210              
211             # add the line to the session's lines
212 0   0     0 $sessions{$session} ||= '';
213 0         0 $sessions{$session} .= $line_text;
214             }
215             else {
216             # show any file change info
217 0         0 $self->changed_file($file);
218              
219             # print out the log line
220 0         0 print $line_text;
221             }
222             }
223              
224             # check if we have any stored lines to print
225 0 0       0 if (@lines) {
226             # print any file change info
227 0         0 $self->changed_file($file);
228              
229             # check which format we are using
230 0 0       0 if ($self->{number}) {
    0          
231 0 0       0 my $first_line = @lines - $self->{number} <= 0 ? 0 : @lines - $self->{number};
232 0         0 print @lines[ $first_line .. (@lines - 1) ];
233             }
234             elsif ( $self->{'session-number'} ) {
235             # work out what to do
236 0 0       0 my $first_line = @lines - $self->{'session-number'} <= 0 ? 0 : @lines - $self->{'session-number'};
237 0         0 for my $i ( $first_line .. (@lines - 1) ) {
238 0         0 print $sessions{$lines[$i]};
239             }
240             }
241             }
242              
243 0         0 $file->reset;
244              
245 0         0 return $file->{handle};
246             }
247              
248             sub read {
249 0     0 1 0 my ($self) = @_;
250 0         0 my @lines;
251             my %sessions;
252 0         0 my $file = $self->{file};
253              
254 0 0       0 if (!ref $file) {
255 0         0 $file = $self->{file} = Log::Deep::File->new($file);
256             }
257              
258 0         0 my $line = $file->line;
259              
260 0 0       0 if ( !$line ) {
261 0         0 $file->reset;
262 0         0 return;
263             }
264              
265 0         0 chomp $line;
266 0 0       0 return $self->read() if !$line;
267              
268             # parse the line
269 0         0 $line = Log::Deep::Line->new( { %{ $self->{line} } }, $line, $file );
  0         0  
270 0         0 $line->colour( $self->session_colour($line->id) );
271              
272             # skip displaying the line if it should be filtered out
273 0 0       0 return $self->read if !$line->show();
274              
275 0         0 return $line;
276             }
277              
278             sub changed_file {
279 0     0 1 0 my ( $self, $file ) = @_;
280              
281             # check if we have printed some lines from this file before
282 0 0 0     0 if ( !$self->{last_print_file} || "$self->{last_print_file}" ne "$file" ) {
283 0 0       0 if ( $self->{file_count} > 1 ) {
284             # print out the change in file (same format as tail)
285 0         0 print "\n==> $file <==\n";
286             }
287              
288             # set this file as the last printed file
289 0         0 $self->{last_print_file} = $file;
290             }
291              
292 0         0 return;
293             }
294              
295             sub session_colour {
296 55     55 1 704 my ($self, $session_id) = @_;
297              
298 55 50       98 confess "No session id supplied!" if !$session_id;
299              
300             # return the cached session colour if we have one
301 55 100       130 return $self->{sessions}{$session_id}{colour} if $self->{sessions}{$session_id};
302              
303             # set the next colour, cycle through backgrounds for each foreground
304 53 100       163 if ( $self->{background} + 1 < @colours ) {
    50          
305 47         223 $self->{background}++;
306             }
307             elsif ( $self->{foreground} + 1 < @colours ) {
308 6         54 $self->{background} = 0;
309 6         8 $self->{foreground}++;
310             }
311             else {
312 0         0 $self->{background} = 0;
313 0         0 $self->{foreground} = 0;
314             }
315              
316             # check that the colour is not an excluded colour or that background and
317             # foreground colours are not the same.
318 53 100 100     179 if (
319             $excludes{ $colours[$self->{foreground}] . $colours[$self->{background}] }
320             || $self->{foreground} == $self->{background}
321             ) {
322             # we cannot use this colour so get the next colour in the sequence
323 13         208 return $self->session_colour($session_id);
324             }
325              
326 40         756 my $colour = "$colours[$self->{foreground}] on_$colours[$self->{background}]";
327              
328             # remove old sessions
329             # TODO need to get this code working
330 40         955 if ( 0 && keys %{ $self->{sessions} } > $self->{sessions_max} ) {
331             # get max session with the current colour
332             my $time = 0;
333             for my $session ( keys %{ $self->{sessions} } ) {
334             $time = $self->{session}{$session}{time} if $time < $self->{session}{$session}{time} && $self->{session}{$session}{colour} eq $colour;
335             }
336              
337             # now remove sessions older than $time
338             for my $session ( keys %{ $self->{sessions} } ) {
339             delete $self->{session}{$session} if $self->{session}{$session}{time} <= $time;
340             }
341             }
342              
343             # cache the session info
344 40         138 $self->{sessions}{$session_id}{time} = time;
345 40         74 $self->{sessions}{$session_id}{colour} = $colour;
346              
347             # return the colour
348 40         131 return $colour;
349             }
350              
351              
352             1;
353              
354             __END__