File Coverage

blib/lib/Acme/Ghost/Log.pm
Criterion Covered Total %
statement 87 100 87.0
branch 25 34 73.5
condition 13 21 61.9
subroutine 22 28 78.5
pod 15 15 100.0
total 162 198 81.8


line stmt bran cond sub pod time code
1             package Acme::Ghost::Log;
2 3     3   369824 use strict;
  3         4  
  3         119  
3 3     3   16 use utf8;
  3         7  
  3         26  
4              
5             =encoding utf-8
6              
7             =head1 NAME
8              
9             Acme::Ghost::Log - Simple logger
10              
11             =head1 SYNOPSIS
12              
13             use Acme::Ghost::Log;
14              
15             my $log = Acme::Ghost::Log->new();
16             $log->error("My test error message to syslog")
17              
18             # Using file
19             my $log = Acme::Ghost::Log->new(file => '/tmp/test.log');
20             $log->error("My test error message to /tmp/test.log")
21              
22             # Customize minimum log level
23             my $log = Acme::Ghost::Log->new(level => 'warn');
24              
25             # Log messages
26             $log->trace('Doing stuff');
27             $log->debug('Not sure what is happening here');
28             $log->info('FYI: it happened again');
29             $log->warn('This might be a problem');
30             $log->error('Garden variety error');
31             $log->fatal('Boom');
32              
33             =head1 DESCRIPTION
34              
35             Acme::Ghost::Log is a simple logger for Acme::Ghost logging after daemonization
36              
37             =head2 new
38              
39             my $log = Acme::Ghost::Log->new(
40             logopt => 'ndelay,pid',
41             facility => 'user',
42             level => 'debug',
43             ident => 'test.pl',
44             );
45              
46             With default attributes
47              
48             use Mojo::Log;
49             my $log = Acme::Ghost::Log->new( logger => Mojo::Log->new );
50             $log->error("Test error message");
51              
52             This is example with external loggers
53              
54             =head1 ATTRIBUTES
55              
56             This class implements the following attributes
57              
58             =head2 facility
59              
60             This attribute sets facility for logging
61              
62             Available standard facilities: C, C, C, C, C,
63             C, C, C, C, C, C, C, C,
64             C, C, C, C, C, C and C
65              
66             Default: C (Sys::Syslog::LOG_USER)
67              
68             See also L
69              
70             =head2 file
71              
72             Log file path used by "handle"
73              
74             =head2 handle
75              
76             Log filehandle, defaults to opening "file" or uses syslog if file not specified
77              
78             =head2 ident
79              
80             The B is prepended to every message
81              
82             Default: script name C
83              
84             =head2 level
85              
86             There are six predefined log levels: C, C, C, C, C, and C (in descending priority).
87             The syslog supports followed additional log levels: C, C, C and C (in descending priority).
88             But we recommend not using them to maintain compatibility.
89             Your configured logging level has to at least match the priority of the logging message.
90              
91             If your configured logging level is C, then messages logged with info(), debug(), and trace()
92             will be suppressed; fatal(), error() and warn() will make their way through, because their
93             priority is higher or equal than the configured setting.
94              
95             Default: C
96              
97             See also L
98              
99             =head2 logger
100              
101             This attribute perfoms to set predefined logger, eg. Mojo::Log
102              
103             Default: C
104              
105             =head2 logopt
106              
107             This attribute contains zero or more of the options detailed in L
108              
109             Default: C<'ndelay,pid'>
110              
111             =head1 METHODS
112              
113             This class implements the following methods
114              
115             =head2 alert
116              
117             $log->alert('Action must be taken immediately');
118             $log->alert('Real', 'problem');
119              
120             Log C message
121              
122             =head2 crit
123              
124             $log->crit('Its over...');
125             $log->crit('Bye', 'bye');
126              
127             Log C message (See L method)
128              
129             =head2 debug
130              
131             $log->debug('You screwed up, but that is ok');
132             $log->debug('All', 'cool');
133              
134             Log C message
135              
136             =head2 emerg
137              
138             $log->emerg('System is unusable');
139             $log->emerg('To', 'die');
140              
141             Log C message
142              
143             =head2 error
144              
145             $log->error('You really screwed up this time');
146             $log->error('Wow', 'seriously');
147              
148             Log C message
149              
150             =head2 fatal
151              
152             $log->fatal('Its over...');
153             $log->fatal('Bye', 'bye');
154              
155             Log C message
156              
157             =head2 info
158              
159             $log->info('You are bad, but you prolly know already');
160             $log->info('Ok', 'then');
161              
162             Log C message
163              
164             =head2 level
165              
166             my $level = $log->level;
167             $log = $log->level('debug');
168              
169             Active log level, defaults to debug.
170             Available log levels are C, C, C, C, C, C,
171             C (C), C and C, in that order
172              
173             =head2 logger
174              
175             my $logger = $log->logger;
176              
177             This method returns the logger object or undef if not exists
178              
179             =head2 notice
180              
181             $log->notice('Normal, but significant, condition...');
182             $log->notice('Ok', 'then');
183              
184             Log C message
185              
186             =head2 provider
187              
188             print $log->provider;
189              
190             Returns provider name (C, C, C or C)
191              
192             =head2 trace
193              
194             $log->trace('Whatever');
195             $log->trace('Who', 'cares');
196              
197             Log C message
198              
199             =head2 warn
200              
201             $log->warn('Dont do that Dave...');
202             $log->warn('No', 'really');
203              
204             Log C message
205              
206             =head1 HISTORY
207              
208             See C file
209              
210             =head1 TO DO
211              
212             See C file
213              
214             =head1 SEE ALSO
215              
216             L
217              
218             =head1 AUTHOR
219              
220             Serż Minus (Sergey Lepenkov) L Eabalama@cpan.orgE
221              
222             =head1 COPYRIGHT
223              
224             Copyright (C) 1998-2026 D&D Corporation
225              
226             =head1 LICENSE
227              
228             This program is distributed under the terms of the Artistic License Version 2.0
229              
230             See the C file or L for details
231              
232             =cut
233              
234 3     3   284 use Carp qw/carp croak/;
  3         6  
  3         253  
235 3     3   18 use Scalar::Util qw/blessed/;
  3         7  
  3         170  
236 3     3   2276 use Sys::Syslog qw//;
  3         59331  
  3         142  
237 3     3   32 use File::Basename qw/basename/;
  3         7  
  3         252  
238 3     3   671 use IO::File qw//;
  3         11176  
  3         86  
239 3     3   19 use Fcntl qw/:flock/;
  3         10  
  3         518  
240 3     3   1433 use Encode qw/find_encoding/;
  3         41027  
  3         400  
241 3     3   30 use Time::HiRes qw/time/;
  3         7  
  3         33  
242              
243             use constant {
244 3         5214 LOGOPTS => 'ndelay,pid', # For Sys::Syslog
245             SEPARATOR => ' ',
246             LOGFORMAT => '%s',
247 3     3   331 };
  3         6  
248              
249             my %LOGLEVELS = (
250             'trace' => Sys::Syslog::LOG_DEBUG, # 7 debug-level message
251             'debug' => Sys::Syslog::LOG_DEBUG, # 7 debug-level message
252             'info' => Sys::Syslog::LOG_INFO, # 6 informational message
253             'notice' => Sys::Syslog::LOG_NOTICE, # 5 normal, but significant, condition
254             'warn' => Sys::Syslog::LOG_WARNING, # 4 warning conditions
255             'error' => Sys::Syslog::LOG_ERR, # 3 error conditions
256             'fatal' => Sys::Syslog::LOG_CRIT, # 2 critical conditions
257             'crit' => Sys::Syslog::LOG_CRIT, # 2 critical conditions
258             'alert' => Sys::Syslog::LOG_ALERT, # 1 action must be taken immediately
259             'emerg' => Sys::Syslog::LOG_EMERG, # 0 system is unusable
260             );
261             my %MAGIC = (
262             'trace' => 8,
263             'debug' => 7,
264             'info' => 6,
265             'notice' => 5,
266             'warn' => 4,
267             'error' => 3,
268             'fatal' => 2, 'crit' => 2,
269             'alert' => 1,
270             'emerg' => 0,
271             );
272             my %SHORT = ( # Log::Log4perl::Level notation
273             0 => 'fatal', 1 => 'fatal', 2 => 'fatal',
274             3 => 'error',
275             4 => 'warn',
276             5 => 'info', 6 => 'info',
277             7 => 'debug',
278             8 => 'trace',
279             );
280              
281             my $ENCODING = find_encoding('UTF-8') or croak qq/Encoding "UTF-8" not found/;
282              
283             sub new {
284 4     4 1 788 my $class = shift;
285 4 50       18 my $args = @_ ? @_ > 1 ? {@_} : {%{$_[0]}} : {};
  0 100       0  
286 4   50     22 $args->{facility} ||= Sys::Syslog::LOG_USER;
287 4   33     215 $args->{ident} ||= basename($0);
288 4   50     18 $args->{logopt} ||= LOGOPTS;
289 4   100     17 $args->{logger} ||= undef;
290 4   100     18 $args->{level} ||= 'debug';
291 4   100     15 $args->{file} ||= undef;
292 4   50     18 $args->{handle} ||= undef;
293 4         9 $args->{provider} = 'unknown';
294              
295             # Check level
296 4 50       53 croak "Incorrect log level specified" unless exists $MAGIC{$args->{level}};
297              
298             # Instance
299 4         39 my $self = bless {%$args}, $class;
300              
301             # Open sys log socket
302 4 100       16 if ($args->{logger}) {
    50          
    100          
303 1 50       4 croak "Blessed reference expected in logger attribute" unless blessed($args->{logger});
304 1         2 $self->{provider} = "external";
305             } elsif ($args->{handle}) {
306 0         0 $self->{provider} = "handle";
307 0         0 return $self;
308             } elsif ($args->{file}) {
309 1         3 my $file = $args->{file};
310 1         4 $self->{handle} = IO::File->new($file, ">>");
311 1 50       251 croak qq/Can't open file "$file": $!/ unless defined $self->{handle};
312 1         8 $self->{provider} = "file";
313             } else {
314 2         10 Sys::Syslog::openlog($args->{ident}, $args->{logopt}, $args->{facility});
315 2         502 $self->{provider} = "syslog";
316             }
317              
318 4         23 return $self;
319             }
320             sub level {
321 10     10 1 19 my $self = shift;
322 10 50       23 if (scalar(@_) >= 1) {
323 0         0 $self->{level} = shift;
324 0         0 return $self;
325             }
326 10         29 return $self->{level};
327             }
328 11     11 1 36 sub logger { shift->{logger} }
329 5     5 1 11 sub handle { shift->{handle} }
330 2     2 1 7 sub provider { shift->{provider} }
331              
332 0     0 1 0 sub trace { shift->_log('trace', @_) }
333 0     0 1 0 sub debug { shift->_log('debug', @_) }
334 3     3 1 515 sub info { shift->_log('info', @_) }
335 0     0 1 0 sub notice { shift->_log('notice', @_) }
336 1     1 1 320 sub warn { shift->_log('warn', @_) }
337 3     3 1 12 sub error { shift->_log('error', @_) }
338 1     1 1 2 sub fatal { shift->_log('fatal', @_) }
339 0     0 1 0 sub crit { shift->_log('crit', @_) }
340 0     0 1 0 sub alert { shift->_log('alert', @_) }
341 0     0 1 0 sub emerg { shift->_log('emerg', @_) }
342              
343             sub _log {
344 8     8   20 my ($self, $level, @msg) = @_;
345 8         18 my $req = $MAGIC{$self->level};
346 8   50     18 my $mag = $MAGIC{$level} // 7;
347 8 100       17 return 0 unless $mag <= $req;
348              
349             # Logger
350 7 100       12 if (my $logger = $self->logger) {
351 2         4 my $name = $SHORT{$mag};
352 2 50       11 if (my $code = $logger->can($name)) {
353 2         4 return $logger->$code(@msg);
354             } else {
355 0         0 carp(sprintf("Can't found '%s' method in '%s' package", $name, ref($logger)));
356             }
357 0         0 return 0;
358             }
359              
360             # Handle
361 5 100       8 if (my $handle = $self->handle) {
362 3         19 flock $handle, LOCK_EX;
363 3         9 my $tm = time;
364 3         50 my ($s, $m, $h, $day, $month, $year) = localtime $tm;
365 3   50     51 my $time = sprintf '%04d-%02d-%02d %02d:%02d:%08.5f', $year + 1900, $month + 1, $day, $h, $m,
366             "$s." . ((split /\./, $tm)[1] // 0);
367 3 50       41 $handle->print($ENCODING->encode("[$time] [$$] [$level] " . join(SEPARATOR, @msg) . "\n", 0))
368             or croak "Can't write to log: $!";
369 3         137 flock $handle, LOCK_UN;
370 3         11 return 1;
371             }
372              
373 2 50       20 return 0 if $self->provider ne "syslog";
374 2   50     5 my $lvl = $LOGLEVELS{$level} // Sys::Syslog::LOG_DEBUG;
375 2         11 Sys::Syslog::syslog($lvl, LOGFORMAT, join(SEPARATOR, @msg));
376             }
377              
378             DESTROY {
379 4     4   1341 my $self = shift;
380 4 100       29 undef $self->{handle} if $self->{file};
381 4 100       9 Sys::Syslog::closelog() unless $self->logger;
382             }
383              
384             1;
385              
386             __END__