File Coverage

blib/lib/Net/CLI/Interact/Logger.pm
Criterion Covered Total %
statement 36 56 64.2
branch 7 22 31.8
condition 1 3 33.3
subroutine 12 13 92.3
pod 2 3 66.6
total 58 97 59.7


line stmt bran cond sub pod time code
1             package Net::CLI::Interact::Logger;
2             $Net::CLI::Interact::Logger::VERSION = '2.400002';
3 1     1   7 use Moo;
  1         2  
  1         6  
4 1     1   286 use Sub::Quote;
  1         2  
  1         55  
5 1     1   6 use MooX::Types::MooseLike::Base qw(HashRef Bool ArrayRef Any);
  1         2  
  1         77  
6              
7 1     1   449 use Class::Mix qw(genpkg);
  1         4691  
  1         62  
8 1     1   7 use Time::HiRes qw(gettimeofday tv_interval);
  1         2  
  1         8  
9 1     1   605 use Log::Dispatch::Config; # loads Log::Dispatch
  1         249389  
  1         53  
10 1     1   476 use Log::Dispatch::Configurator::Any;
  1         10972  
  1         197  
11              
12             sub BUILDARGS {
13 2     2 0 3450 my ($class, @args) = @_;
14              
15             # accept single hash ref or naked hash
16 2 50       11 my $params = (ref {} eq ref $args[0] ? $args[0] : {@args});
17              
18             # back-compat for old attr name
19 2 50       11 $params->{log_stamp} = $params->{log_stamps} if exists $params->{log_stamps};
20              
21 2         35 return $params;
22             }
23              
24             has log_config => (
25             is => 'rw',
26             isa => HashRef,
27             builder => 1,
28             trigger => quote_sub(q{ $_[0]->_clear_logger }),
29             );
30              
31             sub _build_log_config {
32             return {
33 2     2   166 dispatchers => ['screen'],
34             screen => {
35             class => 'Log::Dispatch::Screen',
36             min_level => 'debug',
37             },
38             };
39             }
40              
41             has _logger => (
42             is => 'ro',
43             isa => quote_sub(q{ $_[0]->isa('Log::Dispatch::Config') }),
44             builder => 1,
45             lazy => 1,
46             clearer => 1,
47             );
48              
49             # this allows each instance of this module to have its own
50             # wrapped logger with different configuration.
51             sub _build__logger {
52 0     0   0 my $self = shift;
53              
54 0         0 my $anon_logger = genpkg();
55             {
56             ## no critic (ProhibitNoStrict)
57 1     1   8 no strict 'refs';
  1         2  
  1         592  
  0         0  
58 0         0 @{"$anon_logger\::ISA"} = 'Log::Dispatch::Config';
  0         0  
59             ## use critic
60             }
61              
62 0         0 my $config = Log::Dispatch::Configurator::Any->new($self->log_config);
63 0         0 $anon_logger->configure($config);
64              
65 0         0 return $anon_logger->instance;
66             }
67              
68             has 'log_stamp' => (
69             is => 'rw',
70             isa => Bool,
71             default => quote_sub('1'),
72             );
73              
74             has 'log_category' => (
75             is => 'rw',
76             isa => Bool,
77             default => quote_sub('1'),
78             );
79              
80             has 'log_start' => (
81             is => 'ro',
82             isa => ArrayRef,
83             default => sub{ [gettimeofday] },
84             );
85              
86             has 'log_flags' => (
87             is => 'rw',
88             isa => Any, # FIXME 'ArrayRef|HashRef[Str]',
89             default => sub { {} },
90             );
91              
92             my %code_for = (
93             debug => 0,
94             info => 1,
95             notice => 2,
96             warning => 3,
97             error => 4,
98             critical => 5,
99             alert => 6,
100             emergency => 7,
101             );
102              
103             sub would_log {
104 2     2 1 4 my ($self, $category, $level) = @_;
105 2 50 33     13 return 0 if !defined $category or !defined $level;
106              
107             my $flags = (ref $self->log_flags eq ref []
108 2 50       39 ? { map {$_ => 'error'} @{$self->log_flags} }
  0         0  
  0         0  
109             : $self->log_flags
110             );
111              
112 2 50       57 return 0 if !exists $code_for{$level};
113 2 50       18 return 0 if !exists $flags->{$category};
114 0         0 return ($code_for{$level} >= $code_for{ $flags->{$category} });
115             }
116              
117             sub log {
118 2     2 1 31 my ($self, $category, $level, @msgs) = @_;
119 2 50       7 return unless $self->would_log($category, $level);
120 0           @msgs = grep {defined} @msgs;
  0            
121 0 0         return unless scalar @msgs;
122              
123 0           my $prefix = '';
124 0 0         $prefix .= sprintf "[%11s] ", sprintf "%.6f", (tv_interval $self->log_start, [gettimeofday])
125             if $self->log_stamp;
126 0 0         $prefix .= (substr $category, 0, 2)
127             if $self->log_category;
128              
129 0           my $suffix = '';
130 0 0         $suffix = "\n" if $msgs[-1] !~ m/\n$/;
131              
132 0           $self->_logger->$level($prefix . (' ' x (2 - $code_for{$level})), (join ' ', @msgs) . $suffix);
133             }
134              
135             1;
136              
137             =pod
138              
139             =for Pod::Coverage BUILDARGS
140              
141             =for test_synopsis
142             my ($logger, $category, $level, @message);
143              
144             =head1 NAME
145              
146             Net::CLI::Interact::Logger - Per-instance multi-target logging, with categories
147              
148             =head1 SYNOPSIS
149              
150             $logger->log($category, $level, @message);
151              
152             =head1 DESCRIPTION
153              
154             This module implements a generic logging service, based on L<Log::Dispatch>
155             but with additional options and configuration. Log messages coming from your
156             application are categorized, and each category can be enabled/disabled
157             separately and have its own log level (i.e. C<emergency> .. C<debug>). High
158             resolution timestamps can be added to log messages.
159              
160             =head1 DEFAULT CONFIGURATION
161              
162             Being based on L<Log::Dispatch::Config>, this logger can have multiple
163             targets, each configured for independent level thresholds. The overall default
164             configuration is to print log messages to the screen (console), with a minimum
165             level of C<debug>. Each category (see below) has its own log level as well.
166              
167             Note that categories, as discussed below, are arbitrary so if a category is
168             not explicitly enabled or disabled, it is assumed to be B<disabled>. If you
169             wish to invent a new category for your application, simply think of the name
170             and begin to use it, with a C<$level> and C<@message> as above in the
171             SYNOPSIS.
172              
173             =head1 INTERFACE
174              
175             =head2 log( $category, $level, @message )
176              
177             The combination of category and level determine whether the the log messages
178             are emitted to any of the log destinations. Destinations are set using the
179             C<log_config> method, and categories are configured using the C<log_flags>
180             method.
181              
182             The C<@message> list will be joined by a space character, and a newline
183             appended if the last message doesn't contain one itself. Messages are
184             prepended with the first character of their C<$category>, and then indented
185             proportionally to their C<$level>.
186              
187             =head2 log_config( \%config )
188              
189             A C<Log::Dispatch::Config> configuration (hash ref), meaning multiple log
190             targets may be specified with different minimum level thresholds. There is a
191             default configuration which emits messages to your screen (console) with no
192             minimum threshold:
193              
194             {
195             dispatchers => ['screen'],
196             screen => {
197             class => 'Log::Dispatch::Screen',
198             min_level => 'debug',
199             },
200             };
201              
202             =head2 log_flags( \@categories | \%category_level_map )
203              
204             The user is expected to specify which log categories they are interested in,
205             and at what levels. If a category is used in the application for logging but
206             not specified, then it is deemed B<disabled>. Hence, even though the default
207             destination log level is C<debug>, no messages are emitted until a category is
208             enabled.
209              
210             In the array reference form, the list should contain category names, and they
211             will all be mapped to the C<error> level:
212              
213             $logger->log_flags([qw/
214             network
215             disk
216             io
217             cpu
218             /]);
219              
220             In the hash reference form, the keys should be category names and the values
221             log levels from the list below (ordered such that each level "includes" the
222             levels I<above>):
223              
224             emergency
225             alert
226             critical
227             error
228             warning
229             notice
230             info
231             debug
232              
233             For example:
234              
235             $logger->log_flags({
236             network => 'info',
237             disk => 'debug',
238             io => 'critical',
239             cpu => 'debug',
240             });
241              
242             Messages at or above the specified level will be passed on to the
243             C<Log::Dispatch> target, which may then specify an overriding threshold.
244              
245             =head2 C< Net::CLI::Interact->default_log_categories() >>
246              
247             Not a part of this class, but the only way to retrieve a list of the current
248             log categories used in the L<Net::CLI::Interact> distribution source. Does not
249             take into account any log categories added by the user.
250              
251             =head2 log_stamp( $boolean )
252              
253             Enable (the default) or disable the display of high resolution interval
254             timestamps with each log message.
255              
256             =head2 log_category( $boolean )
257              
258             Enable (the default) or disable the display of the first letters of the
259             category name with each log message.
260              
261             =head2 log_start( [$seconds, $microseconds] )
262              
263             Time of the start for generating a time interval when logging stamps. Defaults
264             to the result of C<Time::HiRes::gettimeofday> at the point the module is
265             loaded, in list context.
266              
267             =head2 would_log( $category, $level )
268              
269             Returns True if, according to the current C<log_flags>, the given C<$category>
270             is enabled at or above the threshold of C<$level>, otherwise returns False.
271             Note that the C<Log::Dispatch> targets maintain their own thresholds as well.
272              
273             =cut
274