File Coverage

blib/lib/Dancer2/Core/Role/Logger.pm
Criterion Covered Total %
statement 60 70 85.7
branch 20 32 62.5
condition 2 11 18.1
subroutine 25 30 83.3
pod 6 8 75.0
total 113 151 74.8


line stmt bran cond sub pod time code
1             package Dancer2::Core::Role::Logger;
2             # ABSTRACT: Role for logger engines
3             $Dancer2::Core::Role::Logger::VERSION = '1.0.0';
4 142     142   114274 use Dancer2::Core::Types;
  142         602  
  142         1136  
5              
6 142     142   1860780 use Moo::Role;
  142         473  
  142         1835  
7 142     142   255083 use POSIX 'strftime';
  142         965829  
  142         947  
8 142     142   222321 use Encode ();
  142         77128  
  142         4096  
9 142     142   4319 use Data::Dumper;
  142         32276  
  142         190907  
10              
11             with 'Dancer2::Core::Role::Engine';
12              
13 102     102 0 351 sub hook_aliases { +{} }
14             sub supported_hooks {
15 35     35 0 319 qw(
16             engine.logger.before
17             engine.logger.after
18             );
19             }
20              
21 0     0   0 sub _build_type {'Logger'}
22              
23             # This is the only method to implement by logger engines.
24             # It receives the following arguments:
25             # $msg_level, $msg_content, it gets called only if the configuration allows
26             # a message of the given level to be logged.
27             requires 'log';
28              
29             has auto_encoding_charset => (
30             is => 'ro',
31             isa => Str,
32             );
33              
34             has app_name => (
35             is => 'ro',
36             isa => Str,
37             default => sub {'-'},
38             );
39              
40             has log_format => (
41             is => 'rw',
42             isa => Str,
43             default => sub {'[%a:%P] %L @%T> %m in %f l. %l'},
44             );
45              
46             my $_levels = {
47              
48             # levels < 0 are for core only
49             core => -10,
50              
51             # levels > 0 are for end-users only
52             debug => 1,
53             info => 2,
54             warn => 3,
55             warning => 3,
56             error => 4,
57             };
58              
59             has log_level => (
60             is => 'rw',
61             isa => Enum[keys %{$_levels}],
62             default => sub {'debug'},
63             );
64              
65             sub _should {
66 2367     2367   4659 my ( $self, $msg_level ) = @_;
67 2367         38657 my $conf_level = $self->log_level;
68 2367         28388 return $_levels->{$conf_level} <= $_levels->{$msg_level};
69             }
70              
71             sub format_message {
72 52     52 1 2293 my ( $self, $level, $message ) = @_;
73 52         155 chomp $message;
74              
75 52 50       251 $message = Encode::encode( $self->auto_encoding_charset, $message )
76             if $self->auto_encoding_charset;
77              
78 52         576 my @stack = caller(8);
79 52         204 my $request = $self->request;
80 52         165 my $config = $self->config;
81              
82             my $block_handler = sub {
83 4     4   16 my ( $block, $type ) = @_;
84 4 50       23 if ( $type eq 't' ) {
    50          
85 0         0 return POSIX::strftime( $block, localtime(time) );
86             }
87             elsif ( $type eq 'h' ) {
88 4   50     27 return ( $request && $request->header($block) ) || '-';
89             }
90             else {
91 0         0 Carp::carp("{$block}$type not supported");
92 0         0 return "-";
93             }
94 52         357 };
95              
96             my $chars_mapping = {
97 48     48   392 a => sub { $self->app_name },
98 0     0   0 t => sub { POSIX::strftime( "%d/%b/%Y %H:%M:%S", localtime(time) ) },
99 48     48   3308 T => sub { POSIX::strftime( "%Y-%m-%d %H:%M:%S", localtime(time) ) },
100 0     0   0 u => sub { POSIX::strftime( "%d/%b/%Y %H:%M:%S", gmtime(time) ) },
101 0     0   0 U => sub { POSIX::strftime( "%Y-%m-%d %H:%M:%S", gmtime(time) ) },
102 48     48   337 P => sub {$$},
103 48     48   204 L => sub {$level},
104 48     48   263 m => sub {$message},
105 48 100   48   379 f => sub { $stack[1] || '-' },
106 48 100   48   351 l => sub { $stack[2] || '-' },
107             h => sub {
108 0 0 0 0   0 ( $request && ( $request->remote_host || $request->address ) ) || '-'
      0        
109             },
110 4 50 33 4   20 i => sub { ( $request && $request->id ) || '-' },
111 52         1404 };
112              
113             my $char_mapping = sub {
114 340     340   617 my $char = shift;
115              
116 340         611 my $cb = $chars_mapping->{$char};
117 340 50       674 if ( !$cb ) {
118 0         0 Carp::carp "%$char not supported.";
119 0         0 return "-";
120             }
121 340         691 $cb->($char);
122 52         255 };
123              
124 52         1193 my $fmt = $self->log_format;
125              
126 52         861 $fmt =~ s/
127             (?:
128             \%\{(.+?)\}([a-z])|
129             \%([a-zA-Z])
130             )
131 344 100       1938 / $1 ? $block_handler->($1, $2) : $char_mapping->($3) /egx;
132              
133 52         2207 return $fmt . "\n";
134             }
135              
136             sub _serialize {
137 46     46   161 my @vars = @_;
138              
139 46 50       1220 return join q{}, map +(
    50          
140             ref $_
141             ? Data::Dumper->new( [$_] )->Terse(1)->Purity(1)->Indent(0)
142             ->Sortkeys(1)->Dump()
143             : ( defined($_) ? $_ : 'undef' )
144             ), @vars;
145             }
146              
147             around 'log' => sub {
148             my ($orig, $self, @args) = @_;
149              
150             $self->execute_hook( 'engine.logger.before', $self, @args );
151             $self->$orig( @args );
152             $self->execute_hook( 'engine.logger.after', $self, @args );
153             };
154              
155             sub core {
156 2317     2317 1 7643 my ( $self, @args ) = @_;
157 2317 100       6110 $self->_should('core') and $self->log( 'core', _serialize(@args) );
158             }
159              
160             sub debug {
161 12     12 1 3684 my ( $self, @args ) = @_;
162 12 100       36 $self->_should('debug') and $self->log( 'debug', _serialize(@args) );
163             }
164              
165             sub info {
166 2     2 1 1746 my ( $self, @args ) = @_;
167 2 50       10 $self->_should('info') and $self->log( 'info', _serialize(@args) );
168             }
169              
170             sub warning {
171 9     9 1 1739 my ( $self, @args ) = @_;
172 9 50       53 $self->_should('warning') and $self->log( 'warning', _serialize(@args) );
173             }
174              
175             sub error {
176 27     27 1 1880 my ( $self, @args ) = @_;
177 27 50       131 $self->_should('error') and $self->log( 'error', _serialize(@args) );
178             }
179              
180             1;
181              
182             __END__
183              
184             =pod
185              
186             =encoding UTF-8
187              
188             =head1 NAME
189              
190             Dancer2::Core::Role::Logger - Role for logger engines
191              
192             =head1 VERSION
193              
194             version 1.0.0
195              
196             =head1 DESCRIPTION
197              
198             Any class that consumes this role will be able to implement to write log messages.
199              
200             In order to implement this role, the consumer B<must> implement the C<log>
201             method. This method will receives as argument the C<level> and the C<message>.
202              
203             =head1 ATTRIBUTES
204              
205             =head2 auto_encoding_charset
206              
207             Charset to use when writing a message.
208              
209             =head2 app_name
210              
211             Name of the application. Can be used in the message.
212              
213             =head2 log_format
214              
215             This is a format string (or a preset name) to specify the log format.
216              
217             The possible values are:
218              
219             =over 4
220              
221             =item %h
222              
223             host emitting the request
224              
225             =item %t
226              
227             date (local timezone, formatted like %d/%b/%Y %H:%M:%S)
228              
229             =item %T
230              
231             date (local timezone, formatted like %Y-%m-%d %H:%M:%S)
232              
233             =item %u
234              
235             date (UTC timezone, formatted like %d/%b/%Y %H:%M:%S)
236              
237             =item %U
238              
239             date (UTC timezone, formatted like %Y-%m-%d %H:%M:%S)
240              
241             =item %P
242              
243             PID
244              
245             =item %L
246              
247             log level
248              
249             =item %D
250              
251             timer
252              
253             =item %m
254              
255             message
256              
257             =item %f
258              
259             file name that emit the message
260              
261             =item %l
262              
263             line from the file
264              
265             =item %i
266              
267             request ID
268              
269             =item %{$fmt}t
270              
271             timer formatted with a valid time format
272              
273             =item %{header}h
274              
275             header value
276              
277             =back
278              
279             =head2 log_level
280              
281             Level to use by default.
282              
283             =head1 METHODS
284              
285             =head2 core
286              
287             Log messages as B<core>.
288              
289             =head2 debug
290              
291             Log messages as B<debug>.
292              
293             =head2 info
294              
295             Log messages as B<info>.
296              
297             =head2 warning
298              
299             Log messages as B<warning>.
300              
301             =head2 error
302              
303             Log messages as B<error>.
304              
305             =head2 format_message
306              
307             Provides a common message formatting.
308              
309             =head1 CONFIGURATION
310              
311             The B<logger> configuration variable tells Dancer2 which engine to use.
312              
313             You can change it either in your config.yml file:
314              
315             # logging to console
316             logger: "console"
317              
318             The log format can also be configured,
319             please see L<Dancer2::Core::Role::Logger/"log_format"> for details.
320              
321             =head1 AUTHOR
322              
323             Dancer Core Developers
324              
325             =head1 COPYRIGHT AND LICENSE
326              
327             This software is copyright (c) 2023 by Alexis Sukrieh.
328              
329             This is free software; you can redistribute it and/or modify it under
330             the same terms as the Perl 5 programming language system itself.
331              
332             =cut