File Coverage

blib/lib/DiaColloDB/Logger.pm
Criterion Covered Total %
statement 15 91 16.4
branch 4 62 6.4
condition 0 34 0.0
subroutine 5 31 16.1
pod 22 22 100.0
total 46 240 19.1


line stmt bran cond sub pod time code
1             ## -*- Mode: CPerl -*-
2             ##
3             ## File: DiaColloDB::Logger.pm
4             ## Author: Bryan Jurish <moocow@cpan.org>
5             ## Description: DiaColloDB logging (using Log::Log4perl)
6              
7             package DiaColloDB::Logger;
8             #use DiaColloDB::Utils ':profile';
9 2     2   14 use Carp;
  2         4  
  2         110  
10 2     2   3290 use Log::Log4perl;
  2         118562  
  2         10  
11 2     2   110 use File::Basename;
  2         86  
  2         148  
12 2     2   14 use strict;
  2         6  
  2         390  
13              
14             ##==============================================================================
15             ## Globals
16             ##==============================================================================
17              
18             our @ISA = qw();
19              
20             our ($MIN_LEVEL); ##-- minimum log level
21             our (%defaultLogOpts); ##-- default log options
22             BEGIN {
23 2     2   22 $MIN_LEVEL = $Log::Log4perl::Level::LEVELS{(sort {$a<=>$b} keys(%Log::Log4perl::Level::LEVELS))[0]};
  32         56  
24 2 50       3730 %defaultLogOpts =
    50          
    50          
    50          
25             (
26             l4pfile => undef, ##-- formerly $logConfigFile
27             watch => undef, ##-- watch l4pfile (undef or secs)?
28             rootLevel => ($^W ? 'WARN' : 'FATAL'),
29             level => ($^W ? $MIN_LEVEL : 'INFO'),
30             stderr => 1,
31             logdate => 1,
32             logtime => 1,
33             logwhich => [qw(DiaColloDB DocClassify DDC.XS DTA.CAB DTA.TokWrap DWDSTermClassifier)],
34             file => undef,
35             rotate => undef, ##-- default: haveFileRotate()
36             syslog => 0,
37             sysLevel => ($^W ? 'debug' : 'info'),
38             sysName => File::Basename::basename($0),
39             sysIdent => undef, ##-- default=$opts{sysName}
40             sysFacility => ($0 =~ m/(?:server|daemon)/i ? 'daemon' : 'user'),
41             );
42             }
43              
44             ## $DEFAULT_LOG_CONF = PACKAGE->defaultLogConf(%opts)
45             ## + default configuration for Log::Log4perl
46             ## + see Log::Log4perl(3pm), Log::Log4perl::Config(3pm) for details
47             ## + %opts:
48             ## rootLevel => $LEVEL_OR_UNDEF, ##-- min root log level (default='WARN' or 'FATAL', depending on $^W)
49             ## level => $LEVEL_OR_UNDEF, ##-- min log level (default=$MIN_LEVEL or 'INFO', depending on $^W)
50             ## stderr => $bool, ##-- whether to log to stderr (default=1)
51             ## logtime => $bool, ##-- whether to log time-stamps on stderr (default=0)
52             ## logdate => $bool, ##-- whether to log date+time-stamps on stderr (default=0)
53             ## logwhich => \@classes, ##-- log4perl-style classes to log (default=qw(DiaColloDB DocClassify DTA.CAB DTA.TokWrap))
54             ## file => $filename, ##-- log to $filename if true
55             ## rotate => $bool, ##-- use Log::Dispatch::FileRotate if available and $filename is true
56             ## syslog => $bool, ##-- use Log::Dispatch::Syslog if available and true (default=false)
57             ## sysLevel => $level, ##-- minimum level for syslog (default='debug' or 'info', depending on $^W)
58             ## ## : available levels: debug,info,notice,warning,error,critical,alert,emergency (== 0..7)
59             ## sysName => $sysName, ##-- name for syslog (default=basename($0))
60             ## sysIdent => $sysIdent, ##-- ident string for syslog (default=$sysName)
61             ## sysFacility => $facility, ##-- facility for syslog (default='daemon')
62             sub defaultLogConf {
63 0     0 1   my ($that,%opts) = @_;
64 0           %opts = (%defaultLogOpts,%opts);
65 0 0 0       $opts{rotate} = haveFileRotate() if (defined($opts{file}) && !defined($opts{rotate}));
66 0 0         $opts{sysIdent} = $opts{sysName} if (!defined($opts{sysIdent}));
67              
68              
69             ##-- generate base config
70 0           my $cfg = "
71             ##-- Loggers
72             log4perl.oneMessagePerAppender = 1 ##-- suppress duplicate messages to the same appender
73             ";
74              
75 0 0         if ($opts{rootLevel}) {
76             ##-- root logger
77 0           $cfg .= "log4perl.rootLogger = $opts{rootLevel}, AppStderr\n";
78             }
79              
80 0 0 0       if ($opts{stderr} || $opts{file} || $opts{syslog}) {
      0        
81             ##-- local package logger(s)
82 0 0         if ($opts{level}) {
83 0   0       my $which = $opts{logwhich} // [qw(DiaColloDB DocClassify DTA.CAB DTA.TokWrap)];
84 0 0 0       $which = [grep {($_//'') ne ''} split(/[\,\s]+/,$which)] if ($which && !ref($which));
  0   0        
85 0           foreach (@$which) {
86             $cfg .= "log4perl.logger.$_ = $opts{level}, ".join(", ",
87             ($opts{stderr} ? 'AppStderr' : qw()),
88             ($opts{file} ? 'AppFile' : qw()),
89 0 0         ($opts{syslog} ? 'AppSyslog' : qw()),
    0          
    0          
90             )."\n";
91             }
92             }
93             ##-- avoid duplicate messages
94 0           $cfg .= "log4perl.additivity.DTA = 0\n";
95             }
96              
97             ##-- appenders: utils
98 0           $cfg .= "
99             ##-- Appenders: Utilities
100             log4perl.PatternLayout.cspec.G = sub { return File::Basename::basename(\"$::0\"); }
101             ";
102              
103             ##-- appender: stderr
104             my $stderr_date = (($opts{logdate} && $opts{logtime}) ? '%d{yyyy-MM-dd HH:mm:ss} '
105 0 0 0       : ($opts{logtime} ? '%d{HH:mm:ss} ' : ''));
    0          
106 0           $cfg .= "
107             ##-- Appender: AppStderr
108             log4perl.appender.AppStderr = Log::Log4perl::Appender::Screen
109             log4perl.appender.AppStderr.stderr = 1
110             log4perl.appender.AppStderr.binmode = :utf8
111             log4perl.appender.AppStderr.layout = Log::Log4perl::Layout::PatternLayout
112             log4perl.appender.AppStderr.layout.ConversionPattern = ${stderr_date}%G[%P] %p: %c: %m%n
113             ";
114              
115             ##-- appender: syslog
116 0 0         if ($opts{syslog}) {
117 0           eval 'use Log::Dispatch::Syslog;';
118 0 0         die "could not use Log::Dispatch::Syslog: $@" if ($@);
119 0           $cfg .= "
120             log4perl.appender.AppSyslog = Log::Dispatch::Syslog
121             log4perl.appender.AppSyslog.name = $opts{sysName}
122             log4perl.appender.AppSyslog.ident = $opts{sysIdent}
123             log4perl.appender.AppSyslog.min_level = $opts{sysLevel}
124             log4perl.appender.AppSyslog.facility = $opts{sysFacility}
125             log4perl.appender.AppSyslog.logopt = pid
126             log4perl.appender.AppSyslog.layout = Log::Log4perl::Layout::PatternLayout
127             log4perl.appender.AppSyslog.layout.ConversionPattern = (%p) %c: %m%n
128             ";
129             }
130              
131 0 0 0       if ($opts{file} && $opts{rotate}) {
    0          
132             ##-- rotating file appender
133 0           eval 'use Log::Dispatch::FileRotate;';
134 0 0         die "could not use Log::Dispatch::FileRotate: $@" if ($@);
135 0           $cfg .= "
136             ##-- Appender: AppFile: rotating file appender
137             log4perl.appender.AppFile = Log::Dispatch::FileRotate
138             log4perl.appender.AppFile.min_level = debug
139             log4perl.appender.AppFile.filename = $opts{file}
140             log4perl.appender.AppFile.binmode = :utf8
141             log4perl.appender.AppFile.mode = append
142             log4perl.appender.AppFile.size = 10485760
143             log4perl.appender.AppFile.max = 10
144             log4perl.appender.AppFile.layout = Log::Log4perl::Layout::PatternLayout
145             log4perl.appender.AppFile.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} [%P] (%p) %c: %m%n
146             ";
147             }
148             elsif ($opts{file}) {
149             ##-- raw file appender
150 0           $cfg .= "
151             ##-- Appender: AppFile: raw file appender (no automatic log rotation)
152             log4perl.appender.AppFile = Log::Log4perl::Appender::File
153             log4perl.appender.AppFile.filename = $opts{file}
154             log4perl.appender.AppFile.mode = append
155             log4perl.appender.AppFile.utf8 = 1
156             log4perl.appender.AppFile.layout = Log::Log4perl::Layout::PatternLayoutl
157             log4perl.appender.AppFile.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} [%P] (%p) %c: %m%n
158             ";
159             }
160              
161 0           return $cfg;
162             }
163              
164             ## $bool = CLASS::haveFileRotate()
165             ## + returns true if Log::Dispatch::FileRotate is available
166             sub haveFileRotate {
167 0 0   0 1   return 1 if (defined($Log::Dispatch::FileRotate::VERSION));
168 0           eval "use Log::Dispatch::FileRotate;";
169 0 0 0       return 1 if (defined($Log::Dispatch::FileRotate::VERSION) && !$@);
170 0           $@='';
171 0           return 0;
172             }
173              
174             ## $bool = CLASS::haveSyslog()
175             ## + returns true if Log::Dispatch::Syslog is available
176             sub haveSyslog {
177 0 0   0 1   return 1 if (defined($Log::Dispatch::Syslog::VERSION));
178 0           eval "use Log::Dispatch::Syslog;";
179 0 0 0       return 1 if (defined($Log::Dispatch::Syslog::VERSION) && !$@);
180 0           $@='';
181 0           return 0;
182             }
183              
184             ##==============================================================================
185             ## Functions: Initialization
186             ##==============================================================================
187              
188             ## undef = PACKAGE->logInit(%opts) ##-- use default configuration with %opts
189             ## + %opts: see defaultLogConf()
190             ## + all log calls in the DiaColloDB namespace should use a subcategory of 'DiaColloDB'
191             ## + only needs to be called once; see Log::Log4perl->initialized()
192             sub logInit {
193 0     0 1   my $that = shift;
194 0           my %opts = (%defaultLogOpts,@_);
195 0           binmode(\*STDERR,':utf8');
196 0 0         if (!defined($opts{l4pfile})) {
197 0           my $confstr = $that->defaultLogConf(%opts);
198 0           Log::Log4perl::init(\$confstr);
199             } else {
200 0           eval 'use Log::Dispatch::Syslog;';
201 0           eval 'use Log::Dispatch::FileRotate;';
202 0 0         if (defined($opts{watch})) {
203 0           Log::Log4perl::init_and_watch($opts{l4pfile},$opts{watch});
204             } else {
205 0           Log::Log4perl::init($opts{l4pfile});
206             }
207             }
208             #__PACKAGE__->info("initialized logging facility");
209             }
210              
211             ## undef = PACKAGE->ensureLog(@args) ##-- ensure a Log::Log4perl has been initialized
212             sub ensureLog {
213 0     0 1   my $that = shift;
214 0 0         $that->logInit(@_) if (!Log::Log4perl->initialized);
215             }
216              
217             ##==============================================================================
218             ## Methods: get logger
219             ##==============================================================================
220              
221             ## $logger = $class_or_obj->logger()
222             ## $logger = $class_or_obj->logger($category)
223             ## + wrapper for Log::Log4perl::get_logger($category)
224             ## + $category defaults to ref($class_or_obj)||$class_or_obj
225 0   0 0 1   sub logger { Log::Log4perl::get_logger(ref($_[0])||$_[0]); }
226              
227             ##==============================================================================
228             ## Methods: messages
229             ##==============================================================================
230              
231             ## undef = $class_or_obj->trace(@msg)
232             ## + be sure you have called Log::Log4perl::init() or similar first
233             ## - e.g. DiaColloDB::Logger::logInit()
234 0     0 1   sub trace { $_[0]->logger->trace(@_[1..$#_]); }
235 0     0 1   sub debug { $_[0]->logger->debug(@_[1..$#_]); }
236 0     0 1   sub info { $_[0]->logger->info(@_[1..$#_]); }
237 0     0 1   sub warn { $_[0]->logger->warn(@_[1..$#_]); }
238 0     0 1   sub error { $_[0]->logger->error(@_[1..$#_]); }
239 0     0 1   sub fatal { $_[0]->logger->fatal(@_[1..$#_]); }
240              
241             ## undef = $class_or_obj->llog($level, @msg)
242             ## + $level is some constant exported by Log::Log4perl::Level
243 0     0 1   sub llog { $_[0]->logger->log(@_[1..$#_]); }
244              
245             ## undef = $class_or_obj->vlog($methodname_or_coderef_or_undef, @msg)
246             ## + calls $methodname_or_coderef_or_undef($class_or_obj,@msg) if defined
247             ## + e.g. $class_or_obj->vlog('trace', @msg)
248             sub vlog {
249 0 0   0 1   return if (!defined($_[1]));
250 0 0 0       my $sub = UNIVERSAL::isa($_[1],'CODE') ? $_[1] : (UNIVERSAL::can($_[0],$_[1]) || UNIVERSAL::can($_[0],lc($_[1])));
251 0 0         return if (!defined($sub));
252 0           return $sub->($_[0],@_[2..$#_]);
253             }
254              
255             ##==============================================================================
256             ## Methods: carp & friends
257             ##==============================================================================
258              
259             ## undef = $class_or_obj->logcroak(@msg)
260 0     0 1   sub logwarn { $_[0]->logger->logwarn(@_[1..$#_]); } # warn w/o stack trace
261 0     0 1   sub logcarp { $_[0]->logger->logcarp(@_[1..$#_]); } # warn w/ 1-level stack trace
262 0     0 1   sub logcluck { $_[0]->logger->logcluck(@_[1..$#_]); } # warn w/ full stack trace
263              
264 0     0 1   sub logdie { $_[0]->logger->logdie(@_[1..$#_]); } # die w/o stack trace
265 0     0 1   sub logcroak { $_[0]->logger->logcroak(@_[1..$#_]); } # die w/ 1-level stack trace
266 0     0 1   sub logconfess { $_[0]->logger->logconfess(@_[1..$#_]); } # die w/ full stack trace
267              
268              
269             ##==============================================================================
270             ## Utils: Getopt::Long specification
271             ##==============================================================================
272              
273             ## %getoptLongHash = $PACKAGE->cldbLogOptions(%localOpts)
274             ## + %localOpts
275             ## verbose => $bool, ##-- if true, add 'verbose|v' as alias for 'log-level'
276             ## + adds support for logging options:
277             ## 'log-level|loglevel|ll|L=s' => \$defaultLogOpts{level},
278             ## 'log-config|logconfig|log4perl-config|l4p-config|l4p=s' => \$defaultLogOpts{l4pfile},
279             ## 'log-watch|logwatch|watch|lw=i' => \$defaultLogOpts{watch},
280             ## 'nolog-watch|nologwatch|nowatch|nolw' => sub { $defaultLogOpts{watch}=undef; },
281             ## 'log-stderr|stderr|lse!' => \$defaultLogOpts{stderr},
282             ## 'log-file|lf=s' => \$defaultLogOpts{file},
283             ## 'nolog-file|nolf' => sub { $defaultLogOpts{file}=undef; },
284             ## 'log-rotate|rotate|lr!' => \$defaultLogOpts{rotate},
285             ## 'log-syslog|syslog|ls!' => \$defaultLogOpts{syslog},
286             ## 'log-option|logopt|lo=s' => \%defaultLogOpts,
287             sub cldbLogOptions {
288 0     0 1   my ($that,%opts) = @_;
289             return
290             (##-- Logging Options
291 0     0     ($opts{verbose} ? ('verbose|v=s' => sub { $defaultLogOpts{level}=uc($_[1]); }) : qw()),
292 0     0     'log-level|loglevel|ll|L=s' => sub { $defaultLogOpts{level}=uc($_[1]); },
293             'log-config|logconfig|log4perl-config|l4p-config|l4p=s' => \$defaultLogOpts{l4pfile},
294             'log-watch|logwatch|watch|lw=i' => \$defaultLogOpts{watch},
295 0     0     'nolog-watch|nologwatch|nowatch|nolw' => sub { $defaultLogOpts{watch}=undef; },
296             'log-stderr|stderr|lse!' => \$defaultLogOpts{stderr},
297             'log-file|lf=s' => \$defaultLogOpts{file},
298 0     0     'nolog-file|nolf' => sub { $defaultLogOpts{file}=undef; },
299             'log-rotate|rotate|lr!' => \$defaultLogOpts{rotate},
300             'log-syslog|syslog|ls!' => \$defaultLogOpts{syslog},
301 0 0         'log-option|logopt|lo=s%' => \%defaultLogOpts,
302             );
303             }
304              
305             ##==============================================================================
306             ## Utils: Profiling
307             ##==============================================================================
308              
309             ## undef = $logger->logProfile($level, $elapsed_secs, $ntoks, $nchrs)
310             sub logProfile {
311 0     0 1   $_[0]->vlog($_[1], profile_str(@_[2..$#_]));
312             }
313              
314              
315             1; ##-- be happy