File Coverage

blib/lib/Log/Any/Simple.pm
Criterion Covered Total %
statement 223 231 96.5
branch 72 86 83.7
condition 22 30 73.3
subroutine 74 74 100.0
pod 0 35 0.0
total 391 456 85.7


line stmt bran cond sub pod time code
1             package Log::Any::Simple;
2              
3 9     12   2439248 use strict;
  9         19  
  9         346  
4 9     9   72 use warnings;
  9         17  
  9         432  
5 9     9   49 use utf8;
  9         20  
  9         61  
6              
7 9     9   331 use Carp qw(croak cluck shortmess longmess);
  9         24  
  9         859  
8 9     9   5979 use Data::Dumper;
  9         104420  
  9         918  
9 9     9   2762 use Log::Any;
  9         51741  
  9         60  
10 9     9   606 use Log::Any::Adapter::Util 'numeric_level';
  9         37  
  9         538  
11 9     9   4856 use Log::Any::Adapter;
  9         4182  
  9         48  
12 9     9   5268 use Readonly;
  9         44201  
  9         846  
13 9     9   4939 use Sub::Util 'set_subname';
  9         3106  
  9         11032  
14              
15             our $VERSION = '0.06';
16              
17             Readonly::Scalar my $DIE_AT_DEFAULT => numeric_level('fatal');
18             Readonly::Scalar my $DIE_AT_KEY => 'Log::Any::Simple/die_at';
19             Readonly::Scalar my $CATEGORY_KEY => 'Log::Any::Simple/category';
20             Readonly::Scalar my $PREFIX_KEY => 'Log::Any::Simple/prefix';
21             Readonly::Scalar my $DUMP_KEY => 'Log::Any::Simple/dump';
22             Readonly::Scalar my $DIE_REPEATS_MSG_KEY => 'Log::Any::Simple/die_repeats_msg';
23              
24             Readonly::Array my @ALL_LOG_METHODS =>
25             (Log::Any::Adapter::Util::logging_methods(), Log::Any::Adapter::Util::logging_aliases);
26             Readonly::Hash my %ALL_LOG_METHODS => map { $_ => 1 } @ALL_LOG_METHODS;
27              
28             Readonly::Array my @DEFAULT_LOG_METHODS => qw(trace debug info warning error fatal);
29              
30             # The index of the %^H hash in the list returned by "caller".
31             Readonly::Scalar my $HINT_HASH => 10;
32              
33             # All our methods that can be imported, other than the logging methods
34             # themselves.
35             Readonly::Array my @EXPORT_OK => qw(die_with_stack_trace get_logger);
36             Readonly::Hash my %EXPORT_OK => map { $_ => 1 } @EXPORT_OK;
37              
38             sub import { ## no critic (RequireArgUnpacking, ProhibitExcessComplexity)
39 28     28   8148 my (undef) = shift @_; # This is the package being imported, so our self.
40              
41 28         72 my $calling_pkg_name = caller(0);
42 28         50 my %to_export;
43              
44 28         103 while (defined (my $arg = shift)) {
45 47 100       1521 if ($arg eq ':default') { ## no critic (ProhibitCascadingIfElse)
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
    100          
    50          
    50          
    100          
46 10         35 $to_export{$_} = 1 for @DEFAULT_LOG_METHODS;
47             } elsif ($arg eq ':all') {
48 5         23 $to_export{$_} = 1 for @ALL_LOG_METHODS;
49             } elsif (exists $ALL_LOG_METHODS{$arg}) {
50 3         39 $to_export{$arg} = 1;
51             } elsif ($arg eq ':die_at') {
52 9         83 my $level = shift;
53 9 100       24 if ($level eq 'none') {
54 1         5 $^H{$DIE_AT_KEY} = numeric_level('emergency') - 1;
55             } else {
56 8         28 my $die_at = numeric_level($level);
57 8 50       77 croak 'Invalid :die_at level' unless defined $die_at;
58 8         59 $^H{$DIE_AT_KEY} = $die_at;
59             }
60             } elsif ($arg eq ':die_repeats_msg') {
61 7         100 $^H{$DIE_REPEATS_MSG_KEY} = 1;
62             } elsif ($arg eq ':die_silence_msg') {
63 2         26 $^H{$DIE_REPEATS_MSG_KEY} = 0;
64             } elsif ($arg eq ':category') {
65 1         31 my $category = shift;
66 1 50       19 croak 'Invalid :category name' unless $category;
67 1         10 $^H{$CATEGORY_KEY} = $category;
68             } elsif ($arg eq ':prefix') {
69 2         38 my $prefix = shift;
70 2 50       7 croak 'Invalid :prefix value' unless $prefix;
71 2         18 $^H{$PREFIX_KEY} = $prefix;
72             } elsif ($arg eq ':dump_long') {
73 2         83 $^H{$DUMP_KEY} = 'long';
74             } elsif ($arg eq ':dump_short') {
75 0         0 $^H{$DUMP_KEY} = 'short';
76             } elsif (exists $EXPORT_OK{$arg}) {
77 2         43 _export_module_method($arg, $calling_pkg_name);
78             } elsif ($arg eq ':to_stderr') {
79 0         0 _activate_logging($arg, \*STDERR, shift);
80             } elsif ($arg eq ':to_stdout') {
81 0         0 _activate_logging($arg, \*STDOUT, shift);
82             } elsif ($arg eq ':from_argv') {
83 3         42 _parse_argv();
84             } else {
85 1         267 croak "Unknown parameter: $arg";
86             }
87             }
88              
89             # We export all the methods at the end, so that all the modifications to the
90             # %^H hash are already done and can be used by the _export method.
91 27 100       273 _export_logger($calling_pkg_name, \%^H) if %to_export;
92 27         148 _export_logging_method($_, $calling_pkg_name, \%^H) for keys %to_export;
93              
94 27         105 @_ = 'Log::Any';
95 27         154 goto &Log::Any::import;
96             }
97              
98             # Unimport is not documented at all, and only here to facilitate testing.
99             sub unimport { ## no critic (RequireArgUnpacking)
100 3     3   195 my (undef) = shift @_; # This is the package being imported, so our self.
101              
102 3         7 while (defined (my $arg = shift)) {
103 3 50       6 if ($arg eq ':logging') {
104 3         5 _deactivate_logging();
105             } else {
106 0         0 croak "Unknown parameter: $arg";
107             }
108             }
109              
110 3         31 return;
111             }
112              
113             # This is slightly ugly but the intent is that the user of a module using this
114             # module will set this variable to 1 to get full backtrace.
115             my $die_with_stack_trace;
116             my %die_with_stack_trace;
117              
118             sub die_with_stack_trace { ## no critic (RequireArgUnpacking)
119 6     6 0 15703 my ($category, $mode);
120 6 100       26 if (@_ == 1) {
    50          
121 3         7 ($mode) = @_;
122             } elsif (@_ == 2) {
123 3         10 ($category, $mode) = @_;
124             } else {
125 0         0 croak 'Invalid number of arguments for die_with_stack_trace(). Expecting 1 or 2, got '
126             .(scalar(@_));
127             }
128 6         26 my @valid = qw(no none short small long full);
129 6         20 my $valid_re = join('|', @valid);
130 6 50 66     126 croak "Invalid mode passed to die_with_stack_trace: ${mode}"
131             if defined $mode && $mode !~ m/^(?:${valid_re})$/;
132 6 100       16 if (defined $category) {
133 3         10 $die_with_stack_trace{$category} = $mode;
134             } else {
135 3         5 $die_with_stack_trace = $mode;
136             }
137 6         22 return;
138             }
139              
140             sub _export_logger {
141 17     17   41 my ($pkg_name, $hint_hash) = @_;
142 17         54 my $category = _get_category($pkg_name, $hint_hash);
143 17         45 my $logger = _get_logger($category, $hint_hash);
144 9     9   84 no strict 'refs'; ## no critic (ProhibitNoStrict)
  9         16  
  9         1155  
145 17         7811 *{"${pkg_name}::__log_any_simple_logger"} = \$logger;
  17         96  
146 17         36 return;
147             }
148              
149             # Export one of the methods of this module to our caller. Should only be called
150             # on methods from the @EXPORT_OK array.
151             sub _export_module_method {
152 2     2   4 my ($method, $pkg_name) = @_;
153 9     9   59 no strict 'refs'; ## no critic (ProhibitNoStrict)
  9         25  
  9         1499  
154 2         2 *{"${pkg_name}::${method}"} = \&{$method};
  2         6  
  2         6  
155 2         6 return;
156             }
157              
158             # Export one of the logging methods of Log::Any to our caller.
159             sub _export_logging_method {
160 133     133   235 my ($method, $pkg_name, $hint_hash) = @_;
161              
162 133         195 my $log_method = $method.'f';
163 133         167 my $sub;
164 133 100       198 if (_should_die($method, $hint_hash)) {
165 46         446 my $category = _get_category($pkg_name, $hint_hash);
166             $sub = sub {
167 9     9   76 no strict 'refs'; ## no critic (ProhibitNoStrict)
  9         31  
  9         1296  
168 13     13 0 168828 my $logger = ${"${pkg_name}::__log_any_simple_logger"};
  13     6 0 67  
        6 0    
        6 0    
        6 0    
        9 0    
        12 0    
        6 0    
        6 0    
        13 0    
        6 0    
        3 0    
        3 0    
        3 0    
        6 0    
        6 0    
        6 0    
        9 0    
        6 0    
        3      
169 13         88 _die($category, _get_die_msg($logger->$log_method(@_), $hint_hash));
170 46         284 };
171             } else {
172             $sub = sub {
173 9     9   58 no strict 'refs'; ## no critic (ProhibitNoStrict)
  9         50  
  9         966  
174 18     18   321591 my $logger = ${"${pkg_name}::__log_any_simple_logger"};
  18         74  
175 18         94 $logger->$log_method(@_);
176 18         418 return;
177 87         1008 };
178             }
179 9     9   86 no strict 'refs'; ## no critic (ProhibitNoStrict)
  9         66  
  9         8834  
180 133         603 *{"${pkg_name}::${method}"} = set_subname($method, $sub);
  133         554  
181 133         328 return;
182             }
183              
184             sub _get_category {
185 75     99   187 my ($pkg_name, $hint_hash) = @_;
186 75   66     339 return $hint_hash->{$CATEGORY_KEY} // $pkg_name;
187             }
188              
189             sub _get_formatter {
190 26     49   47 my ($hint_hash) = @_;
191 26 100 100     160 my $dump = ($hint_hash->{$DUMP_KEY} // 'short') eq 'short' ? \&_dump_short : \&_dump_long;
192             return sub {
193 32     53   871 my (undef, undef, $format, @args) = @_; # First two args are the category and the numeric level.
194 32         83 for (@args) {
195 15 100       58 $_ = $_->() if ref eq 'CODE';
196 15 50       42 $_ = '' unless defined;
197 15 100       41 next unless ref;
198 4         8 $_ = $dump->($_);
199             }
200 32         309 return sprintf($format, @args);
201 26         164 };
202             }
203              
204             sub _get_logger {
205 26     40   62 my ($category, $hint_hash) = @_;
206 26         63 my @args = (category => $category);
207 26 100       98 push @args, prefix => $hint_hash->{$PREFIX_KEY} if exists $hint_hash->{$PREFIX_KEY};
208 26         57 push @args, formatter => _get_formatter($hint_hash);
209 26         146 return Log::Any->get_logger(@args);
210             }
211              
212             sub _should_die {
213 142     150   245 my ($level, $hint_hash) = @_;
214 142   66     322 return numeric_level($level) <= ($hint_hash->{$DIE_AT_KEY} // $DIE_AT_DEFAULT);
215             }
216              
217             sub _get_die_msg {
218 22     30   1510 my ($msg, $hint_hash) = @_;
219 22 100 100     134 if ($hint_hash->{$DIE_REPEATS_MSG_KEY} // 1) {
220 20         71 return $msg;
221             } else {
222 2         8 return 'Fatal error, see the logs for more details';
223             }
224             }
225              
226             # This method is meant to be called only at logging time (and not at import time
227             # like the methods above)
228             sub _die {
229 16     24   44 my ($category, $msg) = @_;
230 16   100     110 my $trace = $die_with_stack_trace{$category} // $die_with_stack_trace // 'short';
      100        
231 16 100 66     119 if ($trace eq 'long' || $trace eq 'full') {
    100 66        
    50 33        
232 2         626 $msg = longmess($msg);
233             } elsif ($trace eq 'short' || $trace eq 'small') {
234 13         2081 $msg = shortmess($msg);
235             } elsif ($trace eq 'none' || $trace eq 'no') {
236 1         3 $msg .= "\n";
237             } else {
238 0         0 cluck 'Invalid $die_with_stack_trace mode. Should not happen'; # The mode is validated.
239             }
240             # The message returned by shortmess and longmess always end with a new line,
241             # so it’s fine to use die here.
242 16         191 die $msg; ## no critic (ErrorHandling::RequireCarping)
243             }
244              
245             sub _dump_short {
246 2     26   3 my ($ref) = @_; # Can be called on anything but intended to be called on ref.
247 2         3 local $Data::Dumper::Indent = 0;
248 2         4 local $Data::Dumper::Pad = ''; ## no critic (ProhibitEmptyQuotes)
249 2         3 local $Data::Dumper::Terse = 1;
250 2         2 local $Data::Dumper::Sortkeys = 1;
251 2         3 local $Data::Dumper::Sparseseen = 1;
252 2         3 local $Data::Dumper::Quotekeys = 0;
253             # Consider Useqq = 1
254 2         6 return Dumper($ref);
255             }
256              
257             sub _dump_long {
258 2     25   6 my ($ref) = @_; # Can be called on anything but intended to be called on ref.
259 2         5 local $Data::Dumper::Indent = 2;
260 2         4 local $Data::Dumper::Pad = ' ' x 4; ## no critic (ProhibitEmptyQuotes, ProhibitMagicNumbers)
261 2         5 local $Data::Dumper::Terse = 1;
262 2         3 local $Data::Dumper::Sortkeys = 1;
263 2         4 local $Data::Dumper::Sparseseen = 1;
264 2         4 local $Data::Dumper::Quotekeys = 0;
265             # Consider Useqq = 1
266 2         9 chop(my $s = Dumper($ref)); # guaranteed to end in a newline, and does not depend on $/
267 2         132 return $s;
268             }
269              
270             sub _get_singleton_logger {
271 9     40   25 my ($pkg_name, $hint_hash) = @_;
272 9         17 my $logger;
273             {
274 9     9   74 no strict 'refs'; ## no critic (ProhibitNoStrict)
  9         48  
  9         1038  
  9         16  
275 9         17 $logger = ${"${pkg_name}::__log_any_simple_logger"};
  9         67  
276             }
277 9 50       32 return $logger if defined $logger;
278 9         32 my $category = _get_category($pkg_name, $hint_hash);
279 9         29 $logger = _get_logger($category, $hint_hash);
280             {
281 9     9   54 no strict 'refs'; ## no critic (ProhibitNoStrict)
  9         38  
  9         1508  
  9         1498  
282 9         15 *{"${pkg_name}::__log_any_simple_logger"} = \$logger;
  9         66  
283             }
284 9         26 return $logger;
285             }
286              
287             # Public alias for _get_singleton_logger
288             sub get_logger {
289 0     23 0 0 my @caller = caller(0);
290 0         0 return _get_singleton_logger($caller[0], $caller[$HINT_HASH]);
291             }
292              
293             # This blocks generates in the Log::Any::Simple namespace logging methods
294             # that can be called directly by the user (although the standard approach would
295             # be to import them in the caller’s namespace). These methods are slower because
296             # They need to retrieve a logger each time.
297             for my $name (@ALL_LOG_METHODS) {
298 9     9   65 no strict 'refs'; ## no critic (ProhibitNoStrict)
  9         18  
  9         7223  
299             *{$name} = set_subname(
300             $name,
301             sub {
302 9     36 0 430661 my @caller = caller(0);
        23 0    
        39 0    
        32 0    
        24 0    
        11 0    
        11 0    
        17 0    
        33 0    
        32 0    
        22 0    
        9 0    
        9 0    
        9 0    
303 9         30 my $hint_hash = $caller[$HINT_HASH];
304 9         31 my $logger = _get_singleton_logger($caller[0], $hint_hash);
305 9         21 my $method = $name.'f';
306 9         45 my $msg = _get_die_msg($logger->$method(@_), $hint_hash);
307 9 100       29 _die(_get_category($caller[0], $hint_hash), $msg) if _should_die($name, $hint_hash);
308             });
309             }
310              
311             # The list of all Log::Any::Adapter objects set by this module.
312             my @set_adapters;
313              
314             # Creates a Log::Any::Adapter that logs to the given file descriptor ($fh)
315             # starting at the given $level_str. $cmd_arg_name is used only for debugging and
316             # is the name of the "use" statement option that triggered this call.
317             sub _activate_logging {
318 2     2   4 my ($cmd_arg_name, $fh, $level_str) = @_;
319 2         6 my $log_from = numeric_level($level_str);
320 2         13 my $numeric_debug = numeric_level('debug');
321 2 50       11 croak "Invalid ${cmd_arg_name} level" unless defined $log_from;
322             my $adapter = Log::Any::Adapter->set(
323             'Capture',
324             format => 'messages',
325             to => sub {
326 5     5   186 my ($level, $category, $text) = @_;
327 5         10 my $num_level = numeric_level($level);
328 5 100       30 return if $num_level > $log_from;
329 3 100       6 if ($num_level >= $numeric_debug) {
330 1         2 chomp($text);
331 1         6 printf $fh "%s(%s) - %s\n", (uc $level), $category, $text;
332             } else {
333 2         2 chomp($text);
334 2         48 printf $fh "%s - %s\n", (uc $level), $text;
335             }
336 2         14 });
337 2         2622 push @set_adapters, $adapter;
338 2         5 return;
339             }
340              
341             # Removes all the Log::Any::Adapter objects set by _activate_logging
342             sub _deactivate_logging {
343 3     3   25 Log::Any::Adapter->remove($_) for splice @set_adapters;
344 3         1028 return;
345             }
346              
347             # Parses @ARGV and activate logging if there is a --log argument in it.
348             sub _parse_argv {
349 3     3   7 for my $i (0 .. $#ARGV) {
350 6 50       30 last if $ARGV[$i] eq '--';
351 6 100       19 next unless $ARGV[$i] =~ m/^--?log(?:=(.*))?$/;
352 3 100 66     14 last if $i == $#ARGV && !defined $1;
353 2         2 my $cmd;
354 2 100       4 if (defined $1) {
355 1         2 $cmd = $1;
356 1         3 splice @ARGV, $i, 1;
357             } else {
358 1         2 $cmd = $ARGV[$i + 1];
359 1         17 splice @ARGV, $i, 2;
360             }
361 2         5 _activate_logging(':from_argv', \*STDERR, $cmd);
362 2         4 last;
363             }
364 3         12 return;
365             }
366              
367             1;
368              
369             __END__