File Coverage

blib/lib/Log/Deep/Read.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


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   29878 use strict;
  2         4  
  2         65  
10 2     2   7 use warnings;
  2         2  
  2         42  
11 2     2   443 use version;
  2         1481  
  2         8  
12 2     2   93 use Carp;
  2         3  
  2         84  
13 2     2   1075 use Data::Dump::Streamer;
  0            
  0            
14             use English qw/ -no_match_vars /;
15             use Readonly;
16             use Time::HiRes qw/sleep/;
17             use base qw/Exporter/;
18             use Log::Deep::File;
19             use Log::Deep::Line;
20              
21             our $VERSION = version->new('0.3.3');
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             my $caller = shift;
39             my $class = ref $caller ? ref $caller : $caller;
40             my %param = @_;
41             my $self = \%param;
42              
43             bless $self, $class;
44              
45             $self->{short_break} ||= 2;
46             $self->{short_lines} ||= 2;
47             $self->{long_break} ||= 5;
48             $self->{long_lines} ||= 5;
49             $self->{foreground} ||= 0;
50             $self->{background} ||= 0;
51             $self->{sessions_max} ||= 100;
52             $self->{sleep_time} ||= 0.5;
53              
54             $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             };
62              
63             delete $self->{show};
64             delete $self->{display};
65              
66             return $self;
67             }
68              
69             sub read_files {
70             my ($self, @files) = @_;
71             my $once = 1;
72             my $read = 5;
73             my %files;
74              
75             for my $file_glob (@files) {
76             my (@files, $warn);
77             {
78             local $SIG{__WARN__} = sub { $warn = $_ };
79             @files = glob $file_glob;
80             }
81              
82             next if !@files || $warn;
83              
84             for my $file (sort @files) {
85             $files{$file} ||= Log::Deep::File->new($file);
86             }
87             }
88             die "No files to read!" if !keys %files;
89              
90             # record the current number of files watched
91             $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             while ( $self->{follow} || $once == 1 ) {
96             # increment $once to keep track of the itteration number
97             $once++;
98             my $lines = 0;
99             if ($read < 1) {
100             $read = 1;
101             }
102              
103             # itterate over each file found/specified
104             FILE:
105             for my $file (keys %files) {
106             next FILE if !$file || !$files{$file};
107              
108             # process the file for any (new) log lines
109             $lines += $self->read_file($files{$file});
110             if ( !$files{$file}->{handle} ) {
111             # delete the file if there was nothing to read
112             delete $files{$file};
113             }
114             }
115              
116             # exit the loop if there was no data to be read
117             last if !%files;
118              
119             # turn off tracking last lines/sessions
120             $self->{number} = 0;
121             $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             if ( $once % 1_000 || !%files ) {
127             for my $file ( map { sort glob $_ } @files ) {
128             # check that the file still exists
129             next if !-e $file;
130              
131             # add the new file only if it doesn't already exist
132             $files{$file} ||= { name => $file };
133             }
134              
135             # record the current number of files watched
136             $self->{file_count} = keys %files;
137             }
138             elsif ( $self->{follow} ) {
139             $read += $lines ? 1 : -1;
140             my $multiplier =
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             sleep $self->{sleep_time} * $multiplier;
147             }
148              
149             # exit the loop if all log files have been deleted
150             last if !%files;
151             }
152              
153             return;
154             }
155              
156             sub read_file {
157             my ($self, $file) = @_;
158             my @lines;
159             my %sessions;
160             my $line_count = 0;
161              
162             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             while (my $line = $file->line) {
167              
168             chomp $line;
169             next if !$line;
170             $line_count++;
171              
172             # parse the line
173             my $line = Log::Deep::Line->new( { %{ $self->{line} } }, $line, $file );
174              
175             # skip lines that don't have a session id
176             next LINE if !$line->id;
177              
178             # set the colour for the line
179             $line->colour( $self->session_colour($line->id) );
180              
181             # skip displaying the line if it should be filtered out
182             next LINE if !$line->show();
183              
184             # get the display text for the line
185             my $line_text = eval { $line->text() . join '', $line->data() };
186              
187             # check that there were no errors
188             if ($EVAL_ERROR) {
189             # warn the errors
190             warn $EVAL_ERROR;
191              
192             # go on to the next line
193             next LINE;
194             }
195              
196             # check if we are displaying lines/sessions from the end of the file
197             if ($self->{number}) {
198             # add the line to end of the lines
199             push @lines, $line_text;
200             if (@lines > 10 * $self->{number}) {
201             @lines = @lines[@lines - $self->{number} - 1 .. @lines - 1];
202             }
203             }
204             elsif ( $self->{'session-number'} ) {
205             # get the session id
206             my $session = $line->id;
207              
208             # add the session to the list of session if we have not already come accross it
209             push @lines, $session if !$sessions{$session};
210              
211             # add the line to the session's lines
212             $sessions{$session} ||= '';
213             $sessions{$session} .= $line_text;
214             }
215             else {
216             # show any file change info
217             $self->changed_file($file);
218              
219             # print out the log line
220             print $line_text;
221             }
222             }
223              
224             # check if we have any stored lines to print
225             if (@lines) {
226             # print any file change info
227             $self->changed_file($file);
228              
229             # check which format we are using
230             if ($self->{number}) {
231             my $first_line = @lines - $self->{number} <= 0 ? 0 : @lines - $self->{number};
232             print @lines[ $first_line .. (@lines - 1) ];
233             }
234             elsif ( $self->{'session-number'} ) {
235             # work out what to do
236             my $first_line = @lines - $self->{'session-number'} <= 0 ? 0 : @lines - $self->{'session-number'};
237             for my $i ( $first_line .. (@lines - 1) ) {
238             print $sessions{$lines[$i]};
239             }
240             }
241             }
242              
243             $file->reset;
244              
245             return $file->{handle};
246             }
247              
248             sub read {
249             my ($self) = @_;
250             my @lines;
251             my %sessions;
252             my $file = $self->{file};
253              
254             if (!ref $file) {
255             $file = $self->{file} = Log::Deep::File->new($file);
256             }
257              
258             my $line = $file->line;
259              
260             if ( !$line ) {
261             $file->reset;
262             return;
263             }
264              
265             chomp $line;
266             return $self->read() if !$line;
267              
268             # parse the line
269             $line = Log::Deep::Line->new( { %{ $self->{line} } }, $line, $file );
270             $line->colour( $self->session_colour($line->id) );
271              
272             # skip displaying the line if it should be filtered out
273             return $self->read if !$line->show();
274              
275             return $line;
276             }
277              
278             sub changed_file {
279             my ( $self, $file ) = @_;
280              
281             # check if we have printed some lines from this file before
282             if ( !$self->{last_print_file} || "$self->{last_print_file}" ne "$file" ) {
283             if ( $self->{file_count} > 1 ) {
284             # print out the change in file (same format as tail)
285             print "\n==> $file <==\n";
286             }
287              
288             # set this file as the last printed file
289             $self->{last_print_file} = $file;
290             }
291              
292             return;
293             }
294              
295             sub session_colour {
296             my ($self, $session_id) = @_;
297              
298             confess "No session id supplied!" if !$session_id;
299              
300             # return the cached session colour if we have one
301             return $self->{sessions}{$session_id}{colour} if $self->{sessions}{$session_id};
302              
303             # set the next colour, cycle through backgrounds for each foreground
304             if ( $self->{background} + 1 < @colours ) {
305             $self->{background}++;
306             }
307             elsif ( $self->{foreground} + 1 < @colours ) {
308             $self->{background} = 0;
309             $self->{foreground}++;
310             }
311             else {
312             $self->{background} = 0;
313             $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             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             return $self->session_colour($session_id);
324             }
325              
326             my $colour = "$colours[$self->{foreground}] on_$colours[$self->{background}]";
327              
328             # remove old sessions
329             # TODO need to get this code working
330             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             $self->{sessions}{$session_id}{time} = time;
345             $self->{sessions}{$session_id}{colour} = $colour;
346              
347             # return the colour
348             return $colour;
349             }
350              
351              
352             1;
353              
354             __END__
355              
356             for file in files
357             for line in file
358             do stuff
359              
360             '
361             for file in files
362             while line = file->next
363             do stuff
364              
365             =head1 NAME
366              
367             Log::Deep::Read - Read and prettily display log files generated by Log::Deep
368              
369             =head1 VERSION
370              
371             This documentation refers to Log::Deep::Read version 0.3.3.
372              
373             =head1 SYNOPSIS
374              
375             use Log::Deep::Read;
376              
377             # Brief but working code example(s) here showing the most common usage(s)
378             # This section will be as far as many users bother reading, so make it as
379             # educational and exemplary as possible.
380              
381             =head1 DESCRIPTION
382              
383             Provides the functionality to read and analyse log files written by Log::Deep
384              
385             =head1 SUBROUTINES/METHODS
386              
387             =head3 C<new ( %args )>
388              
389             Arg: C<mono> - bool - Display out put in mono ie don't use colour
390              
391             Arg: C<follow> - bool - Follow the log files for any new additions
392              
393             Arg: C<number> - int - The number of lines to display from the end of the log file
394              
395             Arg: C<session-number> - int - The number of sessions to display from the end of the file
396              
397             Arg: C<display> - hash ref - keys are the keys of the log's data to display
398             if a true value (or hide if false). The values can also be a comma separated
399             list (or an array reference) to turn on displaying of sub keys of the field
400             (requires the filed to be a hash)
401              
402             Arg: C<filter> - hash ref - specifies the keys to filter (not yet implemented)
403              
404             Arg: C<verbose> - bool - Turn on showing more verbose log messages.
405              
406             Arg: C<short_break> - bool - Turn on showing a short break when some time has
407             passed between displaying log lines (when follow is true)
408              
409             Arg: C<short_lines> - int - the number lines to print out when a short time
410             threshold has been exceeded.
411              
412             Arg: C<long_break> - bool - Turn on showing a short break when a longer time has
413             passed between displaying log lines (when follow is true)
414              
415             Arg: C<long_lines> - int - the number lines to print out when a longer time
416             threshold has been exceeded.
417              
418             Arg: C<sessions_max> - int - The maximum number of sessions to keep before
419             starting to remove older sessions
420              
421             Return: Log::Deep::Read - A new Log::Deep::Read object
422              
423             Description: Sets up a Log::Deep::Read object to play with.
424              
425             =head3 C<read_files ( @files )>
426              
427             Param: C<@files> - List of strings - A list of files to be read
428              
429             Description: Reads and parses all the log files specified
430              
431             =head3 C<read_file ( $file, $fh )>
432              
433             Param: C<$file> - string - The name of the file to read
434              
435             Param: C<$fh> - File Handle - A (possibly) previously open file handle to
436             $file.
437              
438             Return: File Handle - The opened file handle
439              
440             Description: Reads through the lines of $file
441              
442             =head3 C<changed_file ( $file )>
443              
444             Param: C<$file> - hash ref - The file currently being examined
445              
446             Description: Prints a message to the user that the current log file has
447             changed to a new file. The format is the same as for the tail command.
448              
449             =head3 C<read ()>
450              
451             Return: Log::Deep::Line - The next line read or undef if no more lines in file
452              
453             Description: Just parses the next line in the log file (skips blank lines and
454             lines that are filtered out)
455              
456             =head3 C<session_colour ( $session_id )>
457              
458             Params: The session id that is to be coloured
459              
460             Description: Colours session based on their ID's
461              
462             =head1 DIAGNOSTICS
463              
464             =head1 CONFIGURATION AND ENVIRONMENT
465              
466             =head1 DEPENDENCIES
467              
468             =head1 INCOMPATIBILITIES
469              
470             =head1 BUGS AND LIMITATIONS
471              
472             There are no known bugs in this module.
473              
474             Please report problems to Ivan Wills (ivan.wills@gmail.com).
475              
476             Patches are welcome.
477              
478             =head1 AUTHOR
479              
480             Ivan Wills - (ivan.wills@gmail.com)
481              
482             =head1 LICENSE AND COPYRIGHT
483              
484             Copyright (c) 2009 Ivan Wills (14 Mullion Close, Hornsby Heights, NSW 2077).
485             All rights reserved.
486              
487             This module is free software; you can redistribute it and/or modify it under
488             the same terms as Perl itself. See L<perlartistic>. This program is
489             distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
490             without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
491             PARTICULAR PURPOSE.
492              
493             =cut