File Coverage

lib/Class/Usul/Log.pm
Criterion Covered Total %
statement 69 76 90.7
branch 24 36 66.6
condition 6 12 50.0
subroutine 18 20 90.0
pod 3 3 100.0
total 120 147 81.6


line stmt bran cond sub pod time code
1             package Class::Usul::Log;
2              
3 4     7   510887 use namespace::autoclean;
  4         11  
  4         39  
4              
5 4     4   674 use Class::Usul::Constants qw( FALSE LOG_LEVELS NUL SPC TRUE );
  4         9  
  4         101  
6 4         43 use Class::Usul::Functions qw( is_hashref is_member merge_attributes
7 4     4   4029 untaint_identifier );
  4         13  
8 4         35 use Class::Usul::Types qw( Bool DataEncoding HashRef
9 4     4   6402 LoadableClass Logger SimpleStr Undef );
  4         12  
10 4     4   7597 use Encode qw( encode );
  4         10  
  4         246  
11 4     4   26 use File::Basename qw( dirname );
  4         9  
  4         208  
12 4     4   510 use File::DataClass::Types qw( Path );
  4         89907  
  4         47  
13 4     4   4689 use Scalar::Util qw( blessed );
  4         13  
  4         251  
14 4     4   29 use Sub::Install qw( install_sub );
  4         12  
  4         44  
15 4     4   545 use Moo;
  4         23  
  4         38  
16              
17             # Attribute constructors
18             my $_build__log = sub {
19 7     7   215 return $_[ 0 ]->_log_class->new( %{ $_[ 0 ]->_log_attributes } );
  7         74016  
20             };
21              
22             my $_build__log_class = sub {
23 7 50   7   216 return $_[ 0 ]->_logfile ? 'Log::Handler' : 'Class::Null';
24             };
25              
26             # Private attributes
27             has '_debug_flag' => is => 'ro', isa => Bool, default => FALSE,
28             init_arg => 'debug';
29              
30             has '_encoding' => is => 'ro', isa => DataEncoding | Undef,
31             init_arg => 'encoding';
32              
33             has '_log' => is => 'lazy', isa => Logger,
34             builder => $_build__log, init_arg => 'log';
35              
36             has '_log_attributes' => is => 'lazy', isa => HashRef,
37 2     2   52 builder => sub { {} }, init_arg => 'log_attributes';
38              
39             has '_log_class' => is => 'lazy', isa => LoadableClass, coerce => TRUE,
40             builder => $_build__log_class, init_arg => 'log_class';
41              
42             has '_logfile' => is => 'ro', isa => Path | Undef, coerce => TRUE,
43             init_arg => 'logfile';
44              
45             # Private class attributes
46             my $loggers = {};
47              
48             # Construction
49             around 'BUILDARGS' => sub {
50             my ($orig, $class, @args) = @_; my $attr = $orig->( $class, @args );
51              
52             my $builder = $attr->{builder} or return $attr;
53             my $config = $builder->can( 'config' ) ? $builder->config : {};
54             my $keys = [ qw( appclass encoding log_attributes log_class logfile ) ];
55              
56             merge_attributes $attr, $builder, [ 'debug' ];
57             merge_attributes $attr, $config, $keys;
58              
59             return $attr;
60             };
61              
62             sub BUILD {
63 8     8 1 14821 my ($self, $attr) = @_;
64              
65 8 100       50 exists $attr->{appclass} and $loggers->{ $attr->{appclass} } = $self;
66 8 100       38 exists $loggers->{default} or $loggers->{default} = $self;
67              
68 8         138 return;
69             }
70              
71             sub import {
72 2     2   286 my $class = shift;
73 2 50       16 my $params = { (is_hashref $_[ 0 ]) ? %{+ shift } : () };
  0         0  
74 2         10 my @wanted = @_;
75 2         7 my $target = caller;
76              
77 2         9 for my $wanted (grep { defined $_ } @wanted) {
  1         6  
78 1 50       7 if ($wanted eq 'get_logger') {
79 0   0     0 my $subr = $params->{as} // 'get_logger';
80              
81             install_sub { into => $target, as => $subr, code => sub ($) {
82 0     0   0 return $loggers->{ $_[ 0 ] };
83 0         0 } };
84             }
85             else {
86 1   50     9 my $subr = $params->{as} // 'log';
87              
88             install_sub { into => $target, as => $subr, code => sub (;@) {
89 7     7   11523 return $loggers->{ $wanted }->log( @_ );
90 1         11 } };
91             }
92             }
93              
94 2         142174 return;
95             }
96              
97             around '_log_attributes' => sub {
98             my ($orig, $self) = @_; my $attr = $orig->( $self );
99              
100             $self->_log_class ne 'Log::Handler' and return $attr;
101              
102             my $fattr = $attr->{file} //= {};
103             my $logfile = $self->_logfile // $fattr->{filename};
104              
105             ($logfile and -d dirname( "${logfile}" )) or return $attr;
106              
107             $fattr->{alias} = 'file-out';
108             $fattr->{filename} = "${logfile}";
109             $fattr->{maxlevel} = $self->_debug_flag ? 'debug'
110             : untaint_identifier $fattr->{maxlevel} // 'info';
111             $fattr->{mode } = untaint_identifier $fattr->{mode } // 'append';
112              
113             return $attr;
114             };
115              
116             # Private functions
117             my $add_methods = sub {
118             my ($class, @methods) = @_;
119              
120             for my $method (@methods) {
121             $class->can( $method ) or
122             install_sub { into => $class, as => $method, code => sub {
123 20 50   20   80945 my ($self, $text, $opts) = @_; $text or return FALSE;
  20         77  
124              
125 20         78 $text = ucfirst "${text}"; chomp $text; $text .= "\n";
  20         61  
  20         47  
126              
127 20 100       67 if (defined $opts) {
128 1   50     7 my $lead = ucfirst $opts->{leader} // NUL;
129             my $tag = $opts->{tag}
130 1 0 33     5 // ($opts->{user} ? $opts->{user}->username : NUL);
131              
132 1 0       5 $tag = $tag ? "[${tag}] " : $lead ? SPC : NUL;
    50          
133 1         4 $text = "${lead}${tag}${text}";
134             }
135              
136 20 50       180 $self->_encoding and $text = encode( $self->_encoding, $text );
137 20         1761 $self->_log->$method( $text );
138 20         4339 return TRUE;
139             } };
140             }
141              
142             return;
143             };
144              
145             $add_methods->( __PACKAGE__, LOG_LEVELS );
146              
147             my @arg_names = qw( level message options );
148              
149             my $inline_args = sub {
150             my $n = shift; return (map { $arg_names[ $_ ] => $_[ $_ ] } 0 .. $n - 1);
151             };
152              
153             # Public methods
154             sub filehandle {
155 0 0   0 1 0 my $self = shift; $self->_log_class ne 'Log::Handler' and return;
  0         0  
156              
157 0         0 return $self->_log->output( 'file-out' )->{fh};
158             }
159              
160             sub log {
161 7     7 1 20 my ($self, @args) = @_; my $n = 0; $n++ while (defined $args[ $n ]);
  7         15  
  7         32  
162              
163 7 100       31 my $args = ($n == 0 ) ? {}
    100          
    100          
    100          
    100          
164             : (is_hashref $args[ 0 ]) ? $args[ 0 ]
165             : ($n == 1 ) ? { $inline_args->( 2, 'info', @args ) }
166             : ($n == 2 ) ? { $inline_args->( 2, @args ) }
167             : ($n == 3 ) ? { $inline_args->( 3, @args ) }
168             : { @args };
169              
170 7         17 my $level = $args->{level}; $level and is_member $level, LOG_LEVELS
171 7 100 100     31 and return $self->$level( $args->{message}, $args->{options} );
172              
173 2         9 return FALSE;
174             }
175              
176             1;
177              
178             __END__
179              
180             =pod
181              
182             =encoding utf-8
183              
184             =head1 Name
185              
186             Class::Usul::Log - A simple flexible logging class
187              
188             =head1 Synopsis
189              
190             use Class::Usul::Log;
191              
192             my $file = [ 't', 'test.log' ];
193             my $log = Class::Usul::Log->new( encoding => 'UTF-8', logfile => $file );
194             my $text = 'Your error message goes here';
195              
196             # Can now call the following. The text will be encoded UTF-8
197             $log->debug( $text ); # Does not log as debug was not true in the constructor
198             $log->info ( $text );
199             $log->warn ( $text );
200             $log->error( $text );
201             $log->alert( $text );
202             $log->fatal( $text );
203              
204             =head1 Description
205              
206             A simple flexible logging class that supports both OO and functional interfaces
207              
208             Creates methods for each logging level that encode their output. The logging
209             levels are defined by the L<log levels|Class::Usul::Constants/LOG_LEVELS>
210             constant
211              
212             =head1 Configuration and Environment
213              
214             Defines the following attributes;
215              
216             =over 3
217              
218             =item C<appclass>
219              
220             Not an actual attribute. This value, if passed to the constructor, will be used
221             as a key to class attribute that caches instances of this class for use by
222             the functional interface
223              
224             =item C<debug>
225              
226             Debug flag defaults to false. If set to true calls to log at the debug level
227             will succeed rather than being ignored
228              
229             =item C<encoding>
230              
231             Optional output encoding. If present output to the logfile is encoded
232              
233             =item C<log>
234              
235             Optional log object. Will instantiate an instance of L<Log::Handler> if this
236             is not provided and the L</logfile> attribute is provided
237              
238             =item C<log_attributes>
239              
240             Attributes used to create the log object. Defaults to an empty hash reference
241              
242             =item C<log_class>
243              
244             The classname of the log object. This is loaded on demand and defaults to
245             L<Log::Handler> if the L</logfile> attribute is provided. If the L</logfile>
246             attribute is not provided L<Class::Null> is used instead
247              
248             =item C<logfile>
249              
250             Path to the logfile
251              
252             =back
253              
254             =head1 Subroutines/Methods
255              
256             =head2 C<BUILDARGS>
257              
258             $usul_object_ref = Class::Usul->new;
259             $log_object_ref = Class::Usul::Log->new( builder => $usul_object_ref );
260              
261             Passing an instance of L<Class::Usul> to the constructor allows it leech
262             attribute values from the C<Usul> configuration object
263              
264             =head2 C<BUILD>
265              
266             Store the new object reference in a class attribute for later importation.
267             The class attribute is a hash reference keyed by the C<appclass> attribute
268             passed to the constructor. The first logger instance created is also stored
269             keyed by C<default>
270              
271             =head2 C<import>
272              
273             use Class::Usul::Log { as => ... }, 'get_logger';
274              
275             Imports the C<get_logger> function which is called as
276              
277             my $log_object_ref = get_logger $instance_name;
278              
279             where the C<$instance_name> is either the C<appclass> attribute value passed to
280             the OO constructor or the string C<default>. The function returns an instance
281             of this class. The optional parameters allow the function to imported as a
282             different name
283              
284             use Class::Usul::Log { as => ... }, 'default';
285              
286             Imports the L</log> method from the C<default> log instance as a
287             function. Specify the C<appclass> value instead of C<default> to import from
288             that instance instead. The optional parameters allow the function to imported
289             as a different name
290              
291             =head2 C<filehandle>
292              
293             Return the loggers file handle. This was added for L<IO::Async>, so that we
294             can tell it not to close the log file handle when it forks a child process
295             and only works if the C<log_class> is L<Log::Handler>
296              
297             =head2 C<log>
298              
299             $self->log( $message );
300             $self->log( $level, $message );
301             $self->log( $level, $message, { ... } );
302             $self->log( level => $level, message => $message, options => { ... } );
303             $self->log( { level => $level, message => $message, options => { ... } } );
304              
305             Logs the message at the given level. Accepts multiple signatures
306              
307             =head1 Diagnostics
308              
309             None
310              
311             =head1 Dependencies
312              
313             =over 3
314              
315             =item L<Class::Null>
316              
317             =item L<Moo>
318              
319             =item L<Encode>
320              
321             =item L<File::DataClass>
322              
323             =item L<Log::Handler>
324              
325             =back
326              
327             =head1 Incompatibilities
328              
329             There are no known incompatibilities in this module
330              
331             =head1 Bugs and Limitations
332              
333             There are no known bugs in this module.
334             Please report problems to the address below.
335             Patches are welcome
336              
337             =head1 Acknowledgements
338              
339             Larry Wall - For the Perl programming language
340              
341             =head1 Author
342              
343             Peter Flanigan, C<< <pjfl@cpan.org> >>
344              
345             =head1 License and Copyright
346              
347             Copyright (c) 2017 Peter Flanigan. All rights reserved
348              
349             This program is free software; you can redistribute it and/or modify it
350             under the same terms as Perl itself. See L<perlartistic>
351              
352             This program is distributed in the hope that it will be useful,
353             but WITHOUT WARRANTY; without even the implied warranty of
354             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE
355              
356             =cut
357              
358             # Local Variables:
359             # mode: perl
360             # tab-width: 3
361             # End: