File Coverage

blib/lib/Mojo/Log.pm
Criterion Covered Total %
statement 86 87 98.8
branch 20 24 83.3
condition 6 8 75.0
subroutine 31 32 96.8
pod 11 11 100.0
total 154 162 95.0


line stmt bran cond sub pod time code
1             package Mojo::Log;
2 55     55   1713 use Mojo::Base 'Mojo::EventEmitter';
  55         123  
  55         414  
3              
4 55     55   425 use Carp qw(croak);
  55         132  
  55         3723  
5 55     55   379 use Fcntl qw(:flock);
  55         105  
  55         9108  
6 55     55   480 use Mojo::File;
  55         151  
  55         2933  
7 55     55   375 use Mojo::Util qw(encode);
  55         122  
  55         3285  
8 55     55   40343 use Term::ANSIColor qw(colored);
  55         630961  
  55         70428  
9 55     55   727 use Time::HiRes qw(time);
  55         179  
  55         773  
10              
11             has color => sub { $ENV{MOJO_LOG_COLOR} };
12             has format => sub { $_[0]->short ? \&_short : $_[0]->color ? \&_color : \&_default };
13             has handle => sub {
14              
15             # STDERR
16             return \*STDERR unless my $path = shift->path;
17              
18             # File
19             return Mojo::File->new($path)->open('>>');
20             };
21             has history => sub { [] };
22             has level => 'trace';
23             has max_history_size => 10;
24             has 'path';
25             has short => sub { $ENV{MOJO_LOG_SHORT} };
26              
27             # Supported log levels
28             my %LEVEL = (trace => 1, debug => 2, info => 3, warn => 4, error => 5, fatal => 6);
29              
30             # Systemd magic numbers
31             my %MAGIC = (trace => 7, debug => 6, info => 5, warn => 4, error => 3, fatal => 2);
32              
33             # Colors
34             my %COLORS = (warn => ['yellow'], error => ['red'], fatal => ['white on_red']);
35              
36             sub append {
37 53     53 1 143 my ($self, $msg) = @_;
38              
39 53 100       196 return unless my $handle = $self->handle;
40 25         110 flock $handle, LOCK_EX;
41 25 50       86 $handle->print(encode('UTF-8', $msg)) or croak "Can't write to log: $!";
42 25         599 flock $handle, LOCK_UN;
43             }
44              
45             sub capture {
46 18     18 1 67 my ($self, $level) = @_;
47              
48 18 100       392 croak 'Log messages are already being captured' if $self->{capturing}++;
49              
50 17         82 my $original = $self->level;
51 17   66     97 $self->level($level || $original);
52 17         126 my $subscribers = $self->subscribers('message');
53 17         84 $self->unsubscribe('message');
54              
55             my $capture = Mojo::Log::_Capture->new(sub {
56 17     17   94 delete $self->level($original)->unsubscribe('message')->{capturing};
57 17         120 $self->on(message => $_) for @$subscribers;
58 17         193 });
59 17         423 my $messages = $capture->{messages};
60             $self->on(
61             message => sub {
62 56     56   118 my $self = shift;
63 56         297 push @$messages, $self->format->(time, @_);
64             }
65 17         133 );
66              
67 17         57 return $capture;
68             }
69              
70             sub context {
71 981     981 1 3611 my ($self, @context) = @_;
72 981         6673 return $self->new(parent => $self, context => \@context, level => $self->level);
73             }
74              
75 47 100   47 1 294 sub debug { 2 >= $LEVEL{$_[0]->level} ? _log(@_, 'debug') : $_[0] }
76              
77 64 100   64 1 300 sub error { 5 >= $LEVEL{$_[0]->level} ? _log(@_, 'error') : $_[0] }
78 9 50   9 1 84 sub fatal { 6 >= $LEVEL{$_[0]->level} ? _log(@_, 'fatal') : $_[0] }
79 4 50   4 1 18 sub info { 3 >= $LEVEL{$_[0]->level} ? _log(@_, 'info') : $_[0] }
80              
81 30     30 1 113 sub is_level { $LEVEL{pop()} >= $LEVEL{shift->level} }
82              
83             sub new {
84 1072     1072 1 85647 my $self = shift->SUPER::new(@_);
85 1072         6863 $self->on(message => \&_message);
86 1072         14732 return $self;
87             }
88              
89 3378 100   3378 1 12671 sub trace { 1 >= $LEVEL{$_[0]->level} ? _log(@_, 'trace') : $_[0] }
90 4 50   4 1 18 sub warn { 4 >= $LEVEL{$_[0]->level} ? _log(@_, 'warn') : $_[0] }
91              
92             sub _color {
93 6     6   19 my $msg = _default(shift, my $level = shift, @_);
94 6 100       42 return $COLORS{$level} ? colored($COLORS{$level}, $msg) : $msg;
95             }
96              
97             sub _default {
98 269     269   652 my ($time, $level) = (shift, shift);
99 269         5607 my ($s, $m, $h, $day, $month, $year) = localtime $time;
100 269   100     4984 $time = sprintf '%04d-%02d-%02d %02d:%02d:%08.5f', $year + 1900, $month + 1, $day, $h, $m,
101             "$s." . ((split /\./, $time)[1] // 0);
102 269         2516 return "[$time] [$$] [$level] " . join(' ', @_) . "\n";
103             }
104              
105             sub _log {
106 973     973   2522 my ($self, $level) = (shift, pop);
107 973 100       4353 my @msgs = ref $_[0] eq 'CODE' ? $_[0]() : @_;
108 973 100       3272 unshift @msgs, @{$self->{context}} if $self->{context};
  933         3067  
109 973   66     7294 ($self->{parent} || $self)->emit('message', $level, @msgs);
110             }
111              
112             sub _message {
113 53     53   112 my ($self, $level) = (shift, shift);
114              
115 53         164 my $max = $self->max_history_size;
116 53         168 my $history = $self->history;
117 53         292 push @$history, my $msg = [time, $level, @_];
118 53         1287 shift @$history while @$history > $max;
119              
120 53         217 $self->append($self->format->(@$msg));
121             }
122              
123             sub _short {
124 10     10   24 my ($time, $level) = (shift, shift);
125 10         40 my ($magic, $short) = ("<$MAGIC{$level}>", substr($level, 0, 1));
126 10         103 return "${magic}[$$] [$short] " . join(' ', @_) . "\n";
127             }
128              
129             package Mojo::Log::_Capture;
130 55     55   141846 use Mojo::Base -base;
  55         158  
  55         647  
131             use overload
132 0     0   0 bool => sub {1},
133 7     7   827 '@{}' => sub { shift->{messages} },
134 23     23   12093 '""' => sub { join '', @{shift->{messages}} },
  23         245  
135 55     55   510 fallback => 1;
  55         263  
  55         1045  
136              
137 55     55   8066 use Mojo::Util qw(scope_guard);
  55         132  
  55         17494  
138              
139             sub new {
140 17     17   51 my ($class, $cb) = @_;
141 17         87 return $class->SUPER::new(guard => scope_guard($cb), messages => []);
142             }
143              
144             1;
145              
146             =encoding utf8
147              
148             =head1 NAME
149              
150             Mojo::Log - Simple logger
151              
152             =head1 SYNOPSIS
153              
154             use Mojo::Log;
155              
156             # Log to STDERR
157             my $log = Mojo::Log->new;
158              
159             # Customize log file location and minimum log level
160             my $log = Mojo::Log->new(path => '/var/log/mojo.log', level => 'warn');
161              
162             # Log messages
163             $log->trace('Doing stuff');
164             $log->debug('Not sure what is happening here');
165             $log->info('FYI: it happened again');
166             $log->warn('This might be a problem');
167             $log->error('Garden variety error');
168             $log->fatal('Boom');
169              
170             =head1 DESCRIPTION
171              
172             L is a simple logger for L projects.
173              
174             =head1 EVENTS
175              
176             L inherits all events from L and can emit the following new ones.
177              
178             =head2 message
179              
180             $log->on(message => sub ($log, $level, @lines) {...});
181              
182             Emitted when a new message gets logged.
183              
184             $log->on(message => sub ($log, $level, @lines) { say "$level: ", @lines });
185              
186             =head1 ATTRIBUTES
187              
188             L implements the following attributes.
189              
190             =head2 color
191              
192             my $bool = $log->color;
193             $log = $log->color($bool);
194              
195             Colorize log messages with the levels C, C and C using L, defaults to the value of
196             the C environment variables.
197              
198             =head2 format
199              
200             my $cb = $log->format;
201             $log = $log->format(sub {...});
202              
203             A callback for formatting log messages.
204              
205             $log->format(sub ($time, $level, @lines) { "[2018-11-08 14:20:13.77168] [28320] [info] I ♥ Mojolicious\n" });
206              
207             =head2 handle
208              
209             my $handle = $log->handle;
210             $log = $log->handle(IO::Handle->new);
211              
212             Log filehandle used by default L event, defaults to opening L or C.
213              
214             =head2 history
215              
216             my $history = $log->history;
217             $log = $log->history([[time, 'debug', 'That went wrong']]);
218              
219             The last few logged messages.
220              
221             =head2 level
222              
223             my $level = $log->level;
224             $log = $log->level('debug');
225              
226             Active log level, defaults to C. Available log levels are C, C, C, C, C and
227             C, in that order.
228              
229             =head2 max_history_size
230              
231             my $size = $log->max_history_size;
232             $log = $log->max_history_size(5);
233              
234             Maximum number of logged messages to store in L, defaults to C<10>.
235              
236             =head2 path
237              
238             my $path = $log->path
239             $log = $log->path('/var/log/mojo.log');
240              
241             Log file path used by L.
242              
243             =head2 short
244              
245             my $bool = $log->short;
246             $log = $log->short($bool);
247              
248             Generate short log messages without a timestamp but with journald log level prefix, suitable for systemd environments,
249             defaults to the value of the C environment variables.
250              
251             =head1 METHODS
252              
253             L inherits all methods from L and implements the following new ones.
254              
255             =head2 append
256              
257             $log->append("[2018-11-08 14:20:13.77168] [28320] [info] I ♥ Mojolicious\n");
258              
259             Append message to L.
260              
261             =head2 capture
262              
263             my $messages = $log->capture;
264             my $messages = $log->capture('debug');
265              
266             Capture log messages for as long as the returned object exists, useful for testing log messages.
267              
268             # Test your log messages
269             my $messages = $log->capture('trace');
270             $log->fatal('Something very bad happened');
271             $log->trace('Just some debug information');
272             like $messages, qr/Something very bad happened/, 'logs contain fatal message';
273             like $messages->[-1], qr/Just some debug information/, 'trace message was last';
274             undef $messages;
275              
276             =head2 context
277              
278             my $new = $log->context('[extra]', '[information]');
279              
280             Construct a new child L object that will include context information with every log message.
281              
282             # Log with context
283             my $log = Mojo::Log->new;
284             my $context = $log->context('[17a60115]');
285             $context->debug('This is a log message with context information');
286             $context->info('And another');
287              
288             =head2 debug
289              
290             $log = $log->debug('You screwed up, but that is ok');
291             $log = $log->debug('All', 'cool');
292             $log = $log->debug(sub {...});
293              
294             Emit L event and log C message.
295              
296             =head2 error
297              
298             $log = $log->error('You really screwed up this time');
299             $log = $log->error('Wow', 'seriously');
300             $log = $log->error(sub {...});
301              
302             Emit L event and log C message.
303              
304             =head2 fatal
305              
306             $log = $log->fatal('Its over...');
307             $log = $log->fatal('Bye', 'bye');
308             $log = $log->fatal(sub {...});
309              
310             Emit L event and log C message.
311              
312             =head2 info
313              
314             $log = $log->info('You are bad, but you prolly know already');
315             $log = $log->info('Ok', 'then');
316             $log = $log->info(sub {...});
317              
318             Emit L event and log C message.
319              
320             =head2 is_level
321              
322             my $bool = $log->is_level('debug');
323              
324             Check active log L.
325              
326             # True
327             $log->level('debug')->is_level('debug');
328             $log->level('debug')->is_level('info');
329              
330             # False
331             $log->level('info')->is_level('debug');
332             $log->level('fatal')->is_level('warn');
333              
334             =head2 new
335              
336             my $log = Mojo::Log->new;
337             my $log = Mojo::Log->new(level => 'warn');
338             my $log = Mojo::Log->new({level => 'warn'});
339              
340             Construct a new L object and subscribe to L event with default logger.
341              
342             =head2 trace
343              
344             $log = $log->trace('Whatever');
345             $log = $log->trace('Who', 'cares');
346             $log = $log->trace(sub {...});
347              
348             Emit L event and log C message.
349              
350             =head2 warn
351              
352             $log = $log->warn('Dont do that Dave...');
353             $log = $log->warn('No', 'really');
354             $log = $log->warn(sub {...});
355              
356             Emit L event and log C message.
357              
358             =head1 SEE ALSO
359              
360             L, L, L.
361              
362             =cut