File Coverage

blib/lib/Log/Contextual/WarnLogger/Fancy.pm
Criterion Covered Total %
statement 86 107 80.3
branch 42 66 63.6
condition 12 24 50.0
subroutine 23 24 95.8
pod 0 14 0.0
total 163 235 69.3


line stmt bran cond sub pod time code
1 8     8   78276 use 5.006; # our
  8         19  
2 8     8   30 use strict;
  8         7  
  8         145  
3 8     8   22 use warnings;
  8         12  
  8         380  
4              
5             package Log::Contextual::WarnLogger::Fancy;
6              
7             our $VERSION = '0.002000';
8              
9 8     8   27 use Carp qw( croak );
  8         9  
  8         403  
10 8     8   3013 use Term::ANSIColor qw( colored );
  8         36651  
  8         2872  
11              
12             delete $Log::Contextual::WarnLogger::Fancy::{$_}
13             for qw( croak colored ); # namespace clean
14              
15             delete $Log::Contextual::WarnLogger::Fancy::{$_}
16             for qw( _gen_level_sub _gen_is_level_sub _name_sub _can_name_sub _elipsis )
17             ; # not for external use cleaning
18              
19             BEGIN {
20             # Lazily find the best XS Sub naming implementation possible.
21             # Preferring an already loaded implementation where possible.
22             #<<< Tidy Guard
23             my $impl = ( $INC{'Sub/Util.pm'} and defined &Sub::Util::set_subname ) ? 'SU'
24             : ( $INC{'Sub/Name.pm'} and defined &Sub::Name::subname ) ? 'SN'
25             : ( eval { require Sub::Util; 1 } and defined &Sub::Util::set_subname ) ? 'SU'
26 8 50 66 8   61 : ( eval { require Sub::Name; 1 } and defined &Sub::Name::subname ) ? 'SN'
    100 33        
    50 66        
    100 33        
27             : '';
28             *_name_sub = $impl eq 'SU' ? \&Sub::Util::set_subname
29             : $impl eq 'SN' ? \&Sub::Name::subname
30 8 50       31 : sub { $_[1] };
  0 100       0  
31             #>>>
32 8 100       5850 *_can_name_sub = $impl ? sub() { 1 } : sub () { 0 };
33             }
34              
35             _gen_level($_) for (qw( trace debug info warn error fatal ));
36              
37             # Hack Notes: Custom levels are not currently recommended, but doing the following *should* work:
38             #
39             # Log::Contextual::WarnLogger::Fancy::_gen_level('custom');
40             # $logger->{levels} = [ @{ $logger->{levels}, 'custom' ];
41             # $logger->{level_nums}->{ 'custom' } = 1;
42             # $logger->{level_labels}->{ 'custom' } = 'custo';
43              
44             sub new {
45 17     17 0 7969 my ( $class, @args ) = @_;
46              
47 17 100 66     99 my $args = ( @args == 1 && ref $args[0] ? { %{ $args[0] } } : {@args} );
  15         44  
48              
49 17         31 my $self = bless {}, $class;
50              
51             $self->{env_prefix} = $args->{env_prefix}
52 17 50       96 or croak 'no env_prefix passed to ' . __PACKAGE__ . '->new';
53              
54 17         26 for my $field (qw( group_env_prefix default_upto label label_length )) {
55 68 100       153 $self->{$field} = $args->{$field} if exists $args->{$field};
56             }
57 17 100 66     63 if ( defined $self->{label} and length $self->{label} ) {
58 10 100       18 $self->{label_length} = 16 unless exists $args->{label_length};
59             $self->{effective_label} =
60 10         24 _elipsis( $self->{label}, $self->{label_length} );
61             }
62 17         40 my @levels = qw( trace debug info warn error fatal );
63 17         99 my %level_colors = (
64             trace => [],
65             debug => ['blue'],
66             info => ['white'],
67             warn => ['yellow'],
68             error => ['magenta'],
69             fatal => ['red'],
70             );
71              
72 17         33 $self->{levels} = [@levels];
73 17         29 @{ $self->{level_nums} }{@levels} = ( 0 .. $#levels );
  17         54  
74 17         69 for my $level (@levels) {
75 102         1352 $self->{level_labels}->{$level} = sprintf "%-5s", $level;
76 102 50       74 if ( @{ $level_colors{$level} || [] } ) {
  102 100       241  
77             $self->{level_labels}->{$level} =
78 85         146 colored( $level_colors{$level}, $self->{level_labels}->{$level} );
79             }
80             }
81              
82 17 100       283 unless ( exists $self->{default_upto} ) {
83 14         24 $self->{default_upto} = 'warn';
84             }
85 17         60 return $self;
86             }
87              
88             # TODO: Work out how to savely use Unicode \x{2026}, and then elipsis_width
89             # becomes 1. Otherwise utf8::encode() here after computing width might have to do.
90             my $elipsis_char = chr(166); #"\x{183}";
91             my $elipsis_width = length $elipsis_char;
92              
93             sub _elipsis {
94             my ( $text, $length ) = @_;
95             return sprintf "%" . $length . "s", $text if ( length $text ) <= $length;
96              
97             # Because the elipsis doesn't count for our calculations because its logically
98             # "in the middle". Subsequent math should be done assuming there is no elipsis.
99             my $pad_space = $length - $elipsis_width;
100             return '' if $pad_space <= 0;
101              
102             # Doing it this way handles a not entirely balanced case automatically.
103             # trimming asdfghij to length 6 with a 1 character elipis
104             # -> "....._"
105             # -> ".._..."
106             # so left gets a few less than the right here to have room for elipsis.
107             #
108             # When pad_space is even, it all works out in the end due to int truncation.
109             my $lw = int( $pad_space / 2 );
110             my $rw = $pad_space - $lw;
111              
112             return sprintf "%s%s%s", ( substr $text, 0, $lw ), $elipsis_char,
113             ( substr $text, -$rw, $rw );
114             }
115              
116             sub _log {
117 38     38   33 my $self = shift;
118 38         30 my $level = shift;
119 38         49 my $message = join( "\n", @_ );
120 38 50       84 $message .= qq[\n] unless $message =~ /\n\z/;
121 38         37 my $label = $self->{level_labels}->{$level};
122              
123 38 100       61 $label .= ' ' . $self->{effective_label} if $self->{effective_label};
124 38         277 warn "[${label}] $message";
125             }
126              
127             sub _gen_level_sub {
128             my ( $level, $is_name ) = @_;
129             return sub {
130 35     38 0 252 my $self = shift;
  3     38 0 26  
        38 0    
        38 0    
        38 0    
        38 0    
        3 0    
131 35 50       49 return unless $self->$is_name;
  3 50       4  
132 35         65 $self->_log( $level, @_ );
  3         6  
133             };
134             }
135              
136             sub _gen_is_level_sub {
137             my ($level) = @_;
138             my $ulevel = '_' . uc $level;
139              
140             return sub {
141 87     96 0 1583 my $self = shift;
  0     87 0    
        87 0    
        87 0    
        87 0    
        87 0    
        0      
142              
143             # All ENV vars are just treated as an ordered list.
144             #
145             # "env_prefix" comes first, then group_env_prefix comes second as a
146             # fallback.
147             # group_env_prefix can be an arrayref itself ordered by
148             # narrowest-to-broadest.
149              
150 87         105 my (@prefixes) = ( $self->{env_prefix} );
  0            
151 87 100       129 if ( defined $self->{group_env_prefix} ) {
  0 0          
152 18 100       28 if ( ref $self->{group_env_prefix} ) {
  0 0          
153 4         4 push @prefixes, @{ $self->{group_env_prefix} };
  4         5  
  0            
  0            
154             }
155             else {
156 14         14 push @prefixes, $self->{group_env_prefix};
  0            
157             }
158             }
159              
160             # If Any of ${PREFIX}_${LEVEL} is explicitly defined in ENV, it takes
161             # precendence over anythingthing else, returning true/false based on
162             # whether or not those values are true or false
163              
164 87         73 for my $env_var ( map { $_ . $ulevel } @prefixes ) {
  109         192  
  0            
  0            
165 105 100       229 return !!$ENV{$env_var} if defined $ENV{$env_var};
  0 0          
166             }
167              
168             # If Any of ${PREFIX}_UPTO is explicitly defined in ENV,
169             # it falls back from ${PREFIX_LEVEL} but again, the "narrowest"
170             # scope wins.
171              
172 47         36 my $upto;
  0            
173 47         38 for my $env_var ( map { $_ . '_UPTO' } @prefixes ) {
  61         74  
  0            
  0            
174 57 100       78 if ( defined $ENV{$env_var} ) {
  0 0          
175 22         21 $upto = lc $ENV{$env_var};
  0            
176             croak "Unrecognized log level '$upto' in \$ENV{$env_var}"
177 22 50       31 if not defined $self->{level_nums}->{$upto};
  0 0          
178 22         19 last;
  0            
179             }
180             }
181              
182             # If there is no UPTO in env and there's no default, then we can't be
183             # considered.
184 47 50 66     95 return 0 if not defined $upto and not defined $self->{default_upto};
  0 0 0        
185              
186             # Defaults however are considered where possible.
187 47 100       66 $upto = $self->{default_upto} if not defined $upto;
  0 0          
188              
189 47         97 return $self->{level_nums}->{$level} >= $self->{level_nums}->{$upto};
  0            
190             };
191             }
192              
193             sub _gen_level {
194 49     49   87 my ($level) = @_;
195 49         52 my $is_name = "is_$level";
196              
197 49         50 my $level_sub = _gen_level_sub( $level, $is_name );
198 49         57 my $is_level_sub = _gen_is_level_sub($level);
199              
200 49         234 _can_name_sub and _name_sub( "$level", $level_sub );
201 49         115 _can_name_sub and _name_sub( "$is_name", $is_level_sub );
202              
203 8     8   36 no strict 'refs';
  8         9  
  8         414  
204 49         37 *{$level} = $level_sub;
  49         115  
205 49         37 *{$is_name} = $is_level_sub;
  49         146  
206             }
207              
208             1;
209              
210             __END__