File Coverage

blib/lib/Log/Fast.pm
Criterion Covered Total %
statement 229 291 78.6
branch 67 144 46.5
condition 12 17 70.5
subroutine 34 34 100.0
pod 5 5 100.0
total 347 491 70.6


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   317750 use 5.010001;
  7         18  
30 7     7   24 use warnings;
  7         7  
  7         158  
31 7     7   20 use strict;
  7         9  
  7         106  
32 7     7   3377 use utf8;
  7         55  
  7         25  
33 7     7   168 use Carp;
  7         7  
  7         445  
34              
35             our $VERSION = 'v2.0.0';
36              
37 7     7   25 use Scalar::Util qw( refaddr );
  7         8  
  7         291  
38 7     7   2691 use Socket;
  7         14635  
  7         2458  
39 7     7   2621 use Sys::Hostname ();
  7         5252  
  7         133  
40 7     7   3151 use Time::HiRes ();
  7         6426  
  7         136  
41 7     7   3585 use Sys::Syslog (); # for _PATH_LOG()
  7         72437  
  7         181  
42              
43              
44             # from RFC3164
45 7     7   30 use constant LOG_USER => 1*8;
  7         6  
  7         453  
46 7     7   34 use constant LOG_ERR => 3;
  7         9  
  7         243  
47 7     7   23 use constant LOG_WARNING => 4;
  7         5  
  7         239  
48 7     7   27 use constant LOG_NOTICE => 5;
  7         6  
  7         212  
49 7     7   20 use constant LOG_INFO => 6;
  7         7  
  7         216  
50 7     7   19 use constant LOG_DEBUG => 7;
  7         5  
  7         311  
51 7         802 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   23 };
  7         4  
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         20 ident => do { my $s = $0; utf8::decode($s); $s =~ s{\A.*/(?=.)}{}xms; $s },
  7         80  
  7         20  
  7         33  
  7         802  
72             add_pid => 1,
73             pid => $$,
74 7     7   26 };
  7         7  
75              
76             my $GLOBAL;
77              
78             sub new {
79 11     11 1 3295 my ($class, $opt) = @_;
80 11   100     46 $opt ||= {};
81 11 100       50 croak 'options must be HASHREF' if ref $opt ne 'HASH';
82              
83 10         79 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         50 my $sub_class = $class . '::_' . refaddr($self);
99 7     7   25 { no strict 'refs';
  7         8  
  7         3618  
  10         12  
100 10         8 @{$sub_class.'::ISA'} = ( $class );
  10         116  
101             }
102 10         340 bless $self, $sub_class;
103              
104 10         11 $self->config({ %{ DEFAULTS() }, %{ $opt } }); ## no critic (ProhibitCommaSeparatedStatements)
  10         35  
  10         78  
105              
106 10         67 return $self;
107             }
108              
109             sub global {
110 6     6 1 5327 my $class = shift;
111 6   66     28 $GLOBAL ||= $class->new();
112 6         12 return $GLOBAL;
113             }
114              
115             sub config {
116 49     49 1 18511 my ($self, $opt) = @_;
117 49 100       154 croak 'options must be HASHREF' if ref $opt ne 'HASH';
118              
119 47         38 for my $key (keys %{ $opt }) {
  47         117  
120 167 100       210 if (!exists DEFAULTS->{ $key }) {
121 1         9 croak 'unknown option: '.$key;
122             }
123 166         224 $self->{ $key } = $opt->{ $key };
124             }
125              
126 46         94 $self->_generate_methods();
127 45 100       194 if ($self->{type} eq 'unix') {
128 14         23 $self->_connect_unix();
129 14         30 $self->ident($self->{ident});
130             }
131 45         95 $self->level($self->{level});
132              
133 44         56 return;
134             }
135              
136             sub level {
137 54     54 1 1393 my ($self, $level) = @_;
138 54         59 my $prev_level = $self->{level};
139 54 100       90 if (defined $level) {
140 52 100       132 if (!exists PRI->{$level}) {
141 2         2 croak '{level} must be one of: '.join ', ', keys %{ PRI() };
  2         20  
142             }
143 50         45 $self->{level} = $level;
144 50         79 $self->_setup_level();
145             }
146 52         55 return $prev_level;
147             }
148              
149             sub ident {
150 18     18 1 474 my ($self, $ident) = @_;
151 18         19 my $prev_ident = $self->{ident};
152 18 100       25 if (defined $ident) {
153 17         15 $self->{ident} = $ident;
154 17         21 $self->_update_header();
155             }
156 18         23 return $prev_ident;
157             }
158              
159             ### Internal
160              
161             sub _connect_unix {
162 14     14   13 my ($self) = @_;
163 14 50       230 socket $self->{_sock}, AF_UNIX, SOCK_DGRAM, 0 or croak "socket: $!";
164 14 50       42 connect $self->{_sock}, sockaddr_un($self->{path}) or croak "connect: $!";
165 14         242 return;
166             }
167              
168             sub _update_header {
169 17     17   13 my ($self) = @_;
170 17         13 my $h = q{};
171 17 100       23 if ($self->{add_timestamp}) {
172 3         7 $self->{_header_time} = time;
173 3         120 $h .= substr localtime $self->{_header_time}, 4, 16; ## no critic(ProhibitMagicNumbers)
174             }
175 17 100       27 if ($self->{add_hostname}) {
176 3         4 $h .= $self->{hostname} . q{ };
177             }
178 17         14 my $ident_utf8 = $self->{ident};
179 17         23 utf8::encode($ident_utf8);
180 17         15 $h .= $ident_utf8;
181 17 100       19 if ($self->{add_pid}) {
182 4         20 $h .= '[' . $self->{pid} . ']';
183             }
184 17         54 $h .= ': ';
185 17         12 for my $level (keys %{ PRI() }) {
  17         39  
186             $self->{'_header_'.$level}
187 85         139 = '<' . ($self->{facility} + PRI->{$level}) . '>' . $h;
188             }
189 17         23 return;
190             }
191              
192             sub _setup_level {
193 50     50   46 my ($self) = @_;
194 50         52 my $pkg = ref $self;
195 50         33 for my $level (keys %{ PRI() }) {
  50         99  
196 250         491 my $is_active = PRI->{$level} <= PRI->{$self->{level}};
197 7     7   27 no strict 'refs';
  7         7  
  7         155  
198 7     7   20 no warnings 'redefine';
  7         7  
  7         2483  
199 250 100   10   263 *{$pkg.q{::}.$level} = $is_active ? \&{$pkg.q{::_}.$level} : sub {};
  250         1960  
  236         360  
200             }
201 50         82 return;
202             }
203              
204             sub _generate_methods { ## no critic(ProhibitExcessComplexity)
205 46     46   346 my ($self) = @_;
206 46         47 my $pkg = ref $self;
207              
208 46         126 my %feature = map {$_=>1} $self->{prefix} =~ /%(.)/xmsg;
  44         64  
209 46   66     118 $feature{timestamp} = $self->{type} eq 'unix' && $self->{add_timestamp};
210              
211 46         119 my @pfx = split /(%.)/xms, $self->{prefix};
212 46         113 for (0 .. $#pfx) {
213 93         95 utf8::encode($pfx[$_]);
214             }
215              
216 46         38 for my $level (keys %{ PRI() }) {
  46         100  
217             # ... begin
218 226         225 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 66     868 if ($feature{S}) {
    100 66        
226 20         20 $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         22 $code .= <<'EOCODE';
233             my $time = time;
234             EOCODE
235             }
236             # ... if needed, update caches
237 226 100 66     449 if ($feature{D} || $feature{T}) {
238 25         22 $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       278 if ($feature{timestamp}) {
248 15         13 $code .= <<'EOCODE';
249             if ($self->{_header_time} != $time) {
250             $self->_update_header();
251             }
252             EOCODE
253             }
254             # ... calculate prefix
255 226         181 $code .= <<'EOCODE';
256             my $prefix = q{}
257             EOCODE
258 226         274 for my $pfx (@pfx) {
259 465 100       1274 if ($pfx eq q{%L}) { ## no critic(ProhibitCascadingIfElse)
    100          
    100          
    100          
    100          
    100          
    100          
    100          
260 30         42 $code .= <<"EOCODE"
261             . "\Q$level\E"
262             EOCODE
263             }
264             elsif ($pfx eq q{%S}) {
265 25         28 $code .= <<'EOCODE'
266             . $msec
267             EOCODE
268             }
269             elsif ($pfx eq q{%D}) {
270 25         24 $code .= <<'EOCODE'
271             . $self->{_date}
272             EOCODE
273             }
274             elsif ($pfx eq q{%T}) {
275 25         21 $code .= <<'EOCODE'
276             . $self->{_time}
277             EOCODE
278             }
279             elsif ($pfx eq q{%P}) {
280 30         26 $code .= <<'EOCODE'
281             . caller(0)
282             EOCODE
283             }
284             elsif ($pfx eq q{%F}) {
285 30         29 $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         24 $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         331 $code .= <<"EOCODE"
301             . "\Q$pfx\E"
302             EOCODE
303             }
304             }
305 226         158 $code .= <<'EOCODE';
306             ;
307             EOCODE
308             # ... output
309 226 100       773 if ($self->{type} eq 'fh') {
    100          
310 155         131 $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         106 $code .= <<"EOCODE";
316             my \$header = \$self->{_header_$level};
317             EOCODE
318 70         77 $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         11 croak '{type} should be "fh" or "unix"';
327             }
328             # ... end
329 225         162 $code .= <<'EOCODE';
330             }
331             EOCODE
332             # install generated method
333 7     7   24 no strict 'refs';
  7         8  
  7         152  
334 7     7   22 no warnings 'redefine';
  7         6  
  7         571  
335 225 0   3   29651 *{$pkg.'::_'.$level} = eval $code; ## no critic (ProhibitStringyEval)
  225 50       992  
  3 50       45  
  3 0       8  
  0 50       0  
  3 50       5  
  3 0       3  
  3 50       2  
  3 100       19  
  4 50       4  
  4 50       6  
  0 100       0  
  4 0       7  
  4 50       2  
  4 50       4  
  4 0       14  
  4 50       18  
  4 0       5  
  0 0       0  
  4 50       4  
  4 0       4  
  4 0       4  
  3 0       7  
  2 0       313  
  2 0       5  
  1 0       1  
  3 0       5  
  2 0       1  
  30 0       11279  
  30 0       135  
  2 0       4  
  29 0       48  
  28 0       33  
  29 0       38  
  29 0       85  
  29 0       59  
  29 0       127  
  27 0       70  
  28 0       22  
  28 0       116  
  1 0       3  
  1 0       2  
  1 0       5  
  1 0       2  
  1 0       2  
  1         9  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  4         80  
  2         6  
  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            
336             }
337              
338 45         110 return;
339             }
340              
341              
342             1; # Magic true value required at end of module
343             __END__