← Index
NYTProf Performance Profile   « line view »
For ./view
  Run on Fri Jul 31 19:05:14 2015
Reported on Fri Jul 31 19:08:10 2015

Filename/var/www/foswiki11/lib/Foswiki/Logger/PlainFile.pm
StatementsExecuted 158 statements in 2.93ms
Subroutines
Calls P F Exclusive
Time
Inclusive
Time
Subroutine
332364µs2.50msFoswiki::Logger::PlainFile::::log Foswiki::Logger::PlainFile::log
111181µs184µsFoswiki::Logger::PlainFile::::BEGIN@6 Foswiki::Logger::PlainFile::BEGIN@6
31196µs1.81msFoswiki::Logger::PlainFile::::_rotate Foswiki::Logger::PlainFile::_rotate
31164µs100µsFoswiki::Logger::PlainFile::::_getLogForLevel Foswiki::Logger::PlainFile::_getLogForLevel
42136µs36µsFoswiki::Logger::PlainFile::::_time2month Foswiki::Logger::PlainFile::_time2month
11116µs16µsFoswiki::Logger::PlainFile::::BEGIN@10 Foswiki::Logger::PlainFile::BEGIN@10
11113µs26µsFoswiki::Logger::PlainFile::::BEGIN@4 Foswiki::Logger::PlainFile::BEGIN@4
11113µs72µsFoswiki::Logger::PlainFile::::BEGIN@35 Foswiki::Logger::PlainFile::BEGIN@35
11110µs25µsFoswiki::Logger::PlainFile::::BEGIN@7 Foswiki::Logger::PlainFile::BEGIN@7
21110µs10µsFoswiki::Logger::PlainFile::::_stat Foswiki::Logger::PlainFile::_stat
11110µs15µsFoswiki::Logger::PlainFile::::BEGIN@5 Foswiki::Logger::PlainFile::BEGIN@5
3119µs9µsFoswiki::Logger::PlainFile::::_time Foswiki::Logger::PlainFile::_time
1119µs9µsFoswiki::Logger::PlainFile::::new Foswiki::Logger::PlainFile::new
1115µs5µsFoswiki::Logger::PlainFile::::BEGIN@33 Foswiki::Logger::PlainFile::BEGIN@33
1114µs4µsFoswiki::Logger::PlainFile::::BEGIN@9 Foswiki::Logger::PlainFile::BEGIN@9
1114µs4µsFoswiki::Logger::PlainFile::::BEGIN@32 Foswiki::Logger::PlainFile::BEGIN@32
0000s0sFoswiki::Logger::PlainFile::EventIterator::::hasNextFoswiki::Logger::PlainFile::EventIterator::hasNext
0000s0sFoswiki::Logger::PlainFile::EventIterator::::newFoswiki::Logger::PlainFile::EventIterator::new
0000s0sFoswiki::Logger::PlainFile::EventIterator::::nextFoswiki::Logger::PlainFile::EventIterator::next
0000s0sFoswiki::Logger::PlainFile::::eachEventSince Foswiki::Logger::PlainFile::eachEventSince
Call graph for these subroutines as a Graphviz dot language file.
Line State
ments
Time
on line
Calls Time
in subs
Code
1# See bottom of file for license and copyright information
2package Foswiki::Logger::PlainFile;
3
4229µs240µs
# spent 26µs (13+13) within Foswiki::Logger::PlainFile::BEGIN@4 which was called: # once (13µs+13µs) by Foswiki::logger at line 4
use strict;
# spent 26µs making 1 call to Foswiki::Logger::PlainFile::BEGIN@4 # spent 13µs making 1 call to strict::import
5225µs220µs
# spent 15µs (10+5) within Foswiki::Logger::PlainFile::BEGIN@5 which was called: # once (10µs+5µs) by Foswiki::logger at line 5
use warnings;
# spent 15µs making 1 call to Foswiki::Logger::PlainFile::BEGIN@5 # spent 5µs making 1 call to warnings::import
62195µs2186µs
# spent 184µs (181+3) within Foswiki::Logger::PlainFile::BEGIN@6 which was called: # once (181µs+3µs) by Foswiki::logger at line 6
use utf8;
# spent 184µs making 1 call to Foswiki::Logger::PlainFile::BEGIN@6 # spent 3µs making 1 call to utf8::import
7226µs239µs
# spent 25µs (10+14) within Foswiki::Logger::PlainFile::BEGIN@7 which was called: # once (10µs+14µs) by Foswiki::logger at line 7
use Assert;
# spent 25µs making 1 call to Foswiki::Logger::PlainFile::BEGIN@7 # spent 14µs making 1 call to Assert::import
8
9222µs14µs
# spent 4µs within Foswiki::Logger::PlainFile::BEGIN@9 which was called: # once (4µs+0s) by Foswiki::logger at line 9
use Foswiki::Logger ();
# spent 4µs making 1 call to Foswiki::Logger::PlainFile::BEGIN@9
10272µs116µs
# spent 16µs within Foswiki::Logger::PlainFile::BEGIN@10 which was called: # once (16µs+0s) by Foswiki::logger at line 10
use Foswiki::Configure::Load;
# spent 16µs making 1 call to Foswiki::Logger::PlainFile::BEGIN@10
11114µsour @ISA = ('Foswiki::Logger');
12
13=begin TML
14
15---+ package Foswiki::Logger::PlainFile
16
17Plain file implementation of the Foswiki Logger interface. Mostly
18compatible with TWiki (and Foswiki 1.0.0) log files, except that dates
19are recorded using ISO format, and include the time, and it dies when
20a log can't be written (rather than printing a warning).
21
22This logger implementation maps groups of levels to a single logfile, viz.
23 * =debug= messages are output to $Foswiki::cfg{Log}{Dir}/debug.log
24 * =info= messages are output to $Foswiki::cfg{Log}{Dir}/events.log
25 * =warning=, =error=, =critical=, =alert=, =emergency= messages are
26 output to $Foswiki::cfg{Log}{Dir}/error.log.
27 * =error=, =critical=, =alert=, and =emergency= messages are also
28 written to standard error (the webserver log file, usually)
29
30=cut
31
32221µs14µs
# spent 4µs within Foswiki::Logger::PlainFile::BEGIN@32 which was called: # once (4µs+0s) by Foswiki::logger at line 32
use Foswiki::Time ();
# spent 4µs making 1 call to Foswiki::Logger::PlainFile::BEGIN@32
33229µs15µs
# spent 5µs within Foswiki::Logger::PlainFile::BEGIN@33 which was called: # once (5µs+0s) by Foswiki::logger at line 33
use Foswiki::ListIterator ();
# spent 5µs making 1 call to Foswiki::Logger::PlainFile::BEGIN@33
34
3521.81ms2130µs
# spent 72µs (13+59) within Foswiki::Logger::PlainFile::BEGIN@35 which was called: # once (13µs+59µs) by Foswiki::logger at line 35
use constant TRACE => 0;
# spent 72µs making 1 call to Foswiki::Logger::PlainFile::BEGIN@35 # spent 59µs making 1 call to constant::import
36
37# Map from a log level to the root of a log file name
3815µsour %LEVEL2LOG = (
39 debug => 'debug',
40 info => 'events',
41 warning => 'error',
42 error => 'error',
43 critical => 'error',
44 alert => 'error',
45 emergency => 'error'
46);
47
4811µsour %nextCheckDue = (
49 debug => 0,
50 events => 0,
51 error => 0,
52);
53
54# Symbols used so we can override during unit testing
551200nsour $dontRotate = 0;
56312µs
# spent 9µs within Foswiki::Logger::PlainFile::_time which was called 3 times, avg 3µs/call: # 3 times (9µs+0s) by Foswiki::Logger::PlainFile::log at line 76, avg 3µs/call
sub _time { time() }
57214µs
# spent 10µs within Foswiki::Logger::PlainFile::_stat which was called 2 times, avg 5µs/call: # 2 times (10µs+0s) by Foswiki::Logger::PlainFile::_rotate at line 293, avg 5µs/call
sub _stat { stat(@_); }
58
59
# spent 9µs within Foswiki::Logger::PlainFile::new which was called: # once (9µs+0s) by Foswiki::logger at line 2078 of /var/www/foswiki11/lib/Foswiki.pm
sub new {
601500ns my $class = shift;
61112µs return bless( {}, $class );
62}
63
64=begin TML
65
66---++ ObjectMethod log($level, @fields)
67
68See Foswiki::Logger for the interface.
69
70=cut
71
72
# spent 2.50ms (364µs+2.14) within Foswiki::Logger::PlainFile::log which was called 3 times, avg 833µs/call: # once (112µs+1.73ms) by Foswiki::Plugins::load at line 205 of /var/www/foswiki11/lib/Foswiki/Plugins.pm # once (91µs+251µs) by Foswiki::logEvent at line 2269 of /var/www/foswiki11/lib/Foswiki.pm # once (162µs+150µs) by Foswiki::Plugins::enable at line 272 of /var/www/foswiki11/lib/Foswiki/Plugins.pm
sub log {
73315µs my ( $this, $level, @fields ) = @_;
74
75311µs3100µs my $log = _getLogForLevel($level);
# spent 100µs making 3 calls to Foswiki::Logger::PlainFile::_getLogForLevel, avg 33µs/call
7638µs39µs my $now = _time();
# spent 9µs making 3 calls to Foswiki::Logger::PlainFile::_time, avg 3µs/call
77311µs31.81ms _rotate( $LEVEL2LOG{$level}, $log, $now );
# spent 1.81ms making 3 calls to Foswiki::Logger::PlainFile::_rotate, avg 605µs/call
78315µs3207µs my $time = Foswiki::Time::formatTime( $now, 'iso', 'gmtime' );
# spent 207µs making 3 calls to Foswiki::Time::formatTime, avg 69µs/call
79
80 # Unfortunate compatibility requirement; need the level, but the old
81 # logfile format doesn't allow us to add fields. Since we are changing
82 # the date format anyway, the least pain is to concatenate the level
83 # to the date; Foswiki::Time::ParseTime can handle it, and it looks
84 # OK too.
8535µs unshift( @fields, "$time $level" );
861019µs my $message =
871351µs '| ' . join( ' | ', map { s/\|/&vbar;/g; $_ } @fields ) . ' |';
88
893600ns my $file;
9032µs my $mode = '>>';
91
92 # Item10764, SMELL UNICODE: actually, perhaps we should open the stream this
93 # way for any encoding, not just utf8. Babar says: check what Catalyst does.
94330µs35µs if ( $Foswiki::cfg{Site}{CharSet}
# spent 5µs making 3 calls to utf8::is_utf8, avg 2µs/call
95 && $Foswiki::cfg{Site}{CharSet} =~ /^utf-?8$/ )
96 {
97 $mode .= ":encoding($Foswiki::cfg{Site}{CharSet})";
98 }
99 elsif ( utf8::is_utf8($message) ) {
100 require Encode;
101 $message = Encode::encode( $Foswiki::cfg{Site}{CharSet}, $message, 0 );
102 }
103375µs if ( open( $file, $mode, $log ) ) {
104323µs print $file "$message\n";
105366µs close($file);
106 }
107 else {
108 if ( !-w $log ) {
109 die
110"ERROR: Could not open logfile $log for write. Your admin should 'configure' now and fix the errors!\n";
111 }
112
113 # die to force the admin to get permissions correct
114 die 'ERROR: Could not write ' . $message . ' to ' . "$log: $!\n";
115 }
116337µs if ( $level =~ /^(error|critical|alert|emergency)$/ ) {
117 print STDERR "$message\n";
118 }
119}
120
121{
122
123 # Private subclass of LineIterator that splits events into fields
1241300ns package Foswiki::Logger::PlainFile::EventIterator;
125178µs require Foswiki::LineIterator;
12615µs @Foswiki::Logger::PlainFile::EventIterator::ISA = ('Foswiki::LineIterator');
127
128 sub new {
129 my ( $class, $fh, $threshold, $level ) = @_;
130 my $this = $class->SUPER::new($fh);
131 $this->{_threshold} = $threshold;
132 $this->{_level} = $level;
133 return $this;
134 }
135
136 sub hasNext {
137 my $this = shift;
138 return 1 if defined $this->{_nextEvent};
139 while ( $this->SUPER::hasNext() ) {
140 my @line = split( /\s*\|\s*/, $this->SUPER::next() );
141 shift @line; # skip the leading empty cell
142 next unless scalar(@line) && defined $line[0];
143 if (
144 $line[0] =~ s/\s+$this->{_level}\s*$// # test the level
145 # accept a plain 'old' format date with no level only if reading info (statistics)
146 || $line[0] =~ /^\d{1,2} [a-z]{3} \d{4}/i
147 && $this->{_level} eq 'info'
148 )
149 {
150 $line[0] = Foswiki::Time::parseTime( $line[0] );
151 next
152 unless ( defined $line[0] )
153 ; # Skip record if time doesn't decode.
154 if ( $line[0] >= $this->{_threshold} ) { # test the time
155 $this->{_nextEvent} = \@line;
156 return 1;
157 }
158 }
159 }
160 return 0;
161 }
162
163 sub next {
164 my $this = shift;
165 my $data = $this->{_nextEvent};
166 undef $this->{_nextEvent};
167 return $data;
168 }
169}
170
171=begin TML
172
173---++ StaticMethod eachEventSince($time, $level) -> $iterator
174
175See Foswiki::Logger for the interface.
176
177This logger implementation maps groups of levels to a single logfile, viz.
178 * =info= messages are output together.
179 * =warning=, =error=, =critical=, =alert=, =emergency= messages are
180 output together.
181This method cannot
182
183=cut
184
185sub eachEventSince {
186 my ( $this, $time, $level ) = @_;
187 my $log = _getLogForLevel($level);
188
189 # Find the year-month for the current time
190 my $now = _time();
191 my $nowLogYear = Foswiki::Time::formatTime( $now, '$year', 'servertime' );
192 my $nowLogMonth = Foswiki::Time::formatTime( $now, '$mo', 'servertime' );
193
194 # Find the year-month for the first time in the range
195 my $logYear = Foswiki::Time::formatTime( $time, '$year', 'servertime' );
196 my $logMonth = Foswiki::Time::formatTime( $time, '$mo', 'servertime' );
197
198 # Get the names of all the logfiles in the time range
199 my @logs;
200 while ( !( $logMonth == $nowLogMonth && $logYear == $nowLogYear ) ) {
201 my $logfile = $log;
202 my $logTime = $logYear . sprintf( "%02d", $logMonth );
203 $logfile =~ s/\.log$/.$logTime/g;
204 push( @logs, $logfile );
205 $logMonth++;
206 if ( $logMonth == 13 ) {
207 $logMonth = 1;
208 $logYear++;
209 }
210 }
211
212 # Finally the current log
213 push( @logs, $log );
214
215 my @iterators;
216 foreach my $logfile (@logs) {
217 next unless -r $logfile;
218 my $fh;
219 if ( open( $fh, '<', $logfile ) ) {
220 push(
221 @iterators,
222 new Foswiki::Logger::PlainFile::EventIterator(
223 $fh, $time, $level
224 )
225 );
226 }
227 else {
228
229 # Would be nice to report this, but it's chicken and egg and
230 # besides, empty logfiles can happen.
231 print STDERR "Failed to open $logfile: $!" if (TRACE);
232 }
233 }
234 return new Foswiki::ListIterator( \@iterators ) if scalar(@iterators) == 0;
235 return $iterators[0] if scalar(@iterators) == 1;
236 return new Foswiki::AggregateIterator( \@iterators );
237}
238
239# Get the name of the log for a given reporting level
240
# spent 100µs (64+36) within Foswiki::Logger::PlainFile::_getLogForLevel which was called 3 times, avg 33µs/call: # 3 times (64µs+36µs) by Foswiki::Logger::PlainFile::log at line 75, avg 33µs/call
sub _getLogForLevel {
24135µs my $level = shift;
24235µs34µs ASSERT( defined $LEVEL2LOG{$level} ) if DEBUG;
# spent 4µs making 3 calls to Assert::ASSERTS_OFF, avg 1µs/call
243315µs my $log = $Foswiki::cfg{Log}{Dir} . '/' . $LEVEL2LOG{$level} . '.log';
244
245 # SMELL: Expand should not be needed, except if bin/configure tries
246 # to log to locations relative to $Foswiki::cfg{WorkingDir}, DataDir, etc.
247 # Windows seemed to be the most difficult to fix - this was the only thing
248 # that I could find that worked all the time.
249318µs332µs Foswiki::Configure::Load::expandValue($log);
# spent 32µs making 3 calls to Foswiki::Configure::Load::expandValue, avg 11µs/call
250314µs return $log;
251}
252
253
# spent 36µs within Foswiki::Logger::PlainFile::_time2month which was called 4 times, avg 9µs/call: # 2 times (25µs+0s) by Foswiki::Logger::PlainFile::_rotate at line 272, avg 12µs/call # 2 times (11µs+0s) by Foswiki::Logger::PlainFile::_rotate at line 294, avg 6µs/call
sub _time2month {
25442µs my $time = shift;
255415µs my @t = gmtime($time);
25642µs $t[5] += 1900;
257426µs return sprintf( '%0.4d%0.2d', $t[5], $t[4] + 1 );
258}
259
260# See if the log needs to be rotated. If the log was last modified
261# last month, we need to rotate it.
262
# spent 1.81ms (96µs+1.72) within Foswiki::Logger::PlainFile::_rotate which was called 3 times, avg 605µs/call: # 3 times (96µs+1.72ms) by Foswiki::Logger::PlainFile::log at line 77, avg 605µs/call
sub _rotate {
26336µs my ( $level, $log, $now ) = @_;
264
26531µs return if $dontRotate;
2663300ns return unless $level;
267
268 # Don't bother checking if we have checked in this process already
26937µs return if ( $now < $nextCheckDue{$level} );
270
271 # Work out the current month
27224µs225µs my $curMonth = _time2month($now);
# spent 25µs making 2 calls to Foswiki::Logger::PlainFile::_time2month, avg 12µs/call
273
274 # After this check, don't check again for a month.
27527µs $curMonth =~ /(\d{4})(\d{2})/;
276210µs my ( $y, $m ) = ( $1, $2 + 1 );
27722µs if ( $m > 12 ) {
278 $m = '01';
279 $y++;
280 }
281 else {
28223µs $m = sprintf( '%0.2d', $m );
283 }
284211µs21.67ms $nextCheckDue{$level} = Foswiki::Time::parseTime("$y-$m-01");
# spent 1.67ms making 2 calls to Foswiki::Time::parseTime, avg 836µs/call
285 print STDERR "Next log check due $nextCheckDue{$level} for $level\n"
286 if (TRACE);
287
288 # If there's no existing log, there's nothing to rotate
289213µs return unless -e $log;
290
291 # Check when the log was last modified. If it was in the previous
292 # month, if may need to be rotated.
29328µs210µs my @stat = _stat($log);
# spent 10µs making 2 calls to Foswiki::Logger::PlainFile::_stat, avg 5µs/call
29425µs211µs my $modMonth = _time2month( $stat[9] );
# spent 11µs making 2 calls to Foswiki::Logger::PlainFile::_time2month, avg 6µs/call
295 print STDERR "compare $modMonth, $curMonth\n" if (TRACE);
29628µs return if ( $modMonth == $curMonth );
297
298 # The log was last modified in a month that was not the current month.
299 # Rotate older entries out into month-by-month logfiles.
300
301 # Open the current log
302 my $lf;
303 unless ( open( $lf, '<', $log ) ) {
304 print STDERR
305 "ERROR: PlainFile Logger could not open logfile $log for read: $! \n";
306 return;
307 }
308
309 my %months;
310
311 local $/ = "\n";
312 my $line;
313 my $linecount;
314 my $stashline = '';
315 while ( $line = <$lf> ) {
316 $stashline .= $line;
317 my @event = split( /\s*\|\s*/, $line );
318 $linecount++;
319 if ( scalar(@event) > 7 ) {
320 print STDERR "Bad log "
321 . join( ' | ', @event )
322 . " | - Skipped \n "
323 if (TRACE);
324 $stashline = '';
325 next;
326 }
327
328 unless ( $event[1] ) {
329 print STDERR
330 "BAD LOGFILE LINE - skip $line - line $linecount in $log\n"
331 if (TRACE);
332 next;
333 }
334
335 #Item12022: parseTime bogs the CPU down here, so try a dumb regex first
336 # (assuming ISO8601 format Eg. 2000-01-31T23:59:00Z). Result: 4x speedup
337 my $eventMonth;
338 if ( $event[1] =~ /^(\d{4})-(\d{2})-\d{2}T[0-9:]+Z\b/ ) {
339 $eventMonth = $1 . $2;
340 }
341 else {
342 print STDERR ">> Non-ISO date string encountered\n" if (TRACE);
343 $eventMonth = _time2month( Foswiki::Time::parseTime( $event[1] ) );
344 }
345
346 if ( !defined $eventMonth ) {
347
348 print STDERR
349 ">> Bad time in log - skip: $line - line $linecount in $log\n"
350 if (TRACE);
351 next;
352 }
353
354 if ( $eventMonth < $curMonth ) {
355 push( @{ $months{$eventMonth} }, $stashline );
356 $stashline = '';
357 }
358 else {
359
360 # Reached the start of log entries for this month
361 print STDERR ">> Reached start of this month - count $linecount \n"
362 if (TRACE);
363 last;
364 }
365 }
366 print STDERR " Months "
367 . join( ' ', keys %months )
368 . " - processed $linecount records \n"
369 if (TRACE);
370
371 if ( !scalar( keys %months ) ) {
372
373 # no old months, we're done. The modify time on the current
374 # log will be touched by the next write, so we won't attempt
375 # to rotate again until next month.
376 print STDERR ">> No old months\n" if (TRACE);
377 close($lf);
378 return;
379 }
380
381 # Sook up the rest of the current log
382 $line ||= '';
383 $/ = undef;
384 my $curLog = $line . <$lf>;
385 close($lf);
386
387 foreach my $month ( keys %months ) {
388 my $bf;
389 my $backup = $log;
390 $backup =~ s/log$/$month/;
391 if ( -e $backup ) {
392 print STDERR
393"ERROR: PlainFile Logger could not create $backup - file exists\n";
394 return;
395 }
396 unless ( open( $bf, '>', $backup ) ) {
397 print STDERR
398 "ERROR: PlainFile Logger could not create $backup - $! \n";
399 return;
400 }
401 print $bf join( '', @{ $months{$month} } );
402 close($bf);
403 }
404
405 # Finally rewrite the shortened current log
406 unless ( open( $lf, '>', $log ) ) {
407 print STDERR
408"ERROR: PlainFile Logger could not open logfile $log for write: $! \n";
409 return;
410 }
411 print $lf $curLog;
412 close($lf);
413}
414
41516µs1;
416__END__