File Coverage

blib/lib/CTK/Log.pm
Criterion Covered Total %
statement 102 154 66.2
branch 24 54 44.4
condition 18 64 28.1
subroutine 21 43 48.8
pod 19 19 100.0
total 184 334 55.0


line stmt bran cond sub pod time code
1             package CTK::Log; # $Id: Log.pm 276 2020-03-22 16:53:04Z minus $
2 3     3   70753 use strict;
  3         14  
  3         90  
3 3     3   16 use utf8;
  3         4  
  3         16  
4              
5             =encoding utf-8
6              
7             =head1 NAME
8              
9             CTK::Log - CTK Logging
10              
11             =head1 VERSION
12              
13             Version 2.63
14              
15             =head1 SYNOPSIS
16              
17             use CTK::Log;
18             use CTK::Log qw/:constants/;
19              
20             my $logger = new CTK::Logger (
21             file => "logs/foo.log",
22             level => CTK::Log::LOG_INFO,
23             ident => "ident string",
24             );
25              
26             $logger->log( CTK::Log::LOG_INFO, " ... Blah-Blah-Blah ... " );
27              
28             $logger->log_except( "log message" ); # 9 exception
29             $logger->log_fatal( "log message" ); # 8 fatal
30             $logger->log_emerg( "log message" ); # 7 system is unusable
31             $logger->log_alert( "log message" ); # 6 action must be taken immediately
32             $logger->log_crit( "log message" ); # 5 critical conditions
33             $logger->log_error( "log message" ); # 4 error conditions
34             $logger->log_warning( "log message" ); # 3 warning conditions
35             $logger->log_notice( "log message" ); # 2 normal but significant condition
36             $logger->log_info( "log message" ); # 1 informational
37             $logger->log_debug( "log message" ); # 0 debug-level messages (default)
38              
39             =head1 DESCRIPTION
40              
41             Logger class
42              
43             =head1 METHODS
44              
45             =head2 new
46              
47             my $logger = new CTK::Log (
48             file => "logs/foo.log",
49             level => "info", # or CTK::Log::LOG_INFO
50             ident => "ident string",
51             );
52              
53             Returns logger object for logging to file
54              
55             my $logger = new CTK::Log (
56             level => "info", # or CTK::Log::LOG_INFO
57             ident => "ident string",
58             );
59              
60             Returns logger object for logging to syslog
61              
62             =over 8
63              
64             =item B
65              
66             The part of the system to report about, for example C. See L
67              
68             Default: C
69              
70             =item B
71              
72             Specifies log file. If not specify, then will be used syslog
73              
74             Default: undef
75              
76             =item B
77              
78             Specifies ident string for each log-record
79              
80             ident = "test"
81              
82             [Mon Apr 29 20:02:04 2019] [info] [7936] [test] Blah Blah Blah
83              
84             ident = undef
85              
86             [Mon Apr 29 20:02:04 2019] [info] [7936] Blah Blah Blah
87              
88             Default: undef
89              
90             =item B
91              
92             This directive specifies the minimum possible priority level. You can use:
93              
94             constants:
95              
96             LOG_DEBUG
97             LOG_INFO
98             LOG_NOTICE or LOG_NOTE
99             LOG_WARNING or LOG_WARN
100             LOG_ERR or LOG_ERROR
101             LOG_CRIT
102             LOG_ALERT
103             LOG_EMERG or LOG_EMERGENCY
104             LOG_FATAL
105             LOG_EXCEPT or LOG_EXCEPTION
106              
107             ...or strings:
108              
109             'debug'
110             'info'
111             'notice' or 'note'
112             'warning' or 'warn'
113             'error' or 'err'
114             'crit'
115             'alert'
116             'emerg' or 'emergency'
117             'fatal'
118             'except' or 'exception'
119              
120             Default: C
121              
122             =item B
123              
124             Specifies flag for suppressing prefixes log-data
125              
126             ident = "test"
127             pure = 0
128              
129             [Mon Apr 29 19:12:55 2019] [crit] [7480] [test] Blah-Blah-Blah
130              
131             ident = "test"
132             pure = 1
133              
134             [test] Blah-Blah-Blah
135              
136             ident = undef
137             pure = 1
138              
139             Blah-Blah-Blah
140              
141             Default: 0
142              
143             =item B
144              
145             Separator of log-record elements
146              
147             separator = " "
148              
149             [Mon Apr 29 20:02:04 2019] [info] [7936] [test] Blah Blah Blah
150              
151             separator = ","
152              
153             [Mon Apr 29 20:02:04 2019],[info],[7936],[test],Blah Blah Blah
154              
155             Default: C<" ">
156              
157             =item B
158              
159             Socket optrions for L
160              
161             Allowed formats, examples:
162              
163             socketopts => "unix"
164             socketopts => ["unix"]
165             socketopts => { type => "tcp", port => 2486 }
166              
167             Default: C
168              
169             =item B
170              
171             Options of L
172              
173             Default: C
174              
175             =item B
176              
177             Sets to 1 for send data to syslog forced
178              
179             Default: 0
180              
181             =item B
182              
183             Sets flag utf8 for logging data. The flag is enabled by default
184              
185             Default: 1
186              
187             =back
188              
189             =head2 error
190              
191             my $error = $logger->error;
192              
193             Returns error string if occurred any errors while creating the object
194              
195             =head2 status
196              
197             print $logger->error unless $logger->status;
198              
199             Returns boolean status of object creating
200              
201             =head1 LOG METHODS
202              
203             =head2 log
204              
205             $logger->log( , , , ... );
206             $logger->log( LOG_INFO, "Message: Blah-Blah-Blah" );
207             $logger->log( LOG_INFO, "Message: %s", "Blah-Blah-Blah" );
208             $logger->log( "info", "Message: Blah-Blah-Blah" );
209              
210             Logging with info level (1). Same as log_info( "Message: %s", "Blah-Blah-Blah" )
211              
212             =head2 log_debug
213              
214             $logger->log_debug( , , ... );
215             $logger->log_debug( "Blah-Blah-Blah" );
216              
217             Level 0: debug-level messages (default)
218              
219             =head2 log_info
220              
221             $logger->log_info( , , ... );
222             $logger->log_info( "Blah-Blah-Blah" );
223              
224             Level 1: informational
225              
226             =head2 log_notice, log_note
227              
228             $logger->log_notice( , , ... );
229             $logger->log_notice( "Blah-Blah-Blah" );
230              
231             Level 2: normal but significant condition
232              
233             =head2 log_warning, log_warn
234              
235             $logger->log_warning( , , ... );
236             $logger->log_warning( "Blah-Blah-Blah" );
237              
238             Level 3: warning conditions
239              
240             =head2 log_error, log_err
241              
242             $logger->log_error( , , ... );
243             $logger->log_error( "Blah-Blah-Blah" );
244              
245             Level 4: error conditions
246              
247             =head2 log_crit
248              
249             $logger->log_crit( , , ... );
250             $logger->log_crit( "Blah-Blah-Blah" );
251              
252             Level 5: critical conditions
253              
254             =head2 log_alert
255              
256             $logger->log_alert( , , ... );
257             $logger->log_alert( "Blah-Blah-Blah" );
258              
259             Level 6: action must be taken immediately
260              
261             =head2 log_emerg, log_emergency
262              
263             $logger->log_emerg( , , ... );
264             $logger->log_emerg( "Blah-Blah-Blah" );
265              
266             Level 7: system is unusable
267              
268             =head2 log_fatal
269              
270             $logger->log_fatal( , , ... );
271             $logger->log_fatal( "Blah-Blah-Blah" );
272              
273             Level 8: fatal
274              
275             =head2 log_except,log_exception
276              
277             $logger->log_except( , , ... );
278             $logger->log_except( "Blah-Blah-Blah" );
279              
280             Level 9: exception
281              
282             =head1 HISTORY
283              
284             See C file
285              
286             =head1 DEPENDENCIES
287              
288             L, L
289              
290             =head1 TO DO
291              
292             See C file
293              
294             =head1 BUGS
295              
296             * none noted
297              
298             =head1 SEE ALSO
299              
300             L, L
301              
302             =head1 AUTHOR
303              
304             Serż Minus (Sergey Lepenkov) L Eabalama@cpan.orgE
305              
306             =head1 COPYRIGHT
307              
308             Copyright (C) 1998-2019 D&D Corporation. All Rights Reserved
309              
310             =head1 LICENSE
311              
312             This program is free software; you can redistribute it and/or
313             modify it under the same terms as Perl itself.
314              
315             See C file and L
316              
317             =cut
318              
319              
320 3     3   226 use vars qw/$VERSION %EXPORT_TAGS @EXPORT_OK/;
  3         6  
  3         231  
321             $VERSION = '2.63';
322              
323 3     3   21 use base qw/Exporter/;
  3         6  
  3         409  
324              
325 3     3   33 use Carp;
  3         6  
  3         194  
326 3     3   542 use IO::File;
  3         8662  
  3         450  
327 3     3   1965 use Sys::Syslog ();
  3         38220  
  3         95  
328 3     3   1638 use Try::Tiny;
  3         6348  
  3         179  
329 3     3   22 use Cwd qw/getcwd/;
  3         8  
  3         117  
330 3     3   18 use File::Spec ();
  3         7  
  3         488  
331              
332             @EXPORT_OK = qw(
333             LOG_DEBUG LOG_INFO LOG_NOTICE LOG_NOTE LOG_WARNING LOG_WARN LOG_ERR
334             LOG_ERROR LOG_CRIT LOG_ALERT LOG_EMERG LOG_EMERGENCY LOG_FATAL
335             LOG_EXCEPT LOG_EXCEPTION
336             );
337              
338             %EXPORT_TAGS = (
339             constants => [@EXPORT_OK],
340             );
341              
342             use constant {
343 3 50       6303 LOGOPT => 'ndelay,pid', # For Sys::Syslog
344             MSWIN => $^O =~ /mswin/i ? 1 : 0,
345             SEPARATOR => ' ',
346             LOGLEVELSA => [qw/debug info notice warning error crit alert emerg fatal except/],
347             LOGLEVELS => {
348             'debug' => 0,
349             'info' => 1,
350             'notice' => 2, 'note' => -2,
351             'warning' => 3, 'warn' => -3,
352             'error' => 4, 'err' => -4,
353             'crit' => 5,
354             'alert' => 6,
355             'emerg' => 7, 'emergency' => -7,
356             'fatal' => 8,
357             'except' => 9, 'exception' => -9,
358             },
359             LOG_DEBUG => 0,
360             LOG_INFO => 1,
361             LOG_NOTICE => 2, LOG_NOTE => 2,
362             LOG_WARNING => 3, LOG_WARN => 3,
363             LOG_ERR => 4, LOG_ERROR => 4,
364             LOG_CRIT => 5,
365             LOG_ALERT => 6,
366             LOG_EMERG => 7, LOG_EMERGENCY => 7,
367             LOG_FATAL => 8,
368             LOG_EXCEPT => 9, LOG_EXCEPTION => 9,
369 3     3   22 };
  3         6  
370              
371             my %SYSLOG_LEVEL_MAP = (
372             # My LEVEL , SysLog LEVEL
373             LOG_DEBUG , LOG_EMERG,
374             LOG_INFO , LOG_ALERT,
375             LOG_NOTICE , LOG_CRIT,
376             LOG_WARNING , LOG_ERR,
377             LOG_ERR , LOG_WARNING,
378             LOG_CRIT , LOG_NOTICE,
379             LOG_ALERT , LOG_INFO,
380             LOG_EMERG , LOG_DEBUG,
381             LOG_FATAL , LOG_DEBUG,
382             LOG_EXCEPT , LOG_DEBUG,
383             );
384              
385             sub new {
386 1     1 1 173 my $class = shift;
387 1         6 my %args = @_;
388 1   50     8 my $level = _getLevel($args{level} // LOG_DEBUG);
389 1 50       3 carp(sprintf("Incorrect level %s", $args{level})) unless defined $level;
390 1   50     6 my $usesyslog = $args{usesyslog} || 0;
391 1   50     6 my $syslogopts = $args{syslogopts} // LOGOPT;
392 1         2 my $socketopts = $args{socketopts};
393 1   50     5 my $facility = $args{facility} || Sys::Syslog::LOG_USER;
394 1         2 my $file = $args{file};
395 1 50 33     6 $usesyslog = 1 unless defined($file) && length($file);
396 1 50 33     48 $file = File::Spec->catfile(getcwd(), $file)
397             if $file && !File::Spec->file_name_is_absolute($file);
398              
399             # Create object
400             my $self = bless {
401             status => 0,
402             error => "",
403             usesyslog => $usesyslog,
404             file => $file,
405             level => $level || LOG_DEBUG,
406             ident => $args{ident},
407             syslogopts => $syslogopts,
408             socketopts => $socketopts,
409             facility => $facility,
410             separator => $args{separator} // SEPARATOR,
411             "utf8" => $args{"utf8"} // 1,
412 1   50     25 pure => $args{pure} // 0,
      50        
      50        
      50        
413             fh => undef,
414             }, $class;
415              
416 1 50       4 if ($usesyslog) {
417             # never log to console - thats too slow, and
418             # it corrupts the DBD database connection!
419 0 0 0     0 if ($socketopts && ref($socketopts) eq 'ARRAY') {
    0 0        
      0        
420 0         0 Sys::Syslog::setlogsock(@$socketopts);
421             } elsif ($socketopts && (!ref($socketopts) || ref($socketopts) eq 'HASH')) {
422 0         0 Sys::Syslog::setlogsock($socketopts);
423             }
424             #elsif (!MSWIN) {
425             # Sys::Syslog::setlogsock('unix');
426             #}
427 0   0     0 my $ident = $args{ident} || scalar(caller(0));
428             try { # ignore errors
429 0     0   0 Sys::Syslog::openlog($ident, $syslogopts, $facility);
430             } catch {
431 0     0   0 $self->{error} = $_;
432 0         0 return $self;
433 0         0 };
434 0         0 $self->{status} = 1;
435             } else {
436 1         2 my $fh;
437             try {
438 1     1   79 $fh = new IO::File($file, "a");
439             } catch {
440 0     0   0 $self->{error} = sprintf("Can't open log file %s: %s", $file, $_);
441 0         0 return $self;
442 1         13 };
443 1 50       261 unless (defined($fh)) {
444 0         0 $self->{error} = sprintf("Can't open log file %s", $file);
445 0         0 return $self;
446             }
447 1 50       13 $fh->binmode(":raw:utf8") if $self->{"utf8"};
448 1         22 $fh->autoflush(1);
449 1         55 $self->{fh} = $fh;
450 1         3 $self->{status} = 1;
451             }
452              
453 1         4 return $self;
454             }
455             sub error {
456 2     2 1 5 my $self = shift;
457 2   50     8 return $self->{error} // '';
458             }
459             sub status {
460 4     4 1 601 my $self = shift;
461 4 50       18 return $self->{status} ? 1 : 0;
462             }
463              
464             sub log {
465 2     2 1 3 my $self = shift;
466 2   50     19 my $ll = shift // LOG_DEBUG;
467 2         7 my @msg = @_;
468 2 50       5 return 0 unless $self->status;
469 2         5 my $ident = $self->{ident};
470 2         5 my $level = _getLevel($ll);
471 2 50       6 unless (defined($level)) {
472 0         0 unshift(@msg, $ll);
473 0         0 $level = LOG_DEBUG;
474             }
475 2 100       10 return 0 if $level < $self->{level};
476              
477             # Flush!
478 1 50       4 if ($self->{usesyslog}) {
479 0         0 return $self->_flush_to_syslog($level, @msg);
480             } else {
481 1         5 return $self->_flush_to_file($level, @msg);
482             }
483              
484 0         0 return 0;
485             }
486 0     0 1 0 sub log_debug { shift->log(LOG_DEBUG, @_) };
487 1     1 1 5 sub log_info { shift->log(LOG_INFO, @_) };
488 0     0 1 0 sub log_notice { shift->log(LOG_NOTICE, @_) };
489 0     0 1 0 sub log_note { goto &log_notice };
490 0     0 1 0 sub log_warning { shift->log(LOG_WARNING, @_) };
491 0     0 1 0 sub log_warn { goto &log_warning };
492 0     0 1 0 sub log_error { shift->log(LOG_ERROR, @_) };
493 0     0 1 0 sub log_err { goto &log_error };
494 0     0 1 0 sub log_crit { shift->log(LOG_CRIT, @_) };
495 0     0 1 0 sub log_alert { shift->log(LOG_ALERT, @_) };
496 0     0 1 0 sub log_emerg { shift->log(LOG_EMERG, @_) };
497 0     0 1 0 sub log_emergency { goto &log_emerg };
498 0     0 1 0 sub log_fatal { shift->log(LOG_FATAL, @_) };
499 0     0 1 0 sub log_except { shift->log(LOG_EXCEPT, @_) };
500 0     0 1 0 sub log_exception { goto &log_except };
501              
502             # Internal methods
503             sub _flush_to_file {
504 1     1   1 my $self = shift;
505 1         2 my $level = shift;
506 1   50     3 my $format = shift // "";
507 1         2 my @message = @_;
508 1 50       4 return unless defined $level;
509              
510             # Adding
511 1         3 my @buffer = ();
512 1 50       14 unless ($self->{pure}) {
513 1         78 push @buffer, sprintf("[%s]", scalar(localtime(time())));
514 1         7 push @buffer, sprintf("[%s]", LOGLEVELSA()->[$level]);
515 1         5 push @buffer, sprintf("[%s]", $$);
516             }
517              
518             # Ident?
519 1         3 my $ident = $self->{ident};
520 1 50 33     8 push @buffer, sprintf("[%s]", $ident) if defined($ident) && length($ident);
521              
522             # Print
523 1         2 my $fh = $self->{fh};
524 1 50       12 if (defined($fh)) {
525             try {
526 1 50   1   58 $fh->print(join($self->{separator}, @buffer, "")) if @buffer;
527 1         77 $fh->printf($format, @message);
528 1         26 $fh->print("\n");
529             } catch {
530 0     0   0 $self->{error} = $_;
531 0         0 return 0;
532 1         11 };
533             } else {
534 0         0 $self->{status} = 0;
535 0         0 return 0;
536             }
537              
538 1         38 return 1;
539             }
540             sub _flush_to_syslog {
541 0     0   0 my $self = shift;
542 0         0 my $level = shift;
543 0   0     0 my $format = shift // "";
544 0         0 my @message = @_;
545 0 0       0 return unless defined $level;
546 0         0 my $sl = _to_syslog($level);
547             try { # ignore errors
548 0     0   0 Sys::Syslog::syslog($sl, $format, @message);
549             } catch {
550 0     0   0 $self->{error} = $_;
551 0         0 return 0;
552 0         0 };
553 0         0 return 1;
554             }
555              
556             # Internal functions
557             sub _getLevel { # Returns integer val: 0-9 -- ok, undef - incorrect :(
558 3     3   4 my $ll = shift;
559 3 50       8 return LOG_DEBUG unless defined $ll;
560 3         5 my $loglevels = LOGLEVELS;
561 3         27 my %levels = %$loglevels; # name => val
562 3         27 my %rlevels = reverse %$loglevels; # val => name
563 3 50 33     28 if (($ll =~ /^[0-9]+$/) && exists($rlevels{$ll})) { # integer val
    0 0        
564 3 50 33     24 return $ll if $ll >= LOG_DEBUG and $ll <= LOG_EXCEPT;
565 0         0 return LOG_DEBUG;
566             } elsif (($ll =~ /^[a-z]+$/i) && exists($levels{lc($ll)})) { # string
567 0         0 return $levels{lc($ll)};
568             }
569 0         0 return undef;
570             }
571             sub _to_syslog { # for syslog
572 0   0 0   0 my $level = shift // LOG_DEBUG;
573 0   0     0 return $SYSLOG_LEVEL_MAP{$level} // $SYSLOG_LEVEL_MAP{(LOG_DEBUG)};
574             }
575              
576             sub DESTROY {
577 1     1   6 my $self = shift;
578 1 50 33     6 return 1 unless $self && $self->status;
579 1 50       12 if ($self->{usesyslog}) {
580 0         0 Sys::Syslog::closelog();
581             } else {
582 1 50 33     31 $self->{fh}->close if defined($self->{fh}) && ref($self->{fh});
583             }
584 1         23 undef($self);
585 1         8 return 1;
586             }
587              
588             1;
589              
590             __END__