File Coverage

blib/lib/Log/Fast.pm
Criterion Covered Total %
statement 238 291 81.7
branch 68 144 47.2
condition 15 17 88.2
subroutine 34 34 100.0
pod 5 5 100.0
total 360 491 73.3


line stmt bran cond sub pod time code
1             # Implemented optimizations:
2             # * log-method's (ERR(), WARN(), etc.) implementation generated
3             # individually for each log-object (depending on it configuration)
4             # to include only minimum necessary code to do it work
5             # - each time log-object configuration changes (by calling config())
6             # log-method's implementation re-generated to comply new configuration
7             # - different log-objects may have different configuration and so will
8             # need different implementation for same log-methods - so we have to
9             # use unique package/class for each log-object (with class names looks
10             # like 'Log::Fast::_12345678') - these classes will implement only
11             # log-methods and inherit everything else from parent class (Log::Fast)
12             # * implementation for log-methods inactive on current log level replaced
13             # by empty 'sub{}'
14             # - each time log level changes (by calling config() or level())
15             # implementation of all log-methods updated according to current
16             # log level and set either to real implementation or empty 'sub{}'
17             # * if prefixes %D and/or %T are used, then cache will be used to store
18             # formatted date/time to avoid calculating it often than once per second
19             # * when logging to syslog, packet header (which may contain:
20             # log level, facility, timestamp, hostname, ident and pid) will be cached
21             # (one cached header per each log level)
22             # - if {add_timestamp} is true, then cached header will be used only for
23             # one second and then recalculated
24             # - if user change {ident} (by calling config() or ident()) cached
25             # headers will be recalculated
26             # * if log-methods will be called with single param sprintf() won't be used
27              
28             package Log::Fast;
29 7     7   400351 use 5.010001;
  7         103  
30 7     7   33 use warnings;
  7         11  
  7         220  
31 7     7   29 use strict;
  7         16  
  7         145  
32 7     7   3961 use utf8;
  7         78  
  7         32  
33 7     7   173 use Carp;
  7         18  
  7         452  
34              
35             our $VERSION = 'v2.0.1';
36              
37 7     7   66 use Scalar::Util qw( refaddr );
  7         7  
  7         239  
38 7     7   2863 use Socket;
  7         19149  
  7         2246  
39 7     7   2515 use Sys::Hostname ();
  7         5401  
  7         133  
40 7     7   2949 use Time::HiRes ();
  7         7302  
  7         147  
41 7     7   3392 use Sys::Syslog (); # for _PATH_LOG()
  7         93509  
  7         224  
42              
43              
44             # from RFC3164
45 7     7   44 use constant LOG_USER => 1*8;
  7         14  
  7         337  
46 7     7   36 use constant LOG_ERR => 3;
  7         11  
  7         313  
47 7     7   29 use constant LOG_WARNING => 4;
  7         11  
  7         232  
48 7     7   28 use constant LOG_NOTICE => 5;
  7         9  
  7         226  
49 7     7   29 use constant LOG_INFO => 6;
  7         9  
  7         218  
50 7     7   27 use constant LOG_DEBUG => 7;
  7         9  
  7         351  
51 7         942 use constant PRI => {
52             ERR => LOG_ERR,
53             WARN => LOG_WARNING,
54             NOTICE => LOG_NOTICE,
55             INFO => LOG_INFO,
56             DEBUG => LOG_DEBUG,
57 7     7   33 };
  7         12  
58              
59             use constant DEFAULTS => {
60             level => 'DEBUG',
61             prefix => q{},
62             type => 'fh',
63             # used only when {type}='fh':
64             fh => \*STDERR,
65             # used only when {type}='unix':
66             path => Sys::Syslog::_PATH_LOG() || '/dev/log', ## no critic(ProtectPrivateSubs)
67             facility => LOG_USER,
68             add_timestamp => 1,
69             add_hostname => 0,
70             hostname => Sys::Hostname::hostname(),
71 7         27 ident => do { my $s = $0; utf8::decode($s); $s =~ s{\A.*/(?=.)}{}xms; $s },
  7         133  
  7         34  
  7         33  
  7         990  
72             add_pid => 1,
73             pid => $$,
74 7     7   38 };
  7         9  
75              
76             my $GLOBAL;
77              
78             sub new {
79 11     11 1 2855 my ($class, $opt) = @_;
80 11   100     50 $opt ||= {};
81 11 100       45 croak 'options must be HASHREF' if ref $opt ne 'HASH';
82              
83 10         62 my $self = { # will also contain all keys defined in DEFAULTS constant
84             # used only when {type}='unix':
85             _sock => undef, # socket to {path}
86             _header_ERR => q{}, # cached "TIMESTAMP IDENT[PID]: "
87             _header_WARN => q{}, # --"--
88             _header_NOTICE => q{}, # --"--
89             _header_INFO => q{}, # --"--
90             _header_DEBUG => q{}, # --"--
91             _header_time => 0, # last update time for {_header_*}
92             # used only if {prefix} contain %D or %T:
93             _date => q{}, # cached "YYYY-MM-DD"
94             _time => q{}, # cached "HH:MM:SS"
95             _dt_time => 0, # last update time for {_date} and {_time}
96             };
97              
98 10         76 my $sub_class = $class . '::_' . refaddr($self);
99 7     7   41 { no strict 'refs';
  7         13  
  7         4760  
  10         17  
100 10         15 @{$sub_class.'::ISA'} = ( $class );
  10         272  
101             }
102 10         45 bless $self, $sub_class;
103              
104 10         17 $self->config({ %{ DEFAULTS() }, %{ $opt } }); ## no critic (ProhibitCommaSeparatedStatements)
  10         70  
  10         92  
105              
106 10         79 return $self;
107             }
108              
109             sub global {
110 6     6 1 5268 my $class = shift;
111 6   66     29 $GLOBAL ||= $class->new();
112 6         14 return $GLOBAL;
113             }
114              
115             sub config {
116 49     49 1 18172 my ($self, $opt) = @_;
117 49 100       147 croak 'options must be HASHREF' if ref $opt ne 'HASH';
118              
119 47         53 for my $key (keys %{ $opt }) {
  47         163  
120 167 100       299 if (!exists DEFAULTS->{ $key }) {
121 1         10 croak 'unknown option: '.$key;
122             }
123 166         283 $self->{ $key } = $opt->{ $key };
124             }
125              
126 46         135 $self->_generate_methods();
127 45 100       217 if ($self->{type} eq 'unix') {
128 14         32 $self->_connect_unix();
129 14         37 $self->ident($self->{ident});
130             }
131 45         154 $self->level($self->{level});
132              
133 44         88 return;
134             }
135              
136             sub level {
137 54     54 1 1455 my ($self, $level) = @_;
138 54         81 my $prev_level = $self->{level};
139 54 100       99 if (defined $level) {
140 52 100       95 if (!exists PRI->{$level}) {
141 2         3 croak '{level} must be one of: '.join ', ', keys %{ PRI() };
  2         22  
142             }
143 50         68 $self->{level} = $level;
144 50         112 $self->_setup_level();
145             }
146 52         75 return $prev_level;
147             }
148              
149             sub ident {
150 18     18 1 527 my ($self, $ident) = @_;
151 18         25 my $prev_ident = $self->{ident};
152 18 100       29 if (defined $ident) {
153 17         23 $self->{ident} = $ident;
154 17         26 $self->_update_header();
155             }
156 18         28 return $prev_ident;
157             }
158              
159             ### Internal
160              
161             sub _connect_unix {
162 14     14   23 my ($self) = @_;
163 14 50       459 socket $self->{_sock}, AF_UNIX, SOCK_DGRAM, 0 or croak "socket: $!";
164 14 50       59 connect $self->{_sock}, sockaddr_un($self->{path}) or croak "connect: $!";
165 14         284 return;
166             }
167              
168             sub _update_header {
169 17     17   21 my ($self) = @_;
170 17         19 my $h = q{};
171 17 100       28 if ($self->{add_timestamp}) {
172 3         5 $self->{_header_time} = time;
173 3         78 $h .= substr localtime $self->{_header_time}, 4, 16; ## no critic(ProhibitMagicNumbers)
174             }
175 17 100       26 if ($self->{add_hostname}) {
176 3         6 $h .= $self->{hostname} . q{ };
177             }
178 17         19 my $ident_utf8 = $self->{ident};
179 17         39 utf8::encode($ident_utf8);
180 17         20 $h .= $ident_utf8;
181 17 100       27 if ($self->{add_pid}) {
182 4         8 $h .= '[' . $self->{pid} . ']';
183             }
184 17         20 $h .= ': ';
185 17         16 for my $level (keys %{ PRI() }) {
  17         47  
186             $self->{'_header_'.$level}
187 85         163 = '<' . ($self->{facility} + PRI->{$level}) . '>' . $h;
188             }
189 17         26 return;
190             }
191              
192             sub _setup_level {
193 50     50   64 my ($self) = @_;
194 50         73 my $pkg = ref $self;
195 50         51 for my $level (keys %{ PRI() }) {
  50         125  
196 250         727 my $is_active = PRI->{$level} <= PRI->{$self->{level}};
197 7     7   45 no strict 'refs';
  7         9  
  7         226  
198 7     7   32 no warnings 'redefine';
  7         146  
  7         3532  
199 250 100   10   379 *{$pkg.q{::}.$level} = $is_active ? \&{$pkg.q{::_}.$level} : sub {};
  250         2433  
  236         493  
200             }
201 50         100 return;
202             }
203              
204             sub _generate_methods { ## no critic(ProhibitExcessComplexity)
205 46     46   75 my ($self) = @_;
206 46         61 my $pkg = ref $self;
207              
208 46         162 my %feature = map {$_=>1} $self->{prefix} =~ /%(.)/xmsg;
  44         115  
209 46   100     160 $feature{timestamp} = $self->{type} eq 'unix' && $self->{add_timestamp};
210              
211 46         133 my @pfx = split /(%.)/xms, $self->{prefix};
212 46         133 for (0 .. $#pfx) {
213 93         131 utf8::encode($pfx[$_]);
214             }
215              
216 46         59 for my $level (keys %{ PRI() }) {
  46         118  
217             # ... begin
218 226         330 my $code = <<'EOCODE';
219             sub {
220             my $self = shift;
221             my $msg = @_==1 ? $_[0] : sprintf shift, map {ref eq 'CODE' ? $_->() : $_} @_;
222             utf8::encode($msg);
223             EOCODE
224             # ... if needed, get current time
225 226 100 100     1006 if ($feature{S}) {
    100 66        
226 20         51 $code .= <<'EOCODE';
227             my $msec = sprintf '%.05f', Time::HiRes::time();
228             my $time = int $msec;
229             EOCODE
230             }
231             elsif ($feature{D} || $feature{T} || $feature{timestamp}) {
232 20         25 $code .= <<'EOCODE';
233             my $time = time;
234             EOCODE
235             }
236             # ... if needed, update caches
237 226 100 100     545 if ($feature{D} || $feature{T}) {
238 25         42 $code .= <<'EOCODE';
239             if ($self->{_dt_time} != $time) {
240             $self->{_dt_time} = $time;
241             my ($sec,$min,$hour,$mday,$mon,$year) = localtime $time;
242             $self->{_date} = sprintf '%04d-%02d-%02d', $year+1900, $mon+1, $mday;
243             $self->{_time} = sprintf '%02d:%02d:%02d', $hour, $min, $sec;
244             }
245             EOCODE
246             }
247 226 100       317 if ($feature{timestamp}) {
248 15         16 $code .= <<'EOCODE';
249             if ($self->{_header_time} != $time) {
250             $self->_update_header();
251             }
252             EOCODE
253             }
254             # ... calculate prefix
255 226         281 $code .= <<'EOCODE';
256             my $prefix = q{}
257             EOCODE
258 226         321 for my $pfx (@pfx) {
259 465 100       1345 if ($pfx eq q{%L}) { ## no critic(ProhibitCascadingIfElse)
    100          
    100          
    100          
    100          
    100          
    100          
    100          
260 30         92 $code .= <<"EOCODE"
261             . "\Q$level\E"
262             EOCODE
263             }
264             elsif ($pfx eq q{%S}) {
265 25         32 $code .= <<'EOCODE'
266             . $msec
267             EOCODE
268             }
269             elsif ($pfx eq q{%D}) {
270 25         32 $code .= <<'EOCODE'
271             . $self->{_date}
272             EOCODE
273             }
274             elsif ($pfx eq q{%T}) {
275 25         34 $code .= <<'EOCODE'
276             . $self->{_time}
277             EOCODE
278             }
279             elsif ($pfx eq q{%P}) {
280 30         40 $code .= <<'EOCODE'
281             . caller(0)
282             EOCODE
283             }
284             elsif ($pfx eq q{%F}) {
285 30         33 $code .= <<'EOCODE'
286             . do { my $s = (caller(1))[3] || q{}; substr $s, 1+rindex $s, ':' }
287             EOCODE
288             }
289             elsif ($pfx eq q{%_}) {
290 30         41 $code .= <<'EOCODE'
291             . do { my $n=0; 1 while caller(2 + $n++); ' ' x $n }
292             EOCODE
293             }
294             elsif ($pfx eq q{%%}) {
295 25         35 $code .= <<'EOCODE'
296             . '%'
297             EOCODE
298             }
299             else {
300 245         548 $code .= <<"EOCODE"
301             . "\Q$pfx\E"
302             EOCODE
303             }
304             }
305 226         246 $code .= <<'EOCODE';
306             ;
307             EOCODE
308             # ... output
309 226 100       921 if ($self->{type} eq 'fh') {
    100          
310 155         254 $code .= <<'EOCODE';
311             print { $self->{fh} } $prefix, $msg, "\n" or die "print() to log: $!";
312             EOCODE
313             }
314             elsif ($self->{type} eq 'unix') {
315 70         136 $code .= <<"EOCODE";
316             my \$header = \$self->{_header_$level};
317             EOCODE
318 70         89 $code .= <<'EOCODE';
319             send $self->{_sock}, $header.$prefix.$msg, 0 or do {
320             $self->_connect_unix();
321             send $self->{_sock}, $header.$prefix.$msg, 0 or die "send() to syslog: $!";
322             };
323             EOCODE
324             }
325             else {
326 1         10 croak '{type} should be "fh" or "unix"';
327             }
328             # ... end
329 225         252 $code .= <<'EOCODE';
330             }
331             EOCODE
332             # install generated method
333 7     7   53 no strict 'refs';
  7         11  
  7         219  
334 7     7   31 no warnings 'redefine';
  7         10  
  7         689  
335 225 0   2   34284 *{$pkg.'::_'.$level} = eval $code; ## no critic (ProhibitStringyEval)
  225 50       1263  
  2 50       5  
  2 0       6  
  0 50       0  
  2 50       5  
  2 0       4  
  2 50       3  
  2 50       10  
  1 0       3  
  1 50       3  
  0 100       0  
  1 0       3  
  1 50       1  
  1 50       1  
  1 0       7  
  4 0       83  
  4 0       11  
  0 0       0  
  4 0       9  
  4 0       5  
  4 0       6  
  4 50       23  
  2 0       313  
  2 0       6  
  0 50       0  
  2 50       5  
  2 50       4  
  2 0       2  
  2 50       20  
  4 0       10  
  4 0       9  
  0 0       0  
  4 0       10  
  4 0       5  
  4 0       4  
  4 0       21  
  0 0       0  
  0 0       0  
  0 0       0  
  27 0       11852  
  27 0       80  
  0 0       0  
  27 0       58  
  27 0       29  
  27         27  
  27         71  
  27         63  
  27         157  
  27         83  
  27         31  
  27         116  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  1         2  
  1         3  
  0         0  
  1         3  
  1         14  
  1         4  
  1         5  
  1         1  
  1         18  
  1         6  
  1         3  
  1         4  
  0         0  
336             }
337              
338 45         134 return;
339             }
340              
341              
342             1; # Magic true value required at end of module
343             __END__